a serial library to communicate with pebble time's smart strap interface
Dependents: xadow_smartstrap_for_pebble
PebbleSerial.c
- Committer:
- KillingJacky
- Date:
- 2015-11-04
- Revision:
- 0:e4dad9e53f06
File content as of revision 0:e4dad9e53f06:
/* This library is for communicating with a Pebble Time via the accessory port for Smart Straps. */ #include "PebbleSerial.h" #include "crc.h" #include "encoding.h" #define PROTOCOL_VERSION 1 #define GENERIC_SERVICE_VERSION 1 #define FRAME_MIN_LENGTH 8 #define FRAME_VERSION_OFFSET 0 #define FRAME_FLAGS_OFFSET 1 #define FRAME_PROFILE_OFFSET 5 #define FRAME_PAYLOAD_OFFSET 7 #define FLAGS_IS_READ_OFFSET 0 #define FLAGS_IS_MASTER_OFFSET 1 #define FLAGS_IS_NOTIFICATION_OFFSET 2 #define FLAGS_RESERVED_OFFSET 3 #define FLAGS_IS_READ_MASK (0x1 << FLAGS_IS_READ_OFFSET) #define FLAGS_IS_MASTER_MASK (0x1 << FLAGS_IS_MASTER_OFFSET) #define FLAGS_IS_NOTIFICATION_MASK (0x1 << FLAGS_IS_NOTIFICATION_OFFSET) #define FLAGS_RESERVED_MASK (~(FLAGS_IS_READ_MASK | \ FLAGS_IS_MASTER_MASK | \ FLAGS_IS_NOTIFICATION_MASK)) #define FLAGS_GET(flags, mask, offset) (((flags) & mask) >> offset) #define FLAGS_SET(flags, mask, offset, value) \ (flags) = ((flags) & ~mask) | (((value) << offset) & mask) typedef enum { SmartstrapProfileInvalid = 0x00, SmartstrapProfileLinkControl = 0x01, SmartstrapProfileRawData = 0x02, SmartstrapProfileGenericService = 0x03, NumSmartstrapProfiles } SmartstrapProfile; typedef enum { LinkControlTypeInvalid = 0, LinkControlTypeStatus = 1, LinkControlTypeProfiles = 2, LinkControlTypeBaud = 3, NumLinkControlTypes } LinkControlType; typedef enum { LinkControlStatusOk = 0, LinkControlStatusBaudRate = 1, LinkControlStatusDisconnect = 2 } LinkControlStatus; typedef struct { uint8_t version; uint32_t flags; uint16_t profile; } FrameHeader; typedef struct { FrameHeader header; uint8_t *payload; uint8_t checksum; size_t length; size_t max_payload_length; uint8_t footer_byte; bool should_drop; bool read_ready; bool is_read; EncodingStreamingContext encoding_ctx; } PebbleFrameInfo; typedef struct __attribute__((packed)) { uint8_t version; uint16_t service_id; uint16_t attribute_id; uint8_t type; uint8_t error; uint16_t length; uint8_t data[]; } GenericServicePayload; static SmartstrapRequestType s_last_generic_service_type; static uint32_t s_last_message_time = 0; static PebbleFrameInfo s_frame; static SmartstrapCallback s_callback; static bool s_connected; static PebbleBaud s_current_baud = PebbleBaudInvalid; static PebbleBaud s_target_baud = PebbleBaudInvalid; static const uint32_t BAUDS[] = { 9600, 14400, 19200, 28800, 38400, 57600, 67500, 115200, 125000, 230400, 250000, 460800 }; static uint16_t s_notify_service; static uint16_t s_notify_attribute; static const uint16_t *s_supported_services; static uint8_t s_num_supported_services; static struct { bool can_respond; uint16_t service_id; uint16_t attribute_id; } s_pending_response; void prv_set_baud(PebbleBaud baud) { if (baud == s_current_baud) { return; } s_current_baud = baud; s_callback(SmartstrapCmdSetBaudRate, BAUDS[baud]); s_callback(SmartstrapCmdSetTxEnabled, true); s_callback(SmartstrapCmdSetTxEnabled, false); } void pebble_init(SmartstrapCallback callback, PebbleBaud baud, const uint16_t *services, uint8_t num_services) { s_callback = callback; s_target_baud = baud; s_supported_services = services; s_num_supported_services = num_services; prv_set_baud(PebbleBaud9600); } void pebble_prepare_for_read(uint8_t *buffer, size_t length) { s_frame = (PebbleFrameInfo) { .payload = buffer, .max_payload_length = length, .read_ready = true }; } static void prv_send_flag(void) { s_callback(SmartstrapCmdWriteByte, ENCODING_FLAG); } static void prv_send_byte(uint8_t data, uint8_t *parity) { crc8_calculate_byte_streaming(data, parity); if (encoding_encode(&data)) { s_callback(SmartstrapCmdWriteByte, ENCODING_ESCAPE); } s_callback(SmartstrapCmdWriteByte, data); } static void prv_write_internal(SmartstrapProfile profile, const uint8_t *data1, size_t length1, const uint8_t *data2, size_t length2, bool is_notify) { uint8_t parity = 0; // enable tx s_callback(SmartstrapCmdSetTxEnabled, true); // send flag prv_send_flag(); // send version prv_send_byte(PROTOCOL_VERSION, &parity); // send header flags (currently just hard-coded) if (is_notify) { prv_send_byte(0x04, &parity); } else { prv_send_byte(0, &parity); } prv_send_byte(0, &parity); prv_send_byte(0, &parity); prv_send_byte(0, &parity); // send profile (currently well within a single byte) prv_send_byte(profile, &parity); prv_send_byte(0, &parity); // send data size_t i; for (i = 0; i < length1; ++i) { prv_send_byte(data1[i], &parity); } for (i = 0; i < length2; ++i) { prv_send_byte(data2[i], &parity); } // send parity prv_send_byte(parity, &parity); // send flag prv_send_flag(); // flush and disable tx s_callback(SmartstrapCmdSetTxEnabled, false); } static bool prv_supports_raw_data_profile(void) { uint8_t i; for (i = 0; i < s_num_supported_services; i++) { if (s_supported_services[i] == 0x0000) { return true; } } return false; } static bool prv_supports_generic_profile(void) { uint8_t i; for (i = 0; i < s_num_supported_services; i++) { if (s_supported_services[i] > 0x0000) { return true; } } return false; } static void prv_handle_link_control(uint8_t *buffer) { // we will re-use the buffer for the response LinkControlType type = buffer[1]; if (type == LinkControlTypeStatus) { if (s_current_baud != s_target_baud) { buffer[2] = LinkControlStatusBaudRate; } else { buffer[2] = LinkControlStatusOk; s_connected = true; } prv_write_internal(SmartstrapProfileLinkControl, buffer, 3, NULL, 0, false); } else if (type == LinkControlTypeProfiles) { uint16_t profiles[2]; uint8_t num_profiles = 0; if (prv_supports_raw_data_profile()) { profiles[num_profiles++] = SmartstrapProfileRawData; } if (prv_supports_generic_profile()) { profiles[num_profiles++] = SmartstrapProfileGenericService; } prv_write_internal(SmartstrapProfileLinkControl, buffer, 2, (uint8_t *)profiles, num_profiles * sizeof(uint16_t), false); } else if (type == LinkControlTypeBaud) { buffer[2] = 0x05; prv_write_internal(SmartstrapProfileLinkControl, buffer, 3, NULL, 0, false); prv_set_baud(s_target_baud); } } static bool prv_handle_generic_service(GenericServicePayload *data) { if (data->error != 0) { return true; } uint16_t service_id = data->service_id; uint16_t attribute_id = data->attribute_id; s_last_generic_service_type = data->type; uint16_t length = data->length; if ((service_id == 0x0101) && (attribute_id == 0x0002)) { // notification info attribute if (s_notify_service) { uint16_t info[2] = {s_notify_service, s_notify_attribute}; length = sizeof(info); s_pending_response.can_respond = true; s_pending_response.service_id = service_id; s_pending_response.attribute_id = attribute_id; pebble_write(true, (uint8_t *)&info, length); } return true; } else if ((service_id == 0x0101) && (attribute_id == 0x0001)) { // this is a service discovery frame s_pending_response.can_respond = true; s_pending_response.service_id = service_id; s_pending_response.attribute_id = attribute_id; pebble_write(true, (uint8_t *)s_supported_services, s_num_supported_services * sizeof(uint16_t)); return true; } return false; } static void prv_store_byte(const uint8_t data) { // Find which field this byte belongs to based on the number of bytes we've received so far if (s_frame.length >= FRAME_PAYLOAD_OFFSET) { // This byte is part of either the payload or the checksum const uint32_t payload_length = s_frame.length - FRAME_PAYLOAD_OFFSET; if (payload_length > s_frame.max_payload_length) { // The payload is longer than the payload buffer so drop this byte s_frame.should_drop = true; } else { // The checksum byte comes after the payload in the frame. This byte we are receiving could // be the checksum byte, or it could be part of the payload; we don't know at this point. So, // we will store it temporarily in footer_byte and if we've previously stored a byte there, // will copy that previous byte into the payload. This avoids us overrunning the payload // buffer if it's conservatively sized. if (payload_length > 0) { // put the previous byte into the payload buffer s_frame.payload[payload_length - 1] = s_frame.footer_byte; } s_frame.footer_byte = data; } } else if (s_frame.length >= FRAME_PROFILE_OFFSET) { // This byte is part of the profile field const uint32_t byte_offset = s_frame.length - FRAME_PROFILE_OFFSET; s_frame.header.profile |= (data << (byte_offset * 8)); } else if (s_frame.length >= FRAME_FLAGS_OFFSET) { // This byte is part of the flags field const uint32_t byte_offset = s_frame.length - FRAME_FLAGS_OFFSET; s_frame.header.flags |= (data << (byte_offset * 8)); } else { // The version field should always be first (and a single byte) s_frame.header.version = data; } // increment the length run the CRC calculation s_frame.length++; crc8_calculate_byte_streaming(data, &s_frame.checksum); } static void prv_frame_validate(void) { if ((s_frame.should_drop == false) && (s_frame.header.version > 0) && (s_frame.header.version <= PROTOCOL_VERSION) && (FLAGS_GET(s_frame.header.flags, FLAGS_IS_MASTER_MASK, FLAGS_IS_MASTER_OFFSET) == 1) && (FLAGS_GET(s_frame.header.flags, FLAGS_RESERVED_MASK, FLAGS_RESERVED_OFFSET) == 0) && (s_frame.header.profile > SmartstrapProfileInvalid) && (s_frame.header.profile < NumSmartstrapProfiles) && (s_frame.length >= FRAME_MIN_LENGTH) && (s_frame.checksum == 0)) { // this is a valid frame } else { // drop the frame s_frame.should_drop = true; } } bool pebble_handle_byte(uint8_t data, uint16_t *service_id, uint16_t *attribute_id, size_t *length, SmartstrapRequestType *type, uint32_t time) { if (!s_frame.read_ready) { // we shouldn't be reading new data return false; } bool encoding_err, should_store = false; bool is_complete = encoding_streaming_decode(&s_frame.encoding_ctx, &data, &should_store, &encoding_err); if (encoding_err) { s_frame.should_drop = true; } else if (is_complete) { prv_frame_validate(); } else if (should_store) { prv_store_byte(data); } if (s_frame.should_drop || is_complete) { // prepare the encoding context for the next frame encoding_streaming_decode_reset(&s_frame.encoding_ctx); } if (is_complete) { bool give_to_user = false; if (s_frame.should_drop) { // reset the frame pebble_prepare_for_read(s_frame.payload, s_frame.max_payload_length); } else if (s_frame.header.profile == SmartstrapProfileLinkControl) { s_last_message_time = time; // handle this link control frame prv_handle_link_control(s_frame.payload); // prepare for the next frame pebble_prepare_for_read(s_frame.payload, s_frame.max_payload_length); } else if (s_frame.header.profile == SmartstrapProfileGenericService) { GenericServicePayload header = *(GenericServicePayload *)s_frame.payload; memmove(s_frame.payload, &s_frame.payload[sizeof(header)], header.length); // handle this generic service frame if (prv_handle_generic_service(&header)) { s_last_message_time = time; // we handled it, so prepare for the next frame pebble_prepare_for_read(s_frame.payload, s_frame.max_payload_length); } else { // pass up to user to handle give_to_user = true; *service_id = header.service_id; *attribute_id = header.attribute_id; *length = header.length; *type = header.type; } } else { give_to_user = true; *service_id = 0; *attribute_id = 0; *length = s_frame.length - FRAME_MIN_LENGTH; if (FLAGS_GET(s_frame.header.flags, FLAGS_IS_READ_MASK, FLAGS_IS_READ_OFFSET)) { if (*length) { *type = SmartstrapRequestTypeWriteRead; } else { *type = SmartstrapRequestTypeRead; } } else { *type = SmartstrapRequestTypeWrite; } } if (give_to_user) { s_last_message_time = time; s_frame.read_ready = false; s_pending_response.service_id = *service_id; s_pending_response.attribute_id = *attribute_id; s_pending_response.can_respond = true; return true; } } if (time < s_last_message_time) { // wrapped around s_last_message_time = time; } else if (time - s_last_message_time > 10000) { // haven't received a valid frame in over 10 seconds so reset the baudrate prv_set_baud(PebbleBaud9600); s_connected = false; } return false; } bool pebble_write(bool success, const uint8_t *buffer, uint16_t length) { if (!s_pending_response.can_respond) { return false; } if (s_pending_response.service_id == 0) { if (s_pending_response.attribute_id != 0) { return false; } prv_write_internal(SmartstrapProfileRawData, buffer, length, NULL, 0, false); } else if (s_pending_response.service_id < 0x00FF) { return false; } else { GenericServicePayload frame = (GenericServicePayload ) { .version = GENERIC_SERVICE_VERSION, .service_id = s_pending_response.service_id, .attribute_id = s_pending_response.attribute_id, .type = s_last_generic_service_type, .error = success ? 0 : 1, .length = length }; prv_write_internal(SmartstrapProfileGenericService, (uint8_t *)&frame, sizeof(frame), buffer, length, false); } s_pending_response.can_respond = false; return true; } void pebble_notify(uint16_t service_id, uint16_t attribute_id) { s_notify_service = service_id; s_notify_attribute = attribute_id; SmartstrapProfile profile; if (service_id == 0) { profile = SmartstrapProfileRawData; } else { profile = SmartstrapProfileGenericService; } s_callback(SmartstrapCmdSetTxEnabled, true); s_callback(SmartstrapCmdWriteBreak, 0); s_callback(SmartstrapCmdWriteBreak, 0); s_callback(SmartstrapCmdWriteBreak, 0); s_callback(SmartstrapCmdSetTxEnabled, false); prv_write_internal(profile, NULL, 0, NULL, 0, true); } bool pebble_is_connected(uint32_t time) { if (time - s_last_message_time > 10000) { prv_set_baud(PebbleBaud9600); s_connected = false; } return s_connected; }