#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include "uwatec_memomouse.h"
#include "context-private.h"
#include "device-private.h"
#include "checksum.h"
#include "array.h"
#define ISINSTANCE(device) dc_device_isinstance((device), &uwatec_memomouse_device_vtable)
#define PACKETSIZE 126
#define ACK 0x60
#define NAK 0xA8
typedef struct uwatec_memomouse_device_t {
dc_device_t base;
dc_iostream_t *iostream;
unsigned int timestamp;
unsigned int devtime;
dc_ticks_t systime;
} uwatec_memomouse_device_t;
static dc_status_t uwatec_memomouse_device_set_fingerprint (dc_device_t *device, const unsigned char data[], unsigned int size);
static dc_status_t uwatec_memomouse_device_dump (dc_device_t *abstract, dc_buffer_t *buffer);
static dc_status_t uwatec_memomouse_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata);
static const dc_device_vtable_t uwatec_memomouse_device_vtable = {
sizeof(uwatec_memomouse_device_t),
DC_FAMILY_UWATEC_MEMOMOUSE,
uwatec_memomouse_device_set_fingerprint,
NULL,
NULL,
uwatec_memomouse_device_dump,
uwatec_memomouse_device_foreach,
NULL,
NULL
};
static dc_status_t
uwatec_memomouse_extract_dives (dc_device_t *device, const unsigned char data[], unsigned int size, dc_dive_callback_t callback, void *userdata);
dc_status_t
uwatec_memomouse_device_open (dc_device_t **out, dc_context_t *context, dc_iostream_t *iostream)
{
dc_status_t status = DC_STATUS_SUCCESS;
uwatec_memomouse_device_t *device = NULL;
if (out == NULL)
return DC_STATUS_INVALIDARGS;
device = (uwatec_memomouse_device_t *) dc_device_allocate (context, &uwatec_memomouse_device_vtable);
if (device == NULL) {
ERROR (context, "Failed to allocate memory.");
return DC_STATUS_NOMEMORY;
}
device->iostream = iostream;
device->timestamp = 0;
device->systime = (dc_ticks_t) -1;
device->devtime = 0;
status = dc_iostream_configure (device->iostream, 9600, 8, DC_PARITY_NONE, DC_STOPBITS_ONE, DC_FLOWCONTROL_NONE);
if (status != DC_STATUS_SUCCESS) {
ERROR (context, "Failed to set the terminal attributes.");
goto error_free;
}
status = dc_iostream_set_timeout (device->iostream, 1000);
if (status != DC_STATUS_SUCCESS) {
ERROR (context, "Failed to set the timeout.");
goto error_free;
}
status = dc_iostream_set_dtr (device->iostream, 0);
if (status != DC_STATUS_SUCCESS) {
ERROR (context, "Failed to clear the DTR line.");
goto error_free;
}
status = dc_iostream_set_rts (device->iostream, 0);
if (status != DC_STATUS_SUCCESS) {
ERROR (context, "Failed to clear the RTS line.");
goto error_free;
}
dc_iostream_purge (device->iostream, DC_DIRECTION_ALL);
*out = (dc_device_t*) device;
return DC_STATUS_SUCCESS;
error_free:
dc_device_deallocate ((dc_device_t *) device);
return status;
}
static dc_status_t
uwatec_memomouse_device_set_fingerprint (dc_device_t *abstract, const unsigned char data[], unsigned int size)
{
uwatec_memomouse_device_t *device = (uwatec_memomouse_device_t*) abstract;
if (size && size != 4)
return DC_STATUS_INVALIDARGS;
if (size)
device->timestamp = array_uint32_le (data);
else
device->timestamp = 0;
return DC_STATUS_SUCCESS;
}
static dc_status_t
uwatec_memomouse_read_packet (uwatec_memomouse_device_t *device, unsigned char data[], unsigned int size, unsigned int *result)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_device_t *abstract = (dc_device_t *) device;
assert (result != NULL);
status = dc_iostream_read (device->iostream, data, 1, NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to receive the answer.");
return status;
}
array_reverse_bits (data, 1);
unsigned int len = data[0];
if (len + 2 > size) {
ERROR (abstract->context, "Unexpected answer start byte(s).");
return DC_STATUS_PROTOCOL;
}
status = dc_iostream_read (device->iostream, data + 1, len + 1, NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to receive the answer.");
return status;
}
array_reverse_bits (data + 1, len + 1);
unsigned char crc = data[len + 1];
unsigned char ccrc = checksum_xor_uint8 (data, len + 1, 0x00);
if (crc != ccrc) {
ERROR (abstract->context, "Unexpected answer checksum.");
return DC_STATUS_PROTOCOL;
}
*result = len;
return DC_STATUS_SUCCESS;
}
static dc_status_t
uwatec_memomouse_read_packet_outer (uwatec_memomouse_device_t *device, unsigned char data[], unsigned int size, unsigned int *result)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_device_t *abstract = (dc_device_t *) device;
dc_status_t rc = DC_STATUS_SUCCESS;
while ((rc = uwatec_memomouse_read_packet (device, data, size, result)) != DC_STATUS_SUCCESS) {
if (rc != DC_STATUS_PROTOCOL)
return rc;
dc_iostream_purge (device->iostream, DC_DIRECTION_INPUT);
unsigned char value = NAK;
status = dc_iostream_write (device->iostream, &value, 1, NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to reject the packet.");
return status;
}
}
return DC_STATUS_SUCCESS;
}
static dc_status_t
uwatec_memomouse_read_packet_inner (uwatec_memomouse_device_t *device, dc_buffer_t *buffer, dc_event_progress_t *progress)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_device_t *abstract = (dc_device_t *) device;
if (!dc_buffer_clear (buffer)) {
ERROR (abstract->context, "Insufficient buffer space available.");
return DC_STATUS_NOMEMORY;
}
unsigned int nbytes = 0;
unsigned int total = PACKETSIZE;
while (nbytes < total) {
unsigned int length = total - nbytes;
if (length > PACKETSIZE)
length = PACKETSIZE;
unsigned char packet[PACKETSIZE + 2] = {0};
dc_status_t rc = uwatec_memomouse_read_packet_outer (device, packet, length + 2, &length);
if (rc != DC_STATUS_SUCCESS)
return rc;
unsigned char value = ACK;
status = dc_iostream_write (device->iostream, &value, 1, NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to accept the packet.");
return status;
}
if (nbytes == 0) {
if (length < 2) {
ERROR (abstract->context, "Data packet is too short.");
return DC_STATUS_PROTOCOL;
}
total = array_uint16_le (packet + 1) + 3;
if (!dc_buffer_reserve (buffer, total)) {
ERROR (abstract->context, "Insufficient buffer space available.");
return DC_STATUS_NOMEMORY;
}
}
if (progress) {
progress->maximum = total;
progress->current += length;
device_event_emit (&device->base, DC_EVENT_PROGRESS, progress);
}
dc_buffer_append (buffer, packet + 1, length);
nbytes += length;
}
unsigned char *data = dc_buffer_get_data (buffer);
unsigned char crc = data[total - 1];
unsigned char ccrc = checksum_xor_uint8 (data, total - 1, 0x00);
if (crc != ccrc) {
ERROR (abstract->context, "Unexpected answer checksum.");
return DC_STATUS_PROTOCOL;
}
dc_buffer_slice (buffer, 2, total - 3);
return DC_STATUS_SUCCESS;
}
static dc_status_t
uwatec_memomouse_dump_internal (uwatec_memomouse_device_t *device, dc_buffer_t *buffer)
{
dc_status_t status = DC_STATUS_SUCCESS;
dc_device_t *abstract = (dc_device_t *) device;
dc_event_progress_t progress = EVENT_PROGRESS_INITIALIZER;
device_event_emit (&device->base, DC_EVENT_PROGRESS, &progress);
while (dc_iostream_poll (device->iostream, 300) == DC_STATUS_TIMEOUT) {
if (device_is_cancelled (abstract))
return DC_STATUS_CANCELLED;
dc_iostream_purge (device->iostream, DC_DIRECTION_INPUT);
unsigned char value = NAK;
status = dc_iostream_write (device->iostream, &value, 1, NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to reject the packet.");
return status;
}
}
dc_status_t rc = uwatec_memomouse_read_packet_inner (device, buffer, NULL);
if (rc != DC_STATUS_SUCCESS)
return rc;
unsigned char command [9] = {
0x07, 0x05, 0x00, 0x55, (device->timestamp ) & 0xFF,
(device->timestamp >> 8) & 0xFF,
(device->timestamp >> 16) & 0xFF,
(device->timestamp >> 24) & 0xFF,
0x00}; command[8] = checksum_xor_uint8 (command, 8, 0x00);
array_reverse_bits (command, sizeof (command));
dc_iostream_sleep (device->iostream, 50);
unsigned char answer = NAK;
while (answer == NAK) {
dc_iostream_purge (device->iostream, DC_DIRECTION_INPUT);
status = dc_iostream_write (device->iostream, command, sizeof (command), NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to send the command.");
return status;
}
status = dc_iostream_read (device->iostream, &answer, 1, NULL);
if (status != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to receive the answer.");
return status;
}
}
if (answer != ACK) {
ERROR (abstract->context, "Unexpected answer start byte(s).");
return DC_STATUS_PROTOCOL;
}
while (dc_iostream_poll (device->iostream, 100) == DC_STATUS_TIMEOUT) {
if (device_is_cancelled (abstract))
return DC_STATUS_CANCELLED;
device_event_emit (&device->base, DC_EVENT_WAITING, NULL);
}
dc_ticks_t now = dc_datetime_now ();
rc = uwatec_memomouse_read_packet_inner (device, buffer, &progress);
if (rc != DC_STATUS_SUCCESS)
return rc;
device->systime = now;
device->devtime = array_uint32_le (dc_buffer_get_data (buffer) + 1);
dc_event_clock_t clock;
clock.systime = device->systime;
clock.devtime = device->devtime;
device_event_emit ((dc_device_t *) device, DC_EVENT_CLOCK, &clock);
return DC_STATUS_SUCCESS;
}
static dc_status_t
uwatec_memomouse_device_dump (dc_device_t *abstract, dc_buffer_t *buffer)
{
dc_status_t status = DC_STATUS_SUCCESS;
uwatec_memomouse_device_t *device = (uwatec_memomouse_device_t*) abstract;
dc_status_t rc = DC_STATUS_SUCCESS;
dc_iostream_sleep (device->iostream, 500);
rc = dc_iostream_set_dtr (device->iostream, 1);
if (rc != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to set the RTS line.");
return rc;
}
status = uwatec_memomouse_dump_internal (device, buffer);
rc = dc_iostream_set_dtr (device->iostream, 0);
if (rc != DC_STATUS_SUCCESS) {
ERROR (abstract->context, "Failed to set the RTS line.");
return rc;
}
return status;
}
static dc_status_t
uwatec_memomouse_device_foreach (dc_device_t *abstract, dc_dive_callback_t callback, void *userdata)
{
dc_buffer_t *buffer = dc_buffer_new (0);
if (buffer == NULL)
return DC_STATUS_NOMEMORY;
dc_status_t rc = uwatec_memomouse_device_dump (abstract, buffer);
if (rc != DC_STATUS_SUCCESS) {
dc_buffer_free (buffer);
return rc;
}
rc = uwatec_memomouse_extract_dives (abstract,
dc_buffer_get_data (buffer), dc_buffer_get_size (buffer), callback, userdata);
dc_buffer_free (buffer);
return rc;
}
static dc_status_t
uwatec_memomouse_extract_dives (dc_device_t *abstract, const unsigned char data[], unsigned int size, dc_dive_callback_t callback, void *userdata)
{
if (abstract && !ISINSTANCE (abstract))
return DC_STATUS_INVALIDARGS;
unsigned int ndives = 0;
unsigned int previous = 0;
unsigned int current = 5;
while (current + 18 <= size) {
if (previous && memcmp (data + previous, data + current, 18) == 0)
break;
unsigned int len = array_uint16_le (data + current + 16);
if (current + len + 18 > size)
return DC_STATUS_DATAFORMAT;
if (abstract && ndives == 0) {
dc_event_devinfo_t devinfo;
devinfo.model = data[current + 3];
devinfo.firmware = 0;
devinfo.serial = array_uint24_be (data + current);
device_event_emit (abstract, DC_EVENT_DEVINFO, &devinfo);
}
previous = current;
current += len + 18;
ndives++;
}
for (unsigned int i = 0; i < ndives; ++i) {
unsigned int offset = 5;
unsigned int skip = ndives - i - 1;
while (skip) {
unsigned int len = array_uint16_le (data + offset + 16);
offset += len + 18;
skip--;
}
unsigned int length = array_uint16_le (data + offset + 16);
if (callback && !callback (data + offset, length + 18, data + offset + 11, 4, userdata))
return DC_STATUS_SUCCESS;
}
return DC_STATUS_SUCCESS;
}