Move the low-level communication to a common file.

This is done in preparation for the implementation of the new Petrel
protocol, which shares the low level communication with the existing
Predator protocol.
This commit is contained in:
Jef Driesen 2013-04-09 22:57:08 +02:00
parent 389933400f
commit 6f3de69f0d
4 changed files with 421 additions and 320 deletions

View File

@ -45,6 +45,7 @@ libdivecomputer_la_SOURCES = \
cressi_leonardo.c cressi_leonardo_parser.c \
zeagle_n2ition3.c \
atomics_cobalt.c atomics_cobalt_parser.c \
shearwater_common.h shearwater_common.c \
shearwater_predator.c shearwater_predator_parser.c \
ringbuffer.h ringbuffer.c \
checksum.h checksum.c \

355
src/shearwater_common.c Normal file
View File

@ -0,0 +1,355 @@
/*
* libdivecomputer
*
* Copyright (C) 2013 Jef Driesen
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#include <string.h> // memcmp, memcpy
#include <stdlib.h> // malloc, free
#include "shearwater_common.h"
#include "context-private.h"
#define SZ_PACKET 254
// SLIP special character codes
#define END 0xC0
#define ESC 0xDB
#define ESC_END 0xDC
#define ESC_ESC 0xDD
#define EXITCODE(n) ((n) < 0 ? (n) : 0)
dc_status_t
shearwater_common_open (shearwater_common_device_t *device, dc_context_t *context, const char *name)
{
// Open the device.
int rc = serial_open (&device->port, context, name);
if (rc == -1) {
ERROR (context, "Failed to open the serial port.");
return DC_STATUS_IO;
}
// Set the serial communication protocol (115200 8N1).
rc = serial_configure (device->port, 115200, 8, SERIAL_PARITY_NONE, 1, SERIAL_FLOWCONTROL_NONE);
if (rc == -1) {
ERROR (context, "Failed to set the terminal attributes.");
serial_close (device->port);
return DC_STATUS_IO;
}
// Set the timeout for receiving data (3000ms).
if (serial_set_timeout (device->port, 3000) == -1) {
ERROR (context, "Failed to set the timeout.");
serial_close (device->port);
return DC_STATUS_IO;
}
// Make sure everything is in a sane state.
serial_sleep (device->port, 300);
serial_flush (device->port, SERIAL_QUEUE_BOTH);
return DC_STATUS_SUCCESS;
}
dc_status_t
shearwater_common_close (shearwater_common_device_t *device)
{
// Close the device.
if (serial_close (device->port) == -1) {
return DC_STATUS_IO;
}
return DC_STATUS_SUCCESS;
}
static int
shearwater_common_slip_write (shearwater_common_device_t *device, const unsigned char data[], unsigned int size)
{
int n = 0;
const unsigned char end[] = {END};
const unsigned char esc_end[] = {ESC, ESC_END};
const unsigned char esc_esc[] = {ESC, ESC_ESC};
#if 0
// Send an initial END character to flush out any data that may have
// accumulated in the receiver due to line noise.
n = serial_write (device->port, end, sizeof (end));
if (n != sizeof (end)) {
return EXITCODE(n);
}
#endif
for (unsigned int i = 0; i < size; ++i) {
const unsigned char *seq = NULL;
unsigned int len = 0;
switch (data[i]) {
case END:
// Escape the END character.
seq = esc_end;
len = sizeof (esc_end);
break;
case ESC:
// Escape the ESC character.
seq = esc_esc;
len = sizeof (esc_esc);
break;
default:
// Normal character.
seq = data + i;
len = 1;
break;
}
n = serial_write (device->port, seq, len);
if (n != len) {
return EXITCODE(n);
}
}
// Send the END character to indicate the end of the packet.
n = serial_write (device->port, end, sizeof (end));
if (n != sizeof (end)) {
return EXITCODE(n);
}
return size;
}
static int
shearwater_common_slip_read (shearwater_common_device_t *device, unsigned char data[], unsigned int size)
{
unsigned int received = 0;
// Read bytes until a complete packet has been received. If the
// buffer runs out of space, bytes are dropped. The caller can
// detect this condition because the return value will be larger
// than the supplied buffer size.
while (1) {
unsigned char c = 0;
int n = 0;
// Get a single character to process.
n = serial_read (device->port, &c, 1);
if (n != 1) {
return EXITCODE(n);
}
switch (c) {
case END:
// If it's an END character then we're done.
// As a minor optimization, empty packets are ignored. This
// is to avoid bothering the upper layers with all the empty
// packets generated by the duplicate END characters which
// are sent to try to detect line noise.
if (received)
return received;
else
break;
case ESC:
// If it's an ESC character, get another character and then
// figure out what to store in the packet based on that.
n = serial_read (device->port, &c, 1);
if (n != 1) {
return EXITCODE(n);
}
// If it's not one of the two escaped characters, then we
// have a protocol violation. The best bet seems to be to
// leave the byte alone and just stuff it into the packet.
switch (c) {
case ESC_END:
c = END;
break;
case ESC_ESC:
c = ESC;
break;
}
// Fall-through!
default:
if (received < size)
data[received] = c;
received++;
}
}
return received;
}
static dc_status_t
shearwater_common_transfer (shearwater_common_device_t *device, const unsigned char input[], unsigned int isize, unsigned char output[], unsigned int osize, unsigned int *actual)
{
dc_device_t *abstract = (dc_device_t *) device;
unsigned char packet[SZ_PACKET + 4];
int n = 0;
if (isize > SZ_PACKET || osize > SZ_PACKET)
return DC_STATUS_INVALIDARGS;
// Setup the request packet.
packet[0] = 0xFF;
packet[1] = 0x01;
packet[2] = isize + 1;
packet[3] = 0x00;
memcpy (packet + 4, input, isize);
// Send the request packet.
n = shearwater_common_slip_write (device, packet, isize + 4);
if (n != isize + 4) {
ERROR (abstract->context, "Failed to send the request packet.");
if (n < 0)
return DC_STATUS_IO;
else
return DC_STATUS_TIMEOUT;
}
// Receive the response packet.
n = shearwater_common_slip_read (device, packet, sizeof (packet));
if (n <= 0 || n > sizeof (packet)) {
ERROR (abstract->context, "Failed to receive the response packet.");
if (n < 0)
return DC_STATUS_IO;
else if (n > sizeof (packet))
return DC_STATUS_PROTOCOL;
else
return DC_STATUS_TIMEOUT;
}
// Validate the packet header.
if (n < 4 || packet[0] != 0x01 || packet[1] != 0xFF || packet[3] != 0x00) {
ERROR (abstract->context, "Invalid packet header.");
return DC_STATUS_PROTOCOL;
}
// Validate the packet length.
unsigned int length = packet[2];
if (length < 1 || length - 1 + 4 != n || length - 1 > osize) {
ERROR (abstract->context, "Invalid packet header.");
return DC_STATUS_PROTOCOL;
}
memcpy (output, packet + 4, length - 1);
*actual = length - 1;
return DC_STATUS_SUCCESS;
}
dc_status_t
shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buffer, unsigned int address, unsigned int size)
{
dc_device_t *abstract = (dc_device_t *) device;
dc_status_t rc = DC_STATUS_SUCCESS;
unsigned int n = 0;
unsigned char req_init[] = {
0x35, 0x00, 0x34,
(address >> 24) & 0xFF,
(address >> 16) & 0xFF,
(address >> 8) & 0xFF,
(address ) & 0xFF,
(size >> 16) & 0xFF,
(size >> 8) & 0xFF,
(size ) & 0xFF};
unsigned char req_block[] = {0x36, 0x00};
unsigned char req_quit[] = {0x37};
unsigned char response[SZ_PACKET];
// Erase the current contents of the buffer.
if (!dc_buffer_clear (buffer) || !dc_buffer_reserve (buffer, size)) {
ERROR (abstract->context, "Insufficient buffer space available.");
return DC_STATUS_NOMEMORY;
}
// Enable progress notifications.
dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
progress.maximum = 3 + size + 1;
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
// Transfer the init request.
rc = shearwater_common_transfer (device, req_init, sizeof (req_init), response, 3, &n);
if (rc != DC_STATUS_SUCCESS) {
return rc;
}
// Verify the init response.
if (n != 3 || response[0] != 0x75 || response[1] != 0x10 || response[2] != 0x82) {
ERROR (abstract->context, "Unexpected response packet.");
return DC_STATUS_PROTOCOL;
}
// Update and emit a progress event.
progress.current += 3;
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
unsigned char block = 1;
unsigned int nbytes = 0;
while (nbytes < size) {
// Transfer the block request.
req_block[1] = block;
rc = shearwater_common_transfer (device, req_block, sizeof (req_block), response, sizeof (response), &n);
if (rc != DC_STATUS_SUCCESS) {
return rc;
}
// Verify the block header.
if (n < 2 || response[0] != 0x76 || response[1] != block) {
ERROR (abstract->context, "Unexpected response packet.");
return DC_STATUS_PROTOCOL;
}
// Verify the block length.
unsigned int length = n - 2;
if (nbytes + length > size) {
ERROR (abstract->context, "Unexpected packet size.");
return DC_STATUS_PROTOCOL;
}
// Update and emit a progress event.
progress.current += length;
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
dc_buffer_append (buffer, response + 2, length);
nbytes += length;
block++;
}
// Transfer the quit request.
rc = shearwater_common_transfer (device, req_quit, sizeof (req_quit), response, 2, &n);
if (rc != DC_STATUS_SUCCESS) {
return rc;
}
// Verify the quit response.
if (n != 2 || response[0] != 0x77 || response[1] != 0x00) {
ERROR (abstract->context, "Unexpected response packet.");
return DC_STATUS_PROTOCOL;
}
// Update and emit a progress event.
progress.current += 1;
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
return DC_STATUS_SUCCESS;
}

49
src/shearwater_common.h Normal file
View File

@ -0,0 +1,49 @@
/*
* libdivecomputer
*
* Copyright (C) 2013 Jef Driesen
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301 USA
*/
#ifndef SHEARWATER_COMMON_H
#define SHEARWATER_COMMON_H
#include "device-private.h"
#include "serial.h"
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
typedef struct shearwater_common_device_t {
dc_device_t base;
serial_t *port;
} shearwater_common_device_t;
dc_status_t
shearwater_common_open (shearwater_common_device_t *device, dc_context_t *context, const char *name);
dc_status_t
shearwater_common_close (shearwater_common_device_t *device);
dc_status_t
shearwater_common_download (shearwater_common_device_t *device, dc_buffer_t *buffer, unsigned int address, unsigned int size);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* SHEARWATER_COMMON_H */

View File

@ -24,9 +24,10 @@
#include <libdivecomputer/shearwater_predator.h>
#include "shearwater_common.h"
#include "context-private.h"
#include "device-private.h"
#include "serial.h"
#include "array.h"
#define ISINSTANCE(device) dc_device_isinstance((device), &shearwater_predator_device_vtable)
@ -34,24 +35,14 @@
#define PREDATOR 2
#define PETREL 3
#define SZ_PACKET 254
#define SZ_BLOCK 0x80
#define SZ_MEMORY 0x20080
#define RB_PROFILE_BEGIN 0
#define RB_PROFILE_END 0x1F600
// SLIP special character codes
#define END 0xC0
#define ESC 0xDB
#define ESC_END 0xDC
#define ESC_ESC 0xDD
#define EXITCODE(n) ((n) < 0 ? (n) : 0)
typedef struct shearwater_predator_device_t {
dc_device_t base;
serial_t *port;
shearwater_common_device_t base;
unsigned char fingerprint[4];
} shearwater_predator_device_t;
@ -71,183 +62,11 @@ static const dc_device_vtable_t shearwater_predator_device_vtable = {
};
static int
shearwater_predator_slip_write (shearwater_predator_device_t *device, const unsigned char data[], unsigned int size)
{
int n = 0;
const unsigned char end[] = {END};
const unsigned char esc_end[] = {ESC, ESC_END};
const unsigned char esc_esc[] = {ESC, ESC_ESC};
#if 0
// Send an initial END character to flush out any data that may have
// accumulated in the receiver due to line noise.
n = serial_write (device->port, end, sizeof (end));
if (n != sizeof (end)) {
return EXITCODE(n);
}
#endif
for (unsigned int i = 0; i < size; ++i) {
const unsigned char *seq = NULL;
unsigned int len = 0;
switch (data[i]) {
case END:
// Escape the END character.
seq = esc_end;
len = sizeof (esc_end);
break;
case ESC:
// Escape the ESC character.
seq = esc_esc;
len = sizeof (esc_esc);
break;
default:
// Normal character.
seq = data + i;
len = 1;
break;
}
n = serial_write (device->port, seq, len);
if (n != len) {
return EXITCODE(n);
}
}
// Send the END character to indicate the end of the packet.
n = serial_write (device->port, end, sizeof (end));
if (n != sizeof (end)) {
return EXITCODE(n);
}
return size;
}
static int
shearwater_predator_slip_read (shearwater_predator_device_t *device, unsigned char data[], unsigned int size)
{
unsigned int received = 0;
// Read bytes until a complete packet has been received. If the
// buffer runs out of space, bytes are dropped. The caller can
// detect this condition because the return value will be larger
// than the supplied buffer size.
while (1) {
unsigned char c = 0;
int n = 0;
// Get a single character to process.
n = serial_read (device->port, &c, 1);
if (n != 1) {
return EXITCODE(n);
}
switch (c) {
case END:
// If it's an END character then we're done.
// As a minor optimization, empty packets are ignored. This
// is to avoid bothering the upper layers with all the empty
// packets generated by the duplicate END characters which
// are sent to try to detect line noise.
if (received)
return received;
else
break;
case ESC:
// If it's an ESC character, get another character and then
// figure out what to store in the packet based on that.
n = serial_read (device->port, &c, 1);
if (n != 1) {
return EXITCODE(n);
}
// If it's not one of the two escaped characters, then we
// have a protocol violation. The best bet seems to be to
// leave the byte alone and just stuff it into the packet.
switch (c) {
case ESC_END:
c = END;
break;
case ESC_ESC:
c = ESC;
break;
}
// Fall-through!
default:
if (received < size)
data[received] = c;
received++;
}
}
return received;
}
static dc_status_t
shearwater_predator_transfer (shearwater_predator_device_t *device, const unsigned char input[], unsigned int isize, unsigned char output[], unsigned int osize, unsigned int *actual)
{
dc_device_t *abstract = (dc_device_t *) device;
unsigned char packet[SZ_PACKET + 4];
int n = 0;
if (isize > SZ_PACKET || osize > SZ_PACKET)
return DC_STATUS_INVALIDARGS;
// Setup the request packet.
packet[0] = 0xFF;
packet[1] = 0x01;
packet[2] = isize + 1;
packet[3] = 0x00;
memcpy (packet + 4, input, isize);
// Send the request packet.
n = shearwater_predator_slip_write (device, packet, isize + 4);
if (n != isize + 4) {
ERROR (abstract->context, "Failed to send the request packet.");
if (n < 0)
return DC_STATUS_IO;
else
return DC_STATUS_TIMEOUT;
}
// Receive the response packet.
n = shearwater_predator_slip_read (device, packet, sizeof (packet));
if (n <= 0 || n > sizeof (packet)) {
ERROR (abstract->context, "Failed to receive the response packet.");
if (n < 0)
return DC_STATUS_IO;
else if (n > sizeof (packet))
return DC_STATUS_PROTOCOL;
else
return DC_STATUS_TIMEOUT;
}
// Validate the packet header.
if (n < 4 || packet[0] != 0x01 || packet[1] != 0xFF || packet[3] != 0x00) {
ERROR (abstract->context, "Invalid packet header.");
return DC_STATUS_PROTOCOL;
}
// Validate the packet length.
unsigned int length = packet[2];
if (length < 1 || length - 1 + 4 != n || length - 1 > osize) {
ERROR (abstract->context, "Invalid packet header.");
return DC_STATUS_PROTOCOL;
}
memcpy (output, packet + 4, length - 1);
*actual = length - 1;
return DC_STATUS_SUCCESS;
}
dc_status_t
shearwater_predator_device_open (dc_device_t **out, dc_context_t *context, const char *name)
{
dc_status_t rc = DC_STATUS_SUCCESS;
if (out == NULL)
return DC_STATUS_INVALIDARGS;
@ -259,41 +78,18 @@ shearwater_predator_device_open (dc_device_t **out, dc_context_t *context, const
}
// Initialize the base class.
device_init (&device->base, context, &shearwater_predator_device_vtable);
device_init (&device->base.base, context, &shearwater_predator_device_vtable);
// Set the default values.
device->port = NULL;
memset (device->fingerprint, 0, sizeof (device->fingerprint));
// Open the device.
int rc = serial_open (&device->port, context, name);
if (rc == -1) {
ERROR (context, "Failed to open the serial port.");
rc = shearwater_common_open (&device->base, context, name);
if (rc != DC_STATUS_SUCCESS) {
free (device);
return DC_STATUS_IO;
return rc;
}
// Set the serial communication protocol (115200 8N1).
rc = serial_configure (device->port, 115200, 8, SERIAL_PARITY_NONE, 1, SERIAL_FLOWCONTROL_NONE);
if (rc == -1) {
ERROR (context, "Failed to set the terminal attributes.");
serial_close (device->port);
free (device);
return DC_STATUS_IO;
}
// Set the timeout for receiving data (3000ms).
if (serial_set_timeout (device->port, 3000) == -1) {
ERROR (context, "Failed to set the timeout.");
serial_close (device->port);
free (device);
return DC_STATUS_IO;
}
// Make sure everything is in a sane state.
serial_sleep (device->port, 300);
serial_flush (device->port, SERIAL_QUEUE_BOTH);
*out = (dc_device_t *) device;
return DC_STATUS_SUCCESS;
@ -303,18 +99,16 @@ shearwater_predator_device_open (dc_device_t **out, dc_context_t *context, const
static dc_status_t
shearwater_predator_device_close (dc_device_t *abstract)
{
shearwater_predator_device_t *device = (shearwater_predator_device_t*) abstract;
dc_status_t rc = DC_STATUS_SUCCESS;
shearwater_common_device_t *device = (shearwater_common_device_t *) abstract;
// Close the device.
if (serial_close (device->port) == -1) {
free (device);
return DC_STATUS_IO;
}
rc = shearwater_common_close (device);
// Free memory.
free (device);
return DC_STATUS_SUCCESS;
return rc;
}
@ -335,110 +129,12 @@ shearwater_predator_device_set_fingerprint (dc_device_t *abstract, const unsigne
}
static dc_status_t
shearwater_predator_download (dc_device_t *abstract, dc_buffer_t *buffer, unsigned int address, unsigned int size)
{
shearwater_predator_device_t *device = (shearwater_predator_device_t *) abstract;
dc_status_t rc = DC_STATUS_SUCCESS;
unsigned int n = 0;
unsigned char req_init[] = {
0x35, 0x00, 0x34,
(address >> 24) & 0xFF,
(address >> 16) & 0xFF,
(address >> 8) & 0xFF,
(address ) & 0xFF,
(size >> 16) & 0xFF,
(size >> 8) & 0xFF,
(size ) & 0xFF};
unsigned char req_block[] = {0x36, 0x00};
unsigned char req_quit[] = {0x37};
unsigned char response[SZ_PACKET];
// Erase the current contents of the buffer.
if (!dc_buffer_clear (buffer) || !dc_buffer_reserve (buffer, size)) {
ERROR (abstract->context, "Insufficient buffer space available.");
return DC_STATUS_NOMEMORY;
}
// Enable progress notifications.
dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
progress.maximum = 3 + size + 1;
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
// Transfer the init request.
rc = shearwater_predator_transfer (device, req_init, sizeof (req_init), response, 3, &n);
if (rc != DC_STATUS_SUCCESS) {
return rc;
}
// Verify the init response.
if (n != 3 || response[0] != 0x75 || response[1] != 0x10 || response[2] != 0x82) {
ERROR (abstract->context, "Unexpected response packet.");
return DC_STATUS_PROTOCOL;
}
// Update and emit a progress event.
progress.current += 3;
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
unsigned char block = 1;
unsigned int nbytes = 0;
while (nbytes < size) {
// Transfer the block request.
req_block[1] = block;
rc = shearwater_predator_transfer (device, req_block, sizeof (req_block), response, sizeof (response), &n);
if (rc != DC_STATUS_SUCCESS) {
return rc;
}
// Verify the block header.
if (n < 2 || response[0] != 0x76 || response[1] != block) {
ERROR (abstract->context, "Unexpected response packet.");
return DC_STATUS_PROTOCOL;
}
// Verify the block length.
unsigned int length = n - 2;
if (nbytes + length > size) {
ERROR (abstract->context, "Unexpected packet size.");
return DC_STATUS_PROTOCOL;
}
// Update and emit a progress event.
progress.current += length;
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
dc_buffer_append (buffer, response + 2, length);
nbytes += length;
block++;
}
// Transfer the quit request.
rc = shearwater_predator_transfer (device, req_quit, sizeof (req_quit), response, 2, &n);
if (rc != DC_STATUS_SUCCESS) {
return rc;
}
// Verify the quit response.
if (n != 2 || response[0] != 0x77 || response[1] != 0x00) {
ERROR (abstract->context, "Unexpected response packet.");
return DC_STATUS_PROTOCOL;
}
// Update and emit a progress event.
progress.current += 1;
device_event_emit (abstract, DC_EVENT_PROGRESS, &progress);
return DC_STATUS_SUCCESS;
}
static dc_status_t
shearwater_predator_device_dump (dc_device_t *abstract, dc_buffer_t *buffer)
{
return shearwater_predator_download (abstract, buffer, 0xDD000000, SZ_MEMORY);
shearwater_common_device_t *device = (shearwater_common_device_t *) abstract;
return shearwater_common_download (device, buffer, 0xDD000000, SZ_MEMORY);
}