├── .gitignore ├── ArduinoPebbleSerial.cpp ├── ArduinoPebbleSerial.h ├── README.md ├── examples ├── Demo1 │ ├── PebbleApp │ │ ├── .gitignore │ │ ├── appinfo.json │ │ ├── src │ │ │ └── main.c │ │ └── wscript │ └── TeensyDemo │ │ └── TeensyDemo.ino └── Demo2 │ ├── PebbleApp │ ├── appinfo.json │ ├── src │ │ └── main.c │ └── wscript │ └── TeensyDemo │ └── TeensyDemo.ino ├── keywords.txt └── utility ├── LICENSE ├── OneWireSoftSerial.cpp ├── OneWireSoftSerial.h ├── PebbleSerial.c ├── PebbleSerial.h ├── board.h ├── crc.c ├── crc.h ├── encoding.c └── encoding.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Vim files 2 | *.swp 3 | *.swo 4 | 5 | # Ignore build generated files 6 | examples/Demo1/PebbleApp/build 7 | examples/Demo2/PebbleApp/build 8 | 9 | # Ignore Screenshots 10 | pebble-screenshot_* 11 | 12 | # Ignore lock files 13 | .lock-* 14 | -------------------------------------------------------------------------------- /ArduinoPebbleSerial.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This is an Arduino library wrapper around the PebbleSerial library. 3 | */ 4 | 5 | #include "ArduinoPebbleSerial.h" 6 | #include "utility/board.h" 7 | 8 | static bool s_is_hardware; 9 | static uint8_t *s_buffer; 10 | static size_t s_buffer_length; 11 | static uint8_t s_pin; 12 | 13 | static void prv_cmd_cb(SmartstrapCmd cmd, uint32_t arg) { 14 | switch (cmd) { 15 | case SmartstrapCmdSetBaudRate: 16 | if (s_is_hardware) { 17 | if (arg == 57600) { 18 | // The Arduino library intentionally uses bad prescalers for a baud rate of exactly 57600 so 19 | // we just increase it by 1 to prevent it from doing that. 20 | arg++; 21 | } 22 | BOARD_SERIAL.begin(arg); 23 | board_begin(); 24 | } else { 25 | OneWireSoftSerial::begin(s_pin, arg); 26 | } 27 | break; 28 | case SmartstrapCmdSetTxEnabled: 29 | if (s_is_hardware) { 30 | if (!arg) { 31 | BOARD_SERIAL.flush(); 32 | } 33 | board_set_tx_enabled(arg); 34 | } else { 35 | OneWireSoftSerial::set_tx_enabled(arg); 36 | } 37 | break; 38 | case SmartstrapCmdWriteByte: 39 | if (s_is_hardware) { 40 | BOARD_SERIAL.write((uint8_t)arg); 41 | } else { 42 | OneWireSoftSerial::write((uint8_t)arg); 43 | } 44 | break; 45 | case SmartstrapCmdWriteBreak: 46 | if (s_is_hardware) { 47 | board_set_even_parity(true); 48 | BOARD_SERIAL.write((uint8_t)0); 49 | // need to flush before changing parity 50 | BOARD_SERIAL.flush(); 51 | board_set_even_parity(false); 52 | } else { 53 | OneWireSoftSerial::write(0, true /* is_break */); 54 | } 55 | break; 56 | default: 57 | break; 58 | } 59 | } 60 | 61 | static void prv_begin(uint8_t *buffer, size_t length, Baud baud, 62 | const uint16_t *services, uint8_t num_services) { 63 | s_buffer = buffer; 64 | s_buffer_length = length; 65 | 66 | pebble_init(prv_cmd_cb, (PebbleBaud)baud, services, num_services); 67 | pebble_prepare_for_read(s_buffer, s_buffer_length); 68 | } 69 | 70 | void ArduinoPebbleSerial::begin_software(uint8_t pin, uint8_t *buffer, size_t length, Baud baud, 71 | const uint16_t *services, uint8_t num_services) { 72 | s_is_hardware = false; 73 | s_pin = pin; 74 | prv_begin(buffer, length, baud, services, num_services); 75 | } 76 | 77 | void ArduinoPebbleSerial::begin_hardware(uint8_t *buffer, size_t length, Baud baud, 78 | const uint16_t *services, uint8_t num_services) { 79 | s_is_hardware = true; 80 | prv_begin(buffer, length, baud, services, num_services); 81 | } 82 | 83 | static int prv_available_bytes(void) { 84 | if (s_is_hardware) { 85 | return BOARD_SERIAL.available(); 86 | } else { 87 | return OneWireSoftSerial::available(); 88 | } 89 | } 90 | 91 | static uint8_t prv_read_byte(void) { 92 | if (s_is_hardware) { 93 | return (uint8_t)BOARD_SERIAL.read(); 94 | } else { 95 | return (uint8_t)OneWireSoftSerial::read(); 96 | } 97 | } 98 | 99 | bool ArduinoPebbleSerial::feed(uint16_t *service_id, uint16_t *attribute_id, size_t *length, 100 | RequestType *type) { 101 | SmartstrapRequestType request_type; 102 | bool did_feed = false; 103 | while (prv_available_bytes()) { 104 | did_feed = true; 105 | if (pebble_handle_byte(prv_read_byte(), service_id, attribute_id, length, &request_type, 106 | millis())) { 107 | // we have a full frame 108 | pebble_prepare_for_read(s_buffer, s_buffer_length); 109 | switch (request_type) { 110 | case SmartstrapRequestTypeRead: 111 | *type = RequestTypeRead; 112 | break; 113 | case SmartstrapRequestTypeWrite: 114 | *type = RequestTypeWrite; 115 | break; 116 | case SmartstrapRequestTypeWriteRead: 117 | *type = RequestTypeWriteRead; 118 | break; 119 | default: 120 | break; 121 | } 122 | return true; 123 | } 124 | } 125 | 126 | if (!did_feed) { 127 | // allow the pebble code to dicsonnect if we haven't gotten any messages recently 128 | pebble_is_connected(millis()); 129 | } 130 | return false; 131 | } 132 | 133 | bool ArduinoPebbleSerial::write(bool success, const uint8_t *payload, size_t length) { 134 | return pebble_write(success, payload, length); 135 | } 136 | 137 | void ArduinoPebbleSerial::notify(uint16_t service_id, uint16_t attribute_id) { 138 | pebble_notify(service_id, attribute_id); 139 | } 140 | 141 | bool ArduinoPebbleSerial::is_connected(void) { 142 | return pebble_is_connected(millis()); 143 | } 144 | -------------------------------------------------------------------------------- /ArduinoPebbleSerial.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This is an Arduino library wrapper around the PebbleSerial library. 3 | */ 4 | #ifndef __ARDUINO_PEBBLE_SERIAL_H__ 5 | #define __ARDUINO_PEBBLE_SERIAL_H__ 6 | 7 | #define USE_HARDWARE_SERIAL 0 8 | 9 | #include 10 | #include "utility/OneWireSoftSerial.h" 11 | extern "C" { 12 | #include "utility/PebbleSerial.h" 13 | }; 14 | 15 | typedef enum { 16 | Baud9600, 17 | Baud14400, 18 | Baud19200, 19 | Baud28800, 20 | Baud38400, 21 | Baud57600, 22 | Baud62500, 23 | Baud115200, 24 | Baud125000, 25 | Baud230400, 26 | Baud250000, 27 | Baud460800, 28 | } Baud; 29 | 30 | typedef enum { 31 | RequestTypeRead, 32 | RequestTypeWrite, 33 | RequestTypeWriteRead 34 | } RequestType; 35 | 36 | class ArduinoPebbleSerial { 37 | public: 38 | static void begin_software(uint8_t pin, uint8_t *buffer, size_t length, Baud baud, 39 | const uint16_t *services, uint8_t num_services); 40 | static void begin_hardware(uint8_t *buffer, size_t length, Baud baud, const uint16_t *services, 41 | uint8_t num_services); 42 | static bool feed(uint16_t *service_id, uint16_t *attribute_id, size_t *length, RequestType *type); 43 | static bool write(bool success, const uint8_t *payload, size_t length); 44 | static void notify(uint16_t service_id, uint16_t attribute_id); 45 | static bool is_connected(void); 46 | }; 47 | 48 | #endif //__ARDUINO_PEBBLE_SERIAL_H__ 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ArduinoPebbleSerial 2 | 3 | This is an Arduino library for communicating with Pebble Time via the Smartstrap port. For more 4 | information, see the 5 | [Smartstrap guide on the Pebble developer site](https://developer.getpebble.com/guides/hardware/) 6 | 7 | ## Installation ## 8 | 9 | Below are instructions for installing this library with Arduino 1.6.x: 10 | 11 | 1. Download this repository as a .zip by clicking on the "Download ZIP" button on GitHub. 12 | 2. In Arduino, create a new project and go to "Sketch" -> "Include Library" -> "Add .ZIP Library..." 13 | 3. Select the .zip you just downloaded. 14 | 4. Arduino will automatically import the library and place the proper #include at the top. 15 | 5. In the future, you can skip step 1 and simply select the library within the "Sketch" -> 16 | "Include Library" list for step 2. 17 | 18 | ## Hardware Configuration 19 | 20 | This Arduino library has both a software serial and hardware serial mode. The hardware serial mode 21 | requires an external open-drain buffer as detailed in the smartstrap guide linked above, and also 22 | requires board-specific support (see utility/board.h). The software serial mode requires only 23 | a pull-up resistor and supports any AVR-based microcontroller. 24 | 25 | ## Tested Boards ## 26 | 27 | | Board Name | Tested in Software Mode | Tested in Hardware Mode | 28 | | --------------- | ----------------------- | --------------------------------------------- | 29 | | Teensy 2.0 | yes | yes (RX/TX pins shorted together) | 30 | | Teensy 3.0 | no | no, but supported | 31 | | Teensy 3.1 | no | yes (pins 0[RX1] and 1[TX1] shorted together) | 32 | | Arduino Uno | yes | no, but supported | 33 | 34 | ## Examples ## 35 | 36 | There are two demos in the examples folder. Each demo consists of an Arduino project to be run on a 37 | Teensy 2.0/3.x board (Demo2 only supports Teensy 2.0) and a Pebble app to be run on the watch. 38 | -------------------------------------------------------------------------------- /examples/Demo1/PebbleApp/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Ignore build generated files 3 | build 4 | 5 | # Ignore waf lock file 6 | .lock-waf* 7 | -------------------------------------------------------------------------------- /examples/Demo1/PebbleApp/appinfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "019798b0-f1f7-4e43-b4c3-892391790f52", 3 | "shortName": "Smartstrap Demo", 4 | "longName": "Smartstrap Demo", 5 | "companyName": "Pebble", 6 | "versionCode": 1, 7 | "versionLabel": "1.0", 8 | "sdkVersion": "3", 9 | "targetPlatforms": ["basalt"], 10 | "watchapp": { 11 | "watchface": false 12 | }, 13 | "resources": { 14 | "media": [] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/Demo1/PebbleApp/src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #define TIMEOUT_MS 1000 4 | #define MAX_READ_SIZE 100 5 | 6 | static Window *s_main_window; 7 | static TextLayer *s_status_layer; 8 | static TextLayer *s_attr_text_layer; 9 | static TextLayer *s_raw_text_layer; 10 | static char s_text_buffer1[20]; 11 | static char s_text_buffer2[20]; 12 | static SmartstrapAttribute *s_raw_attribute; 13 | static SmartstrapAttribute *s_attr_attribute; 14 | 15 | static void prv_update_text(void) { 16 | if (smartstrap_service_is_available(SMARTSTRAP_RAW_DATA_SERVICE_ID)) { 17 | text_layer_set_text(s_status_layer, "Connected!"); 18 | } else { 19 | text_layer_set_text(s_status_layer, "Connecting..."); 20 | } 21 | } 22 | 23 | static void prv_did_read(SmartstrapAttribute *attr, SmartstrapResult result, 24 | const uint8_t *data, size_t length) { 25 | if (attr == s_attr_attribute) { 26 | APP_LOG(APP_LOG_LEVEL_DEBUG, "did_read(s_attr_attribute, %d, %d)", result, length); 27 | if (result == SmartstrapResultOk && length == 4) { 28 | uint32_t num; 29 | memcpy(&num, data, 4); 30 | snprintf(s_text_buffer1, 20, "%u", (unsigned int)num); 31 | text_layer_set_text(s_attr_text_layer, s_text_buffer1); 32 | } 33 | } else if (attr == s_raw_attribute) { 34 | APP_LOG(APP_LOG_LEVEL_DEBUG, "did_read(s_raw_attribute, %d, %d)", result, length); 35 | if (result == SmartstrapResultOk && length == 4) { 36 | uint32_t time; 37 | memcpy(&time, data, 4); 38 | snprintf(s_text_buffer2, 20, "%u", (unsigned int)time); 39 | text_layer_set_text(s_raw_text_layer, s_text_buffer2); 40 | } 41 | } else { 42 | APP_LOG(APP_LOG_LEVEL_ERROR, "did_read(<%p>, %d)", attr, result); 43 | } 44 | } 45 | 46 | static void prv_did_write(SmartstrapAttribute *attr, SmartstrapResult result) { 47 | if (attr == s_attr_attribute) { 48 | APP_LOG(APP_LOG_LEVEL_DEBUG, "did_write(s_attr_attribute, %d)", result); 49 | } else if (attr == s_raw_attribute) { 50 | APP_LOG(APP_LOG_LEVEL_DEBUG, "did_write(s_raw_attribute, %d)", result); 51 | } else { 52 | APP_LOG(APP_LOG_LEVEL_ERROR, "did_write(<%p>, %d)", attr, result); 53 | } 54 | } 55 | 56 | static void prv_write_read_test_attr(void) { 57 | SmartstrapResult result; 58 | if (!smartstrap_service_is_available(smartstrap_attribute_get_service_id(s_attr_attribute))) { 59 | APP_LOG(APP_LOG_LEVEL_DEBUG, "s_attr_attribute is not available"); 60 | return; 61 | } 62 | 63 | // get the write buffer 64 | uint8_t *buffer = NULL; 65 | size_t length = 0; 66 | result = smartstrap_attribute_begin_write(s_attr_attribute, &buffer, &length); 67 | if (result != SmartstrapResultOk) { 68 | APP_LOG(APP_LOG_LEVEL_ERROR, "Write of s_attr_attribute failed with result %d", result); 69 | return; 70 | } 71 | 72 | // write the data into the buffer 73 | uint32_t num = rand() % 10000; 74 | memcpy(buffer, &num, 4); 75 | 76 | // send it off 77 | result = smartstrap_attribute_end_write(s_attr_attribute, sizeof(num), true); 78 | if (result != SmartstrapResultOk) { 79 | APP_LOG(APP_LOG_LEVEL_ERROR, "Write of s_attr_attribute failed with result %d", result); 80 | } 81 | } 82 | 83 | static void prv_read_raw(void) { 84 | if (!smartstrap_service_is_available(smartstrap_attribute_get_service_id(s_raw_attribute))) { 85 | APP_LOG(APP_LOG_LEVEL_DEBUG, "s_raw_attribute is not available"); 86 | return; 87 | } 88 | SmartstrapResult result = smartstrap_attribute_read(s_raw_attribute); 89 | if (result != SmartstrapResultOk) { 90 | APP_LOG(APP_LOG_LEVEL_ERROR, "Read of s_raw_attribute failed with result: %d", result); 91 | } 92 | } 93 | 94 | static void prv_send_request(void *context) { 95 | prv_write_read_test_attr(); 96 | prv_read_raw(); 97 | app_timer_register(900, prv_send_request, NULL); 98 | } 99 | 100 | static void prv_availablility_status_changed(SmartstrapServiceId service_id, bool is_available) { 101 | APP_LOG(APP_LOG_LEVEL_DEBUG, "Availability for 0x%x is %d", service_id, is_available); 102 | prv_update_text(); 103 | } 104 | 105 | static void prv_notified(SmartstrapAttribute *attr) { 106 | if (attr == s_attr_attribute) { 107 | APP_LOG(APP_LOG_LEVEL_DEBUG, "notified(s_attr_attribute)"); 108 | } else if (attr == s_raw_attribute) { 109 | APP_LOG(APP_LOG_LEVEL_DEBUG, "notified(s_raw_attribute)"); 110 | } else { 111 | APP_LOG(APP_LOG_LEVEL_ERROR, "notified(<%p>)", attr); 112 | } 113 | } 114 | 115 | static void prv_main_window_load(Window *window) { 116 | s_status_layer = text_layer_create(GRect(0, 15, 144, 40)); 117 | text_layer_set_font(s_status_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28)); 118 | prv_update_text(); 119 | text_layer_set_text_color(s_status_layer, GColorBlack); 120 | text_layer_set_background_color(s_status_layer, GColorClear); 121 | text_layer_set_text_alignment(s_status_layer, GTextAlignmentCenter); 122 | text_layer_set_overflow_mode(s_status_layer, GTextOverflowModeWordWrap); 123 | layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_status_layer)); 124 | 125 | s_attr_text_layer = text_layer_create(GRect(0, 60, 144, 40)); 126 | text_layer_set_font(s_attr_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28)); 127 | text_layer_set_text(s_attr_text_layer, "-"); 128 | text_layer_set_text_color(s_attr_text_layer, GColorBlack); 129 | text_layer_set_background_color(s_attr_text_layer, GColorClear); 130 | text_layer_set_text_alignment(s_attr_text_layer, GTextAlignmentCenter); 131 | text_layer_set_overflow_mode(s_attr_text_layer, GTextOverflowModeWordWrap); 132 | layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_attr_text_layer)); 133 | 134 | s_raw_text_layer = text_layer_create(GRect(0, 100, 144, 40)); 135 | text_layer_set_font(s_raw_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28)); 136 | text_layer_set_text(s_raw_text_layer, "-"); 137 | text_layer_set_text_color(s_raw_text_layer, GColorBlack); 138 | text_layer_set_background_color(s_raw_text_layer, GColorClear); 139 | text_layer_set_text_alignment(s_raw_text_layer, GTextAlignmentCenter); 140 | text_layer_set_overflow_mode(s_raw_text_layer, GTextOverflowModeWordWrap); 141 | layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_raw_text_layer)); 142 | } 143 | 144 | static void prv_main_window_unload(Window *window) { 145 | text_layer_destroy(s_status_layer); 146 | } 147 | 148 | static void prv_init(void) { 149 | s_main_window = window_create(); 150 | 151 | window_set_window_handlers(s_main_window, (WindowHandlers) { 152 | .load = prv_main_window_load, 153 | .unload = prv_main_window_unload 154 | }); 155 | window_stack_push(s_main_window, true); 156 | SmartstrapHandlers handlers = (SmartstrapHandlers) { 157 | .availability_did_change = prv_availablility_status_changed, 158 | .did_write = prv_did_write, 159 | .did_read = prv_did_read, 160 | .notified = prv_notified 161 | }; 162 | smartstrap_subscribe(handlers); 163 | smartstrap_set_timeout(50); 164 | s_raw_attribute = smartstrap_attribute_create(0, 0, 2000); 165 | s_attr_attribute = smartstrap_attribute_create(0x1001, 0x1001, 20); 166 | app_timer_register(1000, prv_send_request, NULL); 167 | } 168 | 169 | static void prv_deinit(void) { 170 | window_destroy(s_main_window); 171 | smartstrap_unsubscribe(); 172 | } 173 | 174 | int main(void) { 175 | prv_init(); 176 | APP_LOG(APP_LOG_LEVEL_DEBUG, "STARTING APP"); 177 | if (s_attr_attribute && s_raw_attribute) { 178 | app_event_loop(); 179 | } 180 | APP_LOG(APP_LOG_LEVEL_DEBUG, "ENDING APP"); 181 | prv_deinit(); 182 | } 183 | 184 | -------------------------------------------------------------------------------- /examples/Demo1/PebbleApp/wscript: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # This file is the default set of rules to compile a Pebble project. 4 | # 5 | # Feel free to customize this to your needs. 6 | # 7 | 8 | import os.path 9 | 10 | top = '.' 11 | out = 'build' 12 | 13 | def options(ctx): 14 | ctx.load('pebble_sdk') 15 | 16 | def configure(ctx): 17 | ctx.load('pebble_sdk') 18 | 19 | def build(ctx): 20 | ctx.load('pebble_sdk') 21 | 22 | build_worker = os.path.exists('worker_src') 23 | binaries = [] 24 | 25 | for p in ctx.env.TARGET_PLATFORMS: 26 | ctx.set_env(ctx.all_envs[p]) 27 | ctx.set_group(ctx.env.PLATFORM_NAME) 28 | app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR) 29 | ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'), 30 | target=app_elf) 31 | 32 | if build_worker: 33 | worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR) 34 | binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf}) 35 | ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'), 36 | target=worker_elf) 37 | else: 38 | binaries.append({'platform': p, 'app_elf': app_elf}) 39 | 40 | ctx.set_group('bundle') 41 | ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js')) 42 | -------------------------------------------------------------------------------- /examples/Demo1/TeensyDemo/TeensyDemo.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static const uint16_t SUPPORTED_SERVICES[] = {0x0000, 0x1001}; 4 | static const uint8_t NUM_SERVICES = 2; 5 | static uint8_t pebble_buffer[GET_PAYLOAD_BUFFER_SIZE(200)]; 6 | 7 | void setup() { 8 | // General init 9 | Serial.begin(115200); 10 | pinMode(LED_BUILTIN, OUTPUT); 11 | digitalWrite(LED_BUILTIN, LOW); 12 | 13 | #if defined(__MK20DX256__) || defined(__MK20DX128__) 14 | // Teensy 3.0/3.1 uses hardware serial mode (pins 0/1) with RX/TX shorted together 15 | ArduinoPebbleSerial::begin_hardware(pebble_buffer, sizeof(pebble_buffer), Baud57600, 16 | SUPPORTED_SERVICES, NUM_SERVICES); 17 | #elif defined(__AVR_ATmega32U4__) 18 | // Teensy 2.0 uses the one-wire software serial mode (pin 2); 19 | const uint8_t PEBBLE_PIN = 1; 20 | STATIC_ASSERT_VALID_ONE_WIRE_SOFT_SERIAL_PIN(PEBBLE_PIN); 21 | ArduinoPebbleSerial::begin_software(PEBBLE_PIN, pebble_buffer, sizeof(pebble_buffer), Baud57600, 22 | SUPPORTED_SERVICES, NUM_SERVICES); 23 | #else 24 | #error "This example will only work for the Teensy 2.0, 3.0, or 3.1 boards" 25 | #endif 26 | } 27 | 28 | void loop() { 29 | // Let the ArduinoPebbleSerial code do its processing 30 | size_t length; 31 | uint16_t service_id; 32 | uint16_t attribute_id; 33 | RequestType type; 34 | if (ArduinoPebbleSerial::feed(&service_id, &attribute_id, &length, &type)) { 35 | if ((service_id == 0) && (attribute_id == 0)) { 36 | // we have a raw data frame to process 37 | static bool led_status = false; 38 | led_status = !led_status; 39 | digitalWrite(LED_BUILTIN, led_status); 40 | if (type == RequestTypeRead) { 41 | // send a response to the Pebble - reuse the same buffer for the response 42 | uint32_t current_time = millis(); 43 | memcpy(pebble_buffer, ¤t_time, 4); 44 | ArduinoPebbleSerial::write(true, pebble_buffer, 4); 45 | Serial.println("Got raw data read"); 46 | } else if (type == RequestTypeWrite) { 47 | Serial.print("Got raw data write: "); 48 | Serial.println((uint8_t)pebble_buffer[0], DEC); 49 | } else { 50 | // invalid request type - just ignore the request 51 | } 52 | } else if ((service_id == 0x1001) && (attribute_id == 0x1001)) { 53 | static uint32_t s_test_attr_data = 99999; 54 | if (type == RequestTypeWriteRead) { 55 | // read the previous value and write the new one 56 | uint32_t old_value = s_test_attr_data; 57 | memcpy(&s_test_attr_data, pebble_buffer, sizeof(s_test_attr_data)); 58 | ArduinoPebbleSerial::write(true, (const uint8_t *)&old_value, sizeof(old_value)); 59 | Serial.println("Got WriteRead for 0x1001,0x1001"); 60 | } else { 61 | // invalid request type - just ignore the request 62 | } 63 | } else { 64 | // unsupported attribute - fail the request 65 | ArduinoPebbleSerial::write(false, NULL, 0); 66 | } 67 | } 68 | 69 | static bool is_connected = false; 70 | if (ArduinoPebbleSerial::is_connected()) { 71 | if (!is_connected) { 72 | Serial.println("Connected to the smartstrap!"); 73 | is_connected = true; 74 | } 75 | static uint32_t last_notify = 0; 76 | if (last_notify == 0) { 77 | last_notify = millis(); 78 | } 79 | // notify the pebble every 2.5 seconds 80 | if (millis() - last_notify > 2500) { 81 | Serial.println("Sending notification for 0x1001,0x1001"); 82 | ArduinoPebbleSerial::notify(0x1001, 0x1001); 83 | last_notify = millis(); 84 | } 85 | } else { 86 | if (is_connected) { 87 | Serial.println("Disconnected from the smartstrap!"); 88 | is_connected = false; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /examples/Demo2/PebbleApp/appinfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "ddf1813a-940d-4fc1-9d19-c0b384ddd3c5", 3 | "shortName": "Smartstrap Demo 2", 4 | "longName": "Smartstrap Demo 2", 5 | "companyName": "Pebble", 6 | "versionCode": 1, 7 | "versionLabel": "1.0", 8 | "sdkVersion": "3", 9 | "targetPlatforms": ["basalt"], 10 | "watchapp": { 11 | "watchface": false 12 | }, 13 | "appKeys": { 14 | "dummy": 0 15 | }, 16 | "resources": { 17 | "media": [] 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/Demo2/PebbleApp/src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static const SmartstrapServiceId SERVICE_ID = 0x1001; 4 | static const SmartstrapAttributeId LED_ATTRIBUTE_ID = 0x0001; 5 | static const size_t LED_ATTRIBUTE_LENGTH = 1; 6 | static const SmartstrapAttributeId UPTIME_ATTRIBUTE_ID = 0x0002; 7 | static const size_t UPTIME_ATTRIBUTE_LENGTH = 4; 8 | 9 | static SmartstrapAttribute *led_attribute; 10 | static SmartstrapAttribute *uptime_attribute; 11 | 12 | static Window *window; 13 | static TextLayer *status_text_layer; 14 | static TextLayer *uptime_text_layer; 15 | 16 | 17 | static void prv_availability_changed(SmartstrapServiceId service_id, bool available) { 18 | if (service_id != SERVICE_ID) { 19 | return; 20 | } 21 | 22 | if (available) { 23 | text_layer_set_background_color(status_text_layer, GColorGreen); 24 | text_layer_set_text(status_text_layer, "Connected!"); 25 | } else { 26 | text_layer_set_background_color(status_text_layer, GColorRed); 27 | text_layer_set_text(status_text_layer, "Disconnected!"); 28 | } 29 | } 30 | 31 | static void prv_did_read(SmartstrapAttribute *attr, SmartstrapResult result, 32 | const uint8_t *data, size_t length) { 33 | if (attr != uptime_attribute) { 34 | return; 35 | } 36 | if (result != SmartstrapResultOk) { 37 | APP_LOG(APP_LOG_LEVEL_ERROR, "Read failed with result %d", result); 38 | return; 39 | } 40 | if (length != UPTIME_ATTRIBUTE_LENGTH) { 41 | APP_LOG(APP_LOG_LEVEL_ERROR, "Got response of unexpected length (%d)", length); 42 | return; 43 | } 44 | 45 | static char uptime_buffer[20]; 46 | snprintf(uptime_buffer, 20, "%u", (unsigned int)*(uint32_t *)data); 47 | text_layer_set_text(uptime_text_layer, uptime_buffer); 48 | } 49 | 50 | static void prv_notified(SmartstrapAttribute *attribute) { 51 | if (attribute != uptime_attribute) { 52 | return; 53 | } 54 | smartstrap_attribute_read(uptime_attribute); 55 | } 56 | 57 | 58 | static void prv_set_led_attribute(bool on) { 59 | SmartstrapResult result; 60 | uint8_t *buffer; 61 | size_t length; 62 | result = smartstrap_attribute_begin_write(led_attribute, &buffer, &length); 63 | if (result != SmartstrapResultOk) { 64 | APP_LOG(APP_LOG_LEVEL_ERROR, "Begin write failed with error %d", result); 65 | return; 66 | } 67 | 68 | buffer[0] = on; 69 | 70 | result = smartstrap_attribute_end_write(led_attribute, 1, false); 71 | if (result != SmartstrapResultOk) { 72 | APP_LOG(APP_LOG_LEVEL_ERROR, "End write failed with error %d", result); 73 | return; 74 | } 75 | } 76 | 77 | 78 | static void up_click_handler(ClickRecognizerRef recognizer, void *context) { 79 | prv_set_led_attribute(true); 80 | } 81 | 82 | static void down_click_handler(ClickRecognizerRef recognizer, void *context) { 83 | prv_set_led_attribute(false); 84 | } 85 | 86 | static void click_config_provider(void *context) { 87 | window_single_click_subscribe(BUTTON_ID_UP, up_click_handler); 88 | window_single_click_subscribe(BUTTON_ID_DOWN, down_click_handler); 89 | } 90 | 91 | static void window_load(Window *window) { 92 | window_set_background_color(window, GColorWhite); 93 | 94 | // text layer for connection status 95 | status_text_layer = text_layer_create(GRect(0, 0, 144, 40)); 96 | text_layer_set_font(status_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28)); 97 | text_layer_set_text_alignment(status_text_layer, GTextAlignmentCenter); 98 | layer_add_child(window_get_root_layer(window), text_layer_get_layer(status_text_layer)); 99 | prv_availability_changed(SERVICE_ID, smartstrap_service_is_available(SERVICE_ID)); 100 | 101 | // text layer for showing the attribute 102 | uptime_text_layer = text_layer_create(GRect(0, 60, 144, 40)); 103 | text_layer_set_font(uptime_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28)); 104 | text_layer_set_text_alignment(uptime_text_layer, GTextAlignmentCenter); 105 | text_layer_set_text(uptime_text_layer, "-"); 106 | layer_add_child(window_get_root_layer(window), text_layer_get_layer(uptime_text_layer)); 107 | } 108 | 109 | static void window_unload(Window *window) { 110 | text_layer_destroy(status_text_layer); 111 | text_layer_destroy(uptime_text_layer); 112 | } 113 | 114 | static void init(void) { 115 | // setup window 116 | window = window_create(); 117 | window_set_click_config_provider(window, click_config_provider); 118 | window_set_window_handlers(window, (WindowHandlers) { 119 | .load = window_load, 120 | .unload = window_unload, 121 | }); 122 | const bool animated = true; 123 | window_stack_push(window, animated); 124 | 125 | // setup smartstrap 126 | SmartstrapHandlers handlers = (SmartstrapHandlers) { 127 | .availability_did_change = prv_availability_changed, 128 | .did_read = prv_did_read, 129 | .notified = prv_notified 130 | }; 131 | smartstrap_subscribe(handlers); 132 | led_attribute = smartstrap_attribute_create(SERVICE_ID, LED_ATTRIBUTE_ID, LED_ATTRIBUTE_LENGTH); 133 | uptime_attribute = smartstrap_attribute_create(SERVICE_ID, UPTIME_ATTRIBUTE_ID, 134 | UPTIME_ATTRIBUTE_LENGTH); 135 | } 136 | 137 | static void deinit(void) { 138 | window_destroy(window); 139 | smartstrap_attribute_destroy(led_attribute); 140 | smartstrap_attribute_destroy(uptime_attribute); 141 | } 142 | 143 | int main(void) { 144 | init(); 145 | app_event_loop(); 146 | deinit(); 147 | } 148 | -------------------------------------------------------------------------------- /examples/Demo2/PebbleApp/wscript: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # This file is the default set of rules to compile a Pebble project. 4 | # 5 | # Feel free to customize this to your needs. 6 | # 7 | 8 | import os.path 9 | 10 | top = '.' 11 | out = 'build' 12 | 13 | def options(ctx): 14 | ctx.load('pebble_sdk') 15 | 16 | def configure(ctx): 17 | ctx.load('pebble_sdk') 18 | 19 | def build(ctx): 20 | ctx.load('pebble_sdk') 21 | 22 | build_worker = os.path.exists('worker_src') 23 | binaries = [] 24 | 25 | for p in ctx.env.TARGET_PLATFORMS: 26 | ctx.set_env(ctx.all_envs[p]) 27 | ctx.set_group(ctx.env.PLATFORM_NAME) 28 | app_elf='{}/pebble-app.elf'.format(ctx.env.BUILD_DIR) 29 | ctx.pbl_program(source=ctx.path.ant_glob('src/**/*.c'), 30 | target=app_elf) 31 | 32 | if build_worker: 33 | worker_elf='{}/pebble-worker.elf'.format(ctx.env.BUILD_DIR) 34 | binaries.append({'platform': p, 'app_elf': app_elf, 'worker_elf': worker_elf}) 35 | ctx.pbl_worker(source=ctx.path.ant_glob('worker_src/**/*.c'), 36 | target=worker_elf) 37 | else: 38 | binaries.append({'platform': p, 'app_elf': app_elf}) 39 | 40 | ctx.set_group('bundle') 41 | ctx.pbl_bundle(binaries=binaries, js=ctx.path.ant_glob('src/js/**/*.js')) 42 | -------------------------------------------------------------------------------- /examples/Demo2/TeensyDemo/TeensyDemo.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | static const uint16_t SERVICE_ID = 0x1001; 4 | static const uint16_t LED_ATTRIBUTE_ID = 0x0001; 5 | static const size_t LED_ATTRIBUTE_LENGTH = 1; 6 | static const uint16_t UPTIME_ATTRIBUTE_ID = 0x0002; 7 | static const size_t UPTIME_ATTRIBUTE_LENGTH = 4; 8 | 9 | static const uint16_t SERVICES[] = {SERVICE_ID}; 10 | static const uint8_t NUM_SERVICES = 1; 11 | 12 | static const uint8_t PEBBLE_DATA_PIN = 1; 13 | static uint8_t buffer[GET_PAYLOAD_BUFFER_SIZE(4)]; 14 | 15 | 16 | void setup() { 17 | pinMode(LED_BUILTIN, OUTPUT); 18 | #if defined(__MK20DX256__) || defined(__MK20DX128__) 19 | // Teensy 3.0/3.1 uses hardware serial mode (pins 0/1) with RX/TX shorted together 20 | ArduinoPebbleSerial::begin_hardware(buffer, sizeof(buffer), Baud57600, SERVICES, NUM_SERVICES); 21 | #elif defined(__AVR_ATmega32U4__) 22 | // Teensy 2.0 uses the one-wire software serial mode 23 | ArduinoPebbleSerial::begin_software(PEBBLE_DATA_PIN, buffer, sizeof(buffer), Baud57600, SERVICES, 24 | NUM_SERVICES); 25 | #else 26 | #error "This example will only work for the Teensy 2.0, 3.0, or 3.1 boards" 27 | #endif 28 | } 29 | 30 | void handle_uptime_request(RequestType type, size_t length) { 31 | if (type != RequestTypeRead) { 32 | // unexpected request type 33 | return; 34 | } 35 | // write back the current uptime 36 | const uint32_t uptime = millis() / 1000; 37 | ArduinoPebbleSerial::write(true, (uint8_t *)&uptime, sizeof(uptime)); 38 | } 39 | 40 | void handle_led_request(RequestType type, size_t length) { 41 | if (type != RequestTypeWrite) { 42 | // unexpected request type 43 | return; 44 | } else if (length != LED_ATTRIBUTE_LENGTH) { 45 | // unexpected request length 46 | return; 47 | } 48 | // set the LED 49 | digitalWrite(LED_BUILTIN, (bool) buffer[0]); 50 | // ACK that the write request was received 51 | ArduinoPebbleSerial::write(true, NULL, 0); 52 | } 53 | 54 | void loop() { 55 | if (ArduinoPebbleSerial::is_connected()) { 56 | static uint32_t last_notify_time = 0; 57 | const uint32_t current_time = millis() / 1000; 58 | if (current_time > last_notify_time) { 59 | ArduinoPebbleSerial::notify(SERVICE_ID, UPTIME_ATTRIBUTE_ID); 60 | last_notify_time = current_time; 61 | } 62 | } 63 | 64 | uint16_t service_id; 65 | uint16_t attribute_id; 66 | size_t length; 67 | RequestType type; 68 | if (ArduinoPebbleSerial::feed(&service_id, &attribute_id, &length, &type)) { 69 | // process the request 70 | if (service_id == SERVICE_ID) { 71 | switch (attribute_id) { 72 | case UPTIME_ATTRIBUTE_ID: 73 | handle_uptime_request(type, length); 74 | break; 75 | case LED_ATTRIBUTE_ID: 76 | handle_led_request(type, length); 77 | break; 78 | default: 79 | break; 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /keywords.txt: -------------------------------------------------------------------------------- 1 | ####################################### 2 | # Datatypes (KEYWORD1) 3 | ####################################### 4 | 5 | ArduinoPebbleSerial KEYWORD1 6 | Baud KEYWORD1 7 | RequestType KEYWORD1 8 | 9 | ####################################### 10 | # Methods and Functions (KEYWORD2) 11 | ####################################### 12 | 13 | begin_hardware KEYWORD2 14 | begin_software KEYWORD2 15 | feed KEYWORD2 16 | write KEYWORD2 17 | notify KEYWORD2 18 | is_connected KEYWORD2 19 | 20 | ####################################### 21 | # Constants (LITERAL1) 22 | ####################################### 23 | -------------------------------------------------------------------------------- /utility/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Pebble Technology 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | NOTE: The following files are licensed under the LGPL v2.1 license and not the 24 | MIT license: 25 | * utility/OneWireSoftSerial.cpp 26 | * utility/OneWireSoftSerial.h 27 | -------------------------------------------------------------------------------- /utility/OneWireSoftSerial.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This file provides a one-wire (aka half duplex) software serial implementation to faciliate easy 3 | * connection and communication with a Pebble watch via the smartstrap port. It was heavily inspired 4 | * by Arduino's software serial library, with the code for calculating the delays copied verbatim. 5 | * As is required by the license on Arduino's software serial library, this file is released 6 | * under the LGPL v2.1 license. 7 | * 8 | * This library is free software; you can redistribute it and/or 9 | * modify it under the terms of the GNU Lesser General Public 10 | * License as published by the Free Software Foundation; either 11 | * version 2.1 of the License, or (at your option) any later version. 12 | * 13 | * This library is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * Lesser General Public License for more details. 17 | */ 18 | #include "OneWireSoftSerial.h" 19 | 20 | #ifdef __arm__ 21 | // this library is not yet implemented for ARM microcontrollers 22 | void OneWireSoftSerial::begin(uint8_t pin, long speed) { } 23 | int OneWireSoftSerial::available(void) { return 0; } 24 | void OneWireSoftSerial::set_tx_enabled(bool enabled) { } 25 | void OneWireSoftSerial::write(uint8_t byte, bool is_break) { } 26 | int OneWireSoftSerial::read(void) { return -1; }; 27 | 28 | #else 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | 35 | // Statics 36 | //////////////////////////////////////////////////////////////////////////////// 37 | 38 | static char s_receive_buffer[_SS_MAX_RX_BUFF]; 39 | static volatile uint8_t s_receive_buffer_tail = 0; 40 | static volatile uint8_t s_receive_buffer_head = 0; 41 | static uint16_t s_rx_delay_centering = 0; 42 | static uint16_t s_rx_delay_intrabit = 0; 43 | static uint16_t s_rx_delay_stopbit = 0; 44 | static uint16_t s_tx_delay = 0; 45 | static uint8_t s_pin = 0; 46 | static uint8_t s_bit_mask = 0; 47 | static volatile uint8_t *s_port_output_register = 0; 48 | static volatile uint8_t *s_port_input_register = 0; 49 | static volatile uint8_t *s_pcint_mask_reg = 0; 50 | static uint8_t s_pcint_mask_value = 0; 51 | static bool s_tx_enabled = false; 52 | 53 | 54 | // Helper macros 55 | //////////////////////////////////////////////////////////////////////////////// 56 | 57 | #define SUBTRACT_CAP(a, b) ((a > b) ? (a - b) : 1) 58 | #define TUNED_DELAY(x) _delay_loop_2(x) 59 | 60 | 61 | // Helper methods 62 | //////////////////////////////////////////////////////////////////////////////// 63 | 64 | static inline void prv_set_rx_int_msk(bool enable) { 65 | if (enable) { 66 | *s_pcint_mask_reg |= s_pcint_mask_value; 67 | } else { 68 | *s_pcint_mask_reg &= ~s_pcint_mask_value; 69 | } 70 | } 71 | 72 | static inline void prv_set_tx_enabled(bool enabled) { 73 | if (enabled) { 74 | // enable pullup first to avoid the pin going low briefly 75 | digitalWrite(s_pin, HIGH); 76 | pinMode(s_pin, OUTPUT); 77 | } else { 78 | pinMode(s_pin, INPUT); 79 | digitalWrite(s_pin, HIGH); // enable pullup 80 | } 81 | } 82 | 83 | // The receive routine called by the interrupt handler 84 | static inline void prv_recv(void) { 85 | #if !defined(__arm__) && GCC_VERSION < 40302 86 | // Work-around for avr-gcc 4.3.0 OSX version bug 87 | // Preserve the registers that the compiler misses 88 | // (courtesy of Arduino forum user *etracer*) 89 | asm volatile( 90 | "push r18 \n\t" 91 | "push r19 \n\t" 92 | "push r20 \n\t" 93 | "push r21 \n\t" 94 | "push r22 \n\t" 95 | "push r23 \n\t" 96 | "push r26 \n\t" 97 | "push r27 \n\t" 98 | ::); 99 | #endif 100 | 101 | uint8_t d = 0; 102 | 103 | // If RX line is high, then we don't see any start bit 104 | // so interrupt is probably not for us 105 | if (!(*s_port_input_register & s_bit_mask)) { 106 | // Disable further interrupts during reception, this prevents 107 | // triggering another interrupt directly after we return, which can 108 | // cause problems at higher baudrates. 109 | prv_set_rx_int_msk(false); 110 | 111 | // Wait approximately 1/2 of a bit width to "center" the sample 112 | TUNED_DELAY(s_rx_delay_centering); 113 | 114 | // Read each of the 8 bits 115 | for (uint8_t i=8; i > 0; --i) { 116 | TUNED_DELAY(s_rx_delay_intrabit); 117 | d >>= 1; 118 | if (*s_port_input_register & s_bit_mask) { 119 | d |= 0x80; 120 | } 121 | } 122 | 123 | const uint8_t next = (s_receive_buffer_tail + 1) % _SS_MAX_RX_BUFF; 124 | if (next != s_receive_buffer_head) { 125 | // save new data in buffer: tail points to where byte goes 126 | s_receive_buffer[s_receive_buffer_tail] = d; // save new byte 127 | s_receive_buffer_tail = next; 128 | } 129 | 130 | // skip the stop bit 131 | TUNED_DELAY(s_rx_delay_stopbit); 132 | 133 | // Re-enable interrupts when we're sure to be inside the stop bit 134 | prv_set_rx_int_msk(true); 135 | } 136 | 137 | #if !defined(__arm__) && GCC_VERSION < 40302 138 | // Work-around for avr-gcc 4.3.0 OSX version bug 139 | // Restore the registers that the compiler misses 140 | asm volatile( 141 | "pop r27 \n\t" 142 | "pop r26 \n\t" 143 | "pop r23 \n\t" 144 | "pop r22 \n\t" 145 | "pop r21 \n\t" 146 | "pop r20 \n\t" 147 | "pop r19 \n\t" 148 | "pop r18 \n\t" 149 | ::); 150 | #endif 151 | } 152 | 153 | 154 | // Interrupt handling 155 | //////////////////////////////////////////////////////////////////////////////// 156 | 157 | #if defined(PCINT0_vect) 158 | ISR(PCINT0_vect) { 159 | prv_recv(); 160 | } 161 | #endif 162 | 163 | #if defined(PCINT1_vect) 164 | ISR(PCINT1_vect, ISR_ALIASOF(PCINT0_vect)); 165 | #endif 166 | 167 | #if defined(PCINT2_vect) 168 | ISR(PCINT2_vect, ISR_ALIASOF(PCINT0_vect)); 169 | #endif 170 | 171 | #if defined(PCINT3_vect) 172 | ISR(PCINT3_vect, ISR_ALIASOF(PCINT0_vect)); 173 | #endif 174 | 175 | 176 | // Public methods 177 | //////////////////////////////////////////////////////////////////////////////// 178 | 179 | void OneWireSoftSerial::begin(uint8_t pin, long speed) { 180 | s_pin = pin; 181 | s_bit_mask = digitalPinToBitMask(s_pin); 182 | s_port_output_register = portOutputRegister(digitalPinToPort(s_pin)); 183 | s_port_input_register = portInputRegister(digitalPinToPort(s_pin)); 184 | if (!digitalPinToPCICR(s_pin)) { 185 | return; 186 | } 187 | pinMode(2, OUTPUT); 188 | 189 | s_rx_delay_centering = s_rx_delay_intrabit = s_rx_delay_stopbit = s_tx_delay = 0; 190 | 191 | // Precalculate the various delays, in number of 4-cycle delays 192 | uint16_t bit_delay = (F_CPU / speed) / 4; 193 | 194 | // 12 (gcc 4.8.2) or 13 (gcc 4.3.2) cycles from start bit to first bit, 195 | // 15 (gcc 4.8.2) or 16 (gcc 4.3.2) cycles between bits, 196 | // 12 (gcc 4.8.2) or 14 (gcc 4.3.2) cycles from last bit to stop bit 197 | // These are all close enough to just use 15 cycles, since the inter-bit 198 | // timings are the most critical (deviations stack 8 times) 199 | s_tx_delay = SUBTRACT_CAP(bit_delay, 15 / 4); 200 | 201 | #if GCC_VERSION > 40800 202 | // Timings counted from gcc 4.8.2 output. This works up to 115200 on 203 | // 16Mhz and 57600 on 8Mhz. 204 | // 205 | // When the start bit occurs, there are 3 or 4 cycles before the 206 | // interrupt flag is set, 4 cycles before the PC is set to the right 207 | // interrupt vector address and the old PC is pushed on the stack, 208 | // and then 75 cycles of instructions (including the RJMP in the 209 | // ISR vector table) until the first delay. After the delay, there 210 | // are 17 more cycles until the pin value is read (excluding the 211 | // delay in the loop). 212 | // We want to have a total delay of 1.5 bit time. Inside the loop, 213 | // we already wait for 1 bit time - 23 cycles, so here we wait for 214 | // 0.5 bit time - (71 + 18 - 22) cycles. 215 | s_rx_delay_centering = SUBTRACT_CAP(bit_delay / 2, (4 + 4 + 75 + 17 - 23) / 4); 216 | 217 | // There are 23 cycles in each loop iteration (excluding the delay) 218 | s_rx_delay_intrabit = SUBTRACT_CAP(bit_delay, 23 / 4); 219 | 220 | // There are 37 cycles from the last bit read to the start of 221 | // stopbit delay and 11 cycles from the delay until the interrupt 222 | // mask is enabled again (which _must_ happen during the stopbit). 223 | // This delay aims at 3/4 of a bit time, meaning the end of the 224 | // delay will be at 1/4th of the stopbit. This allows some extra 225 | // time for ISR cleanup, which makes 115200 baud at 16Mhz work more 226 | // reliably 227 | s_rx_delay_stopbit = SUBTRACT_CAP(bit_delay * 3 / 4, (37 + 11) / 4); 228 | #else 229 | // Timings counted from gcc 4.3.2 output 230 | // Note that this code is a _lot_ slower, mostly due to bad register 231 | // allocation choices of gcc. This works up to 57600 on 16Mhz and 232 | // 38400 on 8Mhz. 233 | s_rx_delay_centering = SUBTRACT_CAP(bit_delay / 2, (4 + 4 + 97 + 29 - 11) / 4); 234 | s_rx_delay_intrabit = SUBTRACT_CAP(bit_delay, 11 / 4); 235 | s_rx_delay_stopbit = SUBTRACT_CAP(bit_delay * 3 / 4, (44 + 17) / 4); 236 | #endif 237 | 238 | 239 | // Enable the PCINT for the entire port here, but never disable it 240 | // (others might also need it, so we disable the interrupt by using 241 | // the per-pin PCMSK register). 242 | *digitalPinToPCICR(s_pin) |= _BV(digitalPinToPCICRbit(s_pin)); 243 | // Precalculate the pcint mask register and value, so setRxIntMask 244 | // can be used inside the ISR without costing too much time. 245 | s_pcint_mask_reg = digitalPinToPCMSK(s_pin); 246 | s_pcint_mask_value = _BV(digitalPinToPCMSKbit(s_pin)); 247 | 248 | TUNED_DELAY(s_tx_delay); // if we were low this establishes the end 249 | 250 | s_receive_buffer_head = s_receive_buffer_tail = 0; 251 | 252 | prv_set_tx_enabled(false); 253 | prv_set_rx_int_msk(true); 254 | } 255 | 256 | // Read data from buffer 257 | int OneWireSoftSerial::read() { 258 | if (s_receive_buffer_head == s_receive_buffer_tail) { 259 | return -1; 260 | } 261 | 262 | // Read from "head" 263 | uint8_t d = s_receive_buffer[s_receive_buffer_head]; // grab next byte 264 | s_receive_buffer_head = (s_receive_buffer_head + 1) % _SS_MAX_RX_BUFF; 265 | return d; 266 | } 267 | 268 | int OneWireSoftSerial::available() { 269 | return (s_receive_buffer_tail + _SS_MAX_RX_BUFF - s_receive_buffer_head) % _SS_MAX_RX_BUFF; 270 | } 271 | 272 | void OneWireSoftSerial::set_tx_enabled(bool enabled) { 273 | if (s_tx_enabled == enabled) { 274 | return; 275 | } 276 | static uint8_t s_old_sreg = 0; 277 | if (enabled) { 278 | s_old_sreg = SREG; 279 | cli(); 280 | prv_set_rx_int_msk(false); 281 | prv_set_tx_enabled(true); 282 | } else { 283 | prv_set_tx_enabled(false); 284 | prv_set_rx_int_msk(true); 285 | SREG = s_old_sreg; 286 | } 287 | s_tx_enabled = enabled; 288 | } 289 | 290 | void OneWireSoftSerial::write(uint8_t b, bool is_break /* = false */) { 291 | if (!s_tx_enabled) { 292 | return; 293 | } 294 | 295 | // By declaring these as local variables, the compiler will put them 296 | // in registers _before_ disabling interrupts and entering the 297 | // critical timing sections below, which makes it a lot easier to 298 | // verify the cycle timings 299 | volatile uint8_t *reg = s_port_output_register; 300 | uint8_t reg_mask = s_bit_mask; 301 | uint8_t inv_mask = ~s_bit_mask; 302 | uint16_t delay = s_tx_delay; 303 | uint8_t num_bits = is_break ? 9 : 8; 304 | 305 | // Write the start bit 306 | *reg &= inv_mask; 307 | TUNED_DELAY(delay); 308 | 309 | // Write each of the 8 bits 310 | for (uint8_t i = num_bits; i > 0; --i) { 311 | if (b & 1) { // choose bit 312 | *reg |= reg_mask; // send 1 313 | } else { 314 | *reg &= inv_mask; // send 0 315 | } 316 | 317 | TUNED_DELAY(delay); 318 | b >>= 1; 319 | } 320 | 321 | // restore pin to natural state 322 | *reg |= reg_mask; 323 | TUNED_DELAY(s_tx_delay); 324 | } 325 | #endif // __arm__ 326 | -------------------------------------------------------------------------------- /utility/OneWireSoftSerial.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This file provides a one-wire (aka half duplex) software serial implementation to faciliate easy 3 | * connection and communication with a Pebble watch via the smartstrap port. It was heavily inspired 4 | * by Arduino's software serial library, with the code for calculating the delays copied verbatim. 5 | * As is required by the license on Arduino's software serial library, this file is released 6 | * under the LGPL v2.1 license. 7 | * 8 | * This library is free software; you can redistribute it and/or 9 | * modify it under the terms of the GNU Lesser General Public 10 | * License as published by the Free Software Foundation; either 11 | * version 2.1 of the License, or (at your option) any later version. 12 | * 13 | * This library is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 16 | * Lesser General Public License for more details. 17 | */ 18 | 19 | #ifndef __SOFT_SERIAL_H__ 20 | #define __SOFT_SERIAL_H__ 21 | 22 | #include 23 | 24 | #if ARDUINO > 1000 25 | #define STATIC_ASSERT_VALID_ONE_WIRE_SOFT_SERIAL_PIN(pin) \ 26 | static_assert(digitalPinToPCICR(pin) != NULL, "This pin does not support PC interrupts!") 27 | #else 28 | #define STATIC_ASSERT_VALID_ONE_WIRE_SOFT_SERIAL_PIN(pin) 29 | #endif 30 | #define _SS_MAX_RX_BUFF 64 // RX buffer size 31 | 32 | class OneWireSoftSerial 33 | { 34 | public: 35 | // public methods 36 | static void begin(uint8_t pin, long speed); 37 | static int available(); 38 | static void set_tx_enabled(bool enabled); 39 | static void write(uint8_t byte, bool is_break = false); 40 | static int read(); 41 | }; 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /utility/PebbleSerial.c: -------------------------------------------------------------------------------- 1 | /* This library is for communicating with a Pebble Time via the accessory port for Smart Straps. */ 2 | 3 | #include "PebbleSerial.h" 4 | 5 | #include "crc.h" 6 | #include "encoding.h" 7 | #include "board.h" 8 | 9 | #define PROTOCOL_VERSION 1 10 | #define GENERIC_SERVICE_VERSION 1 11 | 12 | #define FRAME_MIN_LENGTH 8 13 | #define FRAME_VERSION_OFFSET 0 14 | #define FRAME_FLAGS_OFFSET 1 15 | #define FRAME_PROFILE_OFFSET 5 16 | #define FRAME_PAYLOAD_OFFSET 7 17 | 18 | #define FLAGS_IS_READ_OFFSET 0 19 | #define FLAGS_IS_MASTER_OFFSET 1 20 | #define FLAGS_IS_NOTIFICATION_OFFSET 2 21 | #define FLAGS_RESERVED_OFFSET 3 22 | #define FLAGS_IS_READ_MASK (0x1 << FLAGS_IS_READ_OFFSET) 23 | #define FLAGS_IS_MASTER_MASK (0x1 << FLAGS_IS_MASTER_OFFSET) 24 | #define FLAGS_IS_NOTIFICATION_MASK (0x1 << FLAGS_IS_NOTIFICATION_OFFSET) 25 | #define FLAGS_RESERVED_MASK (~(FLAGS_IS_READ_MASK | \ 26 | FLAGS_IS_MASTER_MASK | \ 27 | FLAGS_IS_NOTIFICATION_MASK)) 28 | #define FLAGS_GET(flags, mask, offset) (((flags) & mask) >> offset) 29 | #define FLAGS_SET(flags, mask, offset, value) \ 30 | (flags) = ((flags) & ~mask) | (((value) << offset) & mask) 31 | 32 | typedef enum { 33 | SmartstrapProfileInvalid = 0x00, 34 | SmartstrapProfileLinkControl = 0x01, 35 | SmartstrapProfileRawData = 0x02, 36 | SmartstrapProfileGenericService = 0x03, 37 | NumSmartstrapProfiles 38 | } SmartstrapProfile; 39 | 40 | typedef enum { 41 | LinkControlTypeInvalid = 0, 42 | LinkControlTypeStatus = 1, 43 | LinkControlTypeProfiles = 2, 44 | LinkControlTypeBaud = 3, 45 | NumLinkControlTypes 46 | } LinkControlType; 47 | 48 | typedef enum { 49 | LinkControlStatusOk = 0, 50 | LinkControlStatusBaudRate = 1, 51 | LinkControlStatusDisconnect = 2 52 | } LinkControlStatus; 53 | 54 | typedef struct { 55 | uint8_t version; 56 | uint32_t flags; 57 | uint16_t profile; 58 | } FrameHeader; 59 | 60 | typedef struct { 61 | FrameHeader header; 62 | uint8_t *payload; 63 | uint8_t checksum; 64 | size_t length; 65 | size_t max_payload_length; 66 | uint8_t footer_byte; 67 | bool should_drop; 68 | bool read_ready; 69 | bool is_read; 70 | EncodingStreamingContext encoding_ctx; 71 | } PebbleFrameInfo; 72 | 73 | typedef struct __attribute__((packed)) { 74 | uint8_t version; 75 | uint16_t service_id; 76 | uint16_t attribute_id; 77 | uint8_t type; 78 | uint8_t error; 79 | uint16_t length; 80 | uint8_t data[]; 81 | } GenericServicePayload; 82 | 83 | static SmartstrapRequestType s_last_generic_service_type; 84 | static uint32_t s_last_message_time = 0; 85 | static PebbleFrameInfo s_frame; 86 | static SmartstrapCallback s_callback; 87 | static bool s_connected; 88 | static PebbleBaud s_current_baud = PebbleBaudInvalid; 89 | static PebbleBaud s_target_baud = PebbleBaudInvalid; 90 | static const uint32_t BAUDS[] = { 9600, 14400, 19200, 28800, 38400, 57600, 67500, 115200, 125000, 91 | 230400, 250000, 460800 }; 92 | static uint16_t s_notify_service; 93 | static uint16_t s_notify_attribute; 94 | static const uint16_t *s_supported_services; 95 | static uint8_t s_num_supported_services; 96 | static struct { 97 | bool can_respond; 98 | uint16_t service_id; 99 | uint16_t attribute_id; 100 | } s_pending_response; 101 | 102 | 103 | void prv_set_baud(PebbleBaud baud) { 104 | if (baud == s_current_baud) { 105 | return; 106 | } 107 | s_current_baud = baud; 108 | s_callback(SmartstrapCmdSetBaudRate, BAUDS[baud]); 109 | s_callback(SmartstrapCmdSetTxEnabled, true); 110 | s_callback(SmartstrapCmdSetTxEnabled, false); 111 | } 112 | 113 | void pebble_init(SmartstrapCallback callback, PebbleBaud baud, const uint16_t *services, 114 | uint8_t num_services) { 115 | s_callback = callback; 116 | s_target_baud = baud; 117 | s_supported_services = services; 118 | s_num_supported_services = num_services; 119 | prv_set_baud(PebbleBaud9600); 120 | } 121 | 122 | void pebble_prepare_for_read(uint8_t *buffer, size_t length) { 123 | s_frame = (PebbleFrameInfo) { 124 | .payload = buffer, 125 | .max_payload_length = length, 126 | .read_ready = true 127 | }; 128 | } 129 | 130 | static void prv_send_flag(void) { 131 | s_callback(SmartstrapCmdWriteByte, ENCODING_FLAG); 132 | } 133 | 134 | static void prv_send_byte(uint8_t data, uint8_t *parity) { 135 | crc8_calculate_byte_streaming(data, parity); 136 | if (encoding_encode(&data)) { 137 | s_callback(SmartstrapCmdWriteByte, ENCODING_ESCAPE); 138 | } 139 | s_callback(SmartstrapCmdWriteByte, data); 140 | } 141 | 142 | static void prv_write_internal(SmartstrapProfile profile, const uint8_t *data1, size_t length1, 143 | const uint8_t *data2, size_t length2, bool is_notify) { 144 | uint8_t parity = 0; 145 | 146 | // enable tx 147 | s_callback(SmartstrapCmdSetTxEnabled, true); 148 | 149 | // send flag 150 | prv_send_flag(); 151 | 152 | // send version 153 | prv_send_byte(PROTOCOL_VERSION, &parity); 154 | 155 | // send header flags (currently just hard-coded) 156 | if (is_notify) { 157 | prv_send_byte(0x04, &parity); 158 | } else { 159 | prv_send_byte(0, &parity); 160 | } 161 | prv_send_byte(0, &parity); 162 | prv_send_byte(0, &parity); 163 | prv_send_byte(0, &parity); 164 | 165 | // send profile (currently well within a single byte) 166 | prv_send_byte(profile, &parity); 167 | prv_send_byte(0, &parity); 168 | 169 | // send data 170 | size_t i; 171 | for (i = 0; i < length1; ++i) { 172 | prv_send_byte(data1[i], &parity); 173 | } 174 | for (i = 0; i < length2; ++i) { 175 | prv_send_byte(data2[i], &parity); 176 | } 177 | 178 | // send parity 179 | prv_send_byte(parity, &parity); 180 | 181 | // send flag 182 | prv_send_flag(); 183 | 184 | // flush and disable tx 185 | s_callback(SmartstrapCmdSetTxEnabled, false); 186 | } 187 | 188 | static bool prv_supports_raw_data_profile(void) { 189 | uint8_t i; 190 | for (i = 0; i < s_num_supported_services; i++) { 191 | if (s_supported_services[i] == 0x0000) { 192 | return true; 193 | } 194 | } 195 | return false; 196 | } 197 | 198 | static bool prv_supports_generic_profile(void) { 199 | uint8_t i; 200 | for (i = 0; i < s_num_supported_services; i++) { 201 | if (s_supported_services[i] > 0x0000) { 202 | return true; 203 | } 204 | } 205 | return false; 206 | } 207 | 208 | static void prv_handle_link_control(uint8_t *buffer) { 209 | // we will re-use the buffer for the response 210 | LinkControlType type = buffer[1]; 211 | if (type == LinkControlTypeStatus) { 212 | if (s_current_baud != s_target_baud) { 213 | buffer[2] = LinkControlStatusBaudRate; 214 | } else { 215 | buffer[2] = LinkControlStatusOk; 216 | s_connected = true; 217 | } 218 | prv_write_internal(SmartstrapProfileLinkControl, buffer, 3, NULL, 0, false); 219 | } else if (type == LinkControlTypeProfiles) { 220 | uint16_t profiles[2]; 221 | uint8_t num_profiles = 0; 222 | if (prv_supports_raw_data_profile()) { 223 | profiles[num_profiles++] = SmartstrapProfileRawData; 224 | } 225 | if (prv_supports_generic_profile()) { 226 | profiles[num_profiles++] = SmartstrapProfileGenericService; 227 | } 228 | prv_write_internal(SmartstrapProfileLinkControl, buffer, 2, (uint8_t *)profiles, 229 | num_profiles * sizeof(uint16_t), false); 230 | } else if (type == LinkControlTypeBaud) { 231 | buffer[2] = s_target_baud; 232 | prv_write_internal(SmartstrapProfileLinkControl, buffer, 3, NULL, 0, false); 233 | prv_set_baud(s_target_baud); 234 | } 235 | } 236 | 237 | static bool prv_handle_generic_service(GenericServicePayload *data) { 238 | if (data->error != 0) { 239 | return true; 240 | } 241 | 242 | uint16_t service_id = data->service_id; 243 | uint16_t attribute_id = data->attribute_id; 244 | s_last_generic_service_type = data->type; 245 | uint16_t length = data->length; 246 | if ((service_id == 0x0101) && (attribute_id == 0x0002)) { 247 | // notification info attribute 248 | if (s_notify_service) { 249 | uint16_t info[2] = {s_notify_service, s_notify_attribute}; 250 | length = sizeof(info); 251 | s_pending_response.can_respond = true; 252 | s_pending_response.service_id = service_id; 253 | s_pending_response.attribute_id = attribute_id; 254 | pebble_write(true, (uint8_t *)&info, length); 255 | } 256 | return true; 257 | } else if ((service_id == 0x0101) && (attribute_id == 0x0001)) { 258 | // this is a service discovery frame 259 | s_pending_response.can_respond = true; 260 | s_pending_response.service_id = service_id; 261 | s_pending_response.attribute_id = attribute_id; 262 | pebble_write(true, (uint8_t *)s_supported_services, 263 | s_num_supported_services * sizeof(uint16_t)); 264 | return true; 265 | } 266 | return false; 267 | } 268 | 269 | static void prv_store_byte(const uint8_t data) { 270 | // Find which field this byte belongs to based on the number of bytes we've received so far 271 | if (s_frame.length >= FRAME_PAYLOAD_OFFSET) { 272 | // This byte is part of either the payload or the checksum 273 | const uint32_t payload_length = s_frame.length - FRAME_PAYLOAD_OFFSET; 274 | if (payload_length > s_frame.max_payload_length) { 275 | // The payload is longer than the payload buffer so drop this byte 276 | s_frame.should_drop = true; 277 | } else { 278 | // The checksum byte comes after the payload in the frame. This byte we are receiving could 279 | // be the checksum byte, or it could be part of the payload; we don't know at this point. So, 280 | // we will store it temporarily in footer_byte and if we've previously stored a byte there, 281 | // will copy that previous byte into the payload. This avoids us overrunning the payload 282 | // buffer if it's conservatively sized. 283 | if (payload_length > 0) { 284 | // put the previous byte into the payload buffer 285 | s_frame.payload[payload_length - 1] = s_frame.footer_byte; 286 | } 287 | s_frame.footer_byte = data; 288 | } 289 | } else if (s_frame.length >= FRAME_PROFILE_OFFSET) { 290 | // This byte is part of the profile field 291 | const uint32_t byte_offset = s_frame.length - FRAME_PROFILE_OFFSET; 292 | s_frame.header.profile |= (data << (byte_offset * 8)); 293 | } else if (s_frame.length >= FRAME_FLAGS_OFFSET) { 294 | // This byte is part of the flags field 295 | const uint32_t byte_offset = s_frame.length - FRAME_FLAGS_OFFSET; 296 | s_frame.header.flags |= (data << (byte_offset * 8)); 297 | } else { 298 | // The version field should always be first (and a single byte) 299 | s_frame.header.version = data; 300 | } 301 | 302 | // increment the length run the CRC calculation 303 | s_frame.length++; 304 | crc8_calculate_byte_streaming(data, &s_frame.checksum); 305 | } 306 | 307 | static void prv_frame_validate(void) { 308 | if ((s_frame.should_drop == false) && 309 | (s_frame.header.version > 0) && 310 | (s_frame.header.version <= PROTOCOL_VERSION) && 311 | (FLAGS_GET(s_frame.header.flags, FLAGS_IS_MASTER_MASK, FLAGS_IS_MASTER_OFFSET) == 1) && 312 | (FLAGS_GET(s_frame.header.flags, FLAGS_RESERVED_MASK, FLAGS_RESERVED_OFFSET) == 0) && 313 | (s_frame.header.profile > SmartstrapProfileInvalid) && 314 | (s_frame.header.profile < NumSmartstrapProfiles) && 315 | (s_frame.length >= FRAME_MIN_LENGTH) && 316 | (s_frame.checksum == 0)) { 317 | // this is a valid frame 318 | } else { 319 | // drop the frame 320 | s_frame.should_drop = true; 321 | } 322 | } 323 | 324 | bool pebble_handle_byte(uint8_t data, uint16_t *service_id, uint16_t *attribute_id, size_t *length, 325 | SmartstrapRequestType *type, uint32_t time) { 326 | if (!s_frame.read_ready) { 327 | // we shouldn't be reading new data 328 | return false; 329 | } 330 | 331 | bool encoding_err, should_store = false; 332 | bool is_complete = encoding_streaming_decode(&s_frame.encoding_ctx, &data, &should_store, 333 | &encoding_err); 334 | if (encoding_err) { 335 | s_frame.should_drop = true; 336 | } else if (is_complete) { 337 | prv_frame_validate(); 338 | } else if (should_store) { 339 | prv_store_byte(data); 340 | } 341 | 342 | if (s_frame.should_drop || is_complete) { 343 | // prepare the encoding context for the next frame 344 | encoding_streaming_decode_reset(&s_frame.encoding_ctx); 345 | } 346 | 347 | if (is_complete) { 348 | bool give_to_user = false; 349 | if (s_frame.should_drop) { 350 | // reset the frame 351 | pebble_prepare_for_read(s_frame.payload, s_frame.max_payload_length); 352 | } else if (s_frame.header.profile == SmartstrapProfileLinkControl) { 353 | s_last_message_time = time; 354 | // handle this link control frame 355 | prv_handle_link_control(s_frame.payload); 356 | // prepare for the next frame 357 | pebble_prepare_for_read(s_frame.payload, s_frame.max_payload_length); 358 | } else if (s_frame.header.profile == SmartstrapProfileGenericService) { 359 | GenericServicePayload header = *(GenericServicePayload *)s_frame.payload; 360 | memmove(s_frame.payload, &s_frame.payload[sizeof(header)], header.length); 361 | // handle this generic service frame 362 | if (prv_handle_generic_service(&header)) { 363 | s_last_message_time = time; 364 | // we handled it, so prepare for the next frame 365 | pebble_prepare_for_read(s_frame.payload, s_frame.max_payload_length); 366 | } else { 367 | // pass up to user to handle 368 | give_to_user = true; 369 | *service_id = header.service_id; 370 | *attribute_id = header.attribute_id; 371 | *length = header.length; 372 | *type = header.type; 373 | } 374 | } else { 375 | give_to_user = true; 376 | *service_id = 0; 377 | *attribute_id = 0; 378 | *length = s_frame.length - FRAME_MIN_LENGTH; 379 | if (FLAGS_GET(s_frame.header.flags, FLAGS_IS_READ_MASK, FLAGS_IS_READ_OFFSET)) { 380 | if (*length) { 381 | *type = SmartstrapRequestTypeWriteRead; 382 | } else { 383 | *type = SmartstrapRequestTypeRead; 384 | } 385 | } else { 386 | *type = SmartstrapRequestTypeWrite; 387 | } 388 | } 389 | if (give_to_user) { 390 | s_last_message_time = time; 391 | s_frame.read_ready = false; 392 | s_pending_response.service_id = *service_id; 393 | s_pending_response.attribute_id = *attribute_id; 394 | s_pending_response.can_respond = true; 395 | return true; 396 | } 397 | } 398 | 399 | if (time < s_last_message_time) { 400 | // wrapped around 401 | s_last_message_time = time; 402 | } else if (time - s_last_message_time > 10000) { 403 | // haven't received a valid frame in over 10 seconds so reset the baudrate 404 | prv_set_baud(PebbleBaud9600); 405 | s_connected = false; 406 | } 407 | 408 | return false; 409 | } 410 | 411 | bool pebble_write(bool success, const uint8_t *buffer, uint16_t length) { 412 | if (!s_pending_response.can_respond) { 413 | return false; 414 | } 415 | if (s_pending_response.service_id == 0) { 416 | if (s_pending_response.attribute_id != 0) { 417 | return false; 418 | } 419 | prv_write_internal(SmartstrapProfileRawData, buffer, length, NULL, 0, false); 420 | } else if (s_pending_response.service_id < 0x00FF) { 421 | return false; 422 | } else { 423 | GenericServicePayload frame = (GenericServicePayload ) { 424 | .version = GENERIC_SERVICE_VERSION, 425 | .service_id = s_pending_response.service_id, 426 | .attribute_id = s_pending_response.attribute_id, 427 | .type = s_last_generic_service_type, 428 | .error = success ? 0 : 1, 429 | .length = length 430 | }; 431 | prv_write_internal(SmartstrapProfileGenericService, (uint8_t *)&frame, sizeof(frame), buffer, 432 | length, false); 433 | } 434 | s_pending_response.can_respond = false; 435 | return true; 436 | } 437 | 438 | void pebble_notify(uint16_t service_id, uint16_t attribute_id) { 439 | s_notify_service = service_id; 440 | s_notify_attribute = attribute_id; 441 | SmartstrapProfile profile; 442 | if (service_id == 0) { 443 | profile = SmartstrapProfileRawData; 444 | } else { 445 | profile = SmartstrapProfileGenericService; 446 | } 447 | s_callback(SmartstrapCmdSetTxEnabled, true); 448 | s_callback(SmartstrapCmdWriteBreak, 0); 449 | s_callback(SmartstrapCmdWriteBreak, 0); 450 | s_callback(SmartstrapCmdWriteBreak, 0); 451 | s_callback(SmartstrapCmdSetTxEnabled, false); 452 | prv_write_internal(profile, NULL, 0, NULL, 0, true); 453 | } 454 | 455 | bool pebble_is_connected(uint32_t time) { 456 | if (time - s_last_message_time > 10000) { 457 | prv_set_baud(PebbleBaud9600); 458 | s_connected = false; 459 | } 460 | return s_connected; 461 | } 462 | -------------------------------------------------------------------------------- /utility/PebbleSerial.h: -------------------------------------------------------------------------------- 1 | #ifndef __PEBBLE_SERIAL_H__ 2 | #define __PEBBLE_SERIAL_H__ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #define PEBBLE_MIN_PAYLOAD (20 + PEBBLE_PAYLOAD_OVERHEAD) 9 | #define PEBBLE_PAYLOAD_OVERHEAD 9 10 | 11 | #define MAX(a,b) ((a) > (b) ? (a) : (b)) 12 | #define GET_PAYLOAD_BUFFER_SIZE(max_data_length) \ 13 | MAX(max_data_length + PEBBLE_PAYLOAD_OVERHEAD, PEBBLE_MIN_PAYLOAD) 14 | 15 | typedef enum { 16 | SmartstrapCmdSetBaudRate, 17 | SmartstrapCmdSetTxEnabled, 18 | SmartstrapCmdWriteByte, 19 | SmartstrapCmdWriteBreak 20 | } SmartstrapCmd; 21 | 22 | typedef enum { 23 | PebbleBaud9600, 24 | PebbleBaud14400, 25 | PebbleBaud19200, 26 | PebbleBaud28800, 27 | PebbleBaud38400, 28 | PebbleBaud57600, 29 | PebbleBaud62500, 30 | PebbleBaud115200, 31 | PebbleBaud125000, 32 | PebbleBaud230400, 33 | PebbleBaud250000, 34 | PebbleBaud460800, 35 | 36 | PebbleBaudInvalid 37 | } PebbleBaud; 38 | 39 | typedef enum { 40 | SmartstrapResultOk = 0, 41 | SmartstrapResultNotSupported 42 | } SmartstrapResult; 43 | 44 | typedef enum { 45 | SmartstrapRequestTypeRead = 0, 46 | SmartstrapRequestTypeWrite = 1, 47 | SmartstrapRequestTypeWriteRead = 2 48 | } SmartstrapRequestType; 49 | 50 | 51 | typedef void (*SmartstrapCallback)(SmartstrapCmd cmd, uint32_t arg); 52 | 53 | void pebble_init(SmartstrapCallback callback, PebbleBaud baud, const uint16_t *services, 54 | uint8_t num_services); 55 | void pebble_prepare_for_read(uint8_t *buffer, size_t length); 56 | bool pebble_handle_byte(uint8_t data, uint16_t *service_id, uint16_t *attribute_id, size_t *length, 57 | SmartstrapRequestType *type, uint32_t time_ms); 58 | bool pebble_write(bool success, const uint8_t *buffer, uint16_t length); 59 | void pebble_notify(uint16_t service_id, uint16_t attribute_id); 60 | bool pebble_is_connected(uint32_t time); 61 | 62 | #endif // __PEBBLE_SERIAL_H__ 63 | -------------------------------------------------------------------------------- /utility/board.h: -------------------------------------------------------------------------------- 1 | #ifndef __BOARD_H__ 2 | #define __BOARD_H__ 3 | 4 | /* 5 | * This file contains definitions of board-specific variables and functions to support the 6 | * ArduinoPebbleSerial library. 7 | */ 8 | 9 | #include 10 | #include 11 | 12 | // Helper macros for setting and clearing a bit within a register 13 | #define cbi(sfr, bit) (sfr &= ~_BV(bit)) 14 | #define sbi(sfr, bit) (sfr |= _BV(bit)) 15 | 16 | // The board-specific variables are defined below 17 | #if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega2560__) 18 | /* Arduino Mega, Teensy 2.0, etc */ 19 | #define BOARD_SERIAL Serial1 20 | static inline void board_begin(void) { 21 | } 22 | static inline void board_set_tx_enabled(bool enabled) { 23 | if (enabled) { 24 | bitSet(UCSR1B, TXEN1); 25 | bitClear(UCSR1B, RXEN1); 26 | } else { 27 | bitClear(UCSR1B, TXEN1); 28 | bitClear(DDRD, 3); 29 | bitSet(PORTD, 3); 30 | bitSet(UCSR1B, RXEN1); 31 | } 32 | } 33 | static inline void board_set_even_parity(bool enabled) { 34 | if (enabled) { 35 | bitSet(UCSR1C, UPM11); 36 | } else { 37 | bitClear(UCSR1C, UPM11); 38 | } 39 | } 40 | 41 | #elif defined(__AVR_ATmega328__) || defined(__AVR_ATmega328P__) 42 | /* Arduino Uno, etc */ 43 | #define BOARD_SERIAL Serial 44 | static inline void board_begin(void) { 45 | } 46 | static inline void board_set_tx_enabled(bool enabled) { 47 | if (enabled) { 48 | bitSet(UCSR0B, TXEN0); 49 | bitClear(UCSR0B, RXEN0); 50 | } else { 51 | bitClear(UCSR0B, TXEN0); 52 | bitClear(DDRD, 1); 53 | bitSet(PORTD, 1); 54 | bitSet(UCSR0B, RXEN0); 55 | } 56 | } 57 | static inline void board_set_even_parity(bool enabled) { 58 | if (enabled) { 59 | bitSet(UCSR0C, UPM01); 60 | } else { 61 | bitClear(UCSR0C, UPM01); 62 | } 63 | } 64 | #elif defined(__MK20DX256__) || defined(__MK20DX128__) 65 | /* Teensy 3.0, Teensy 3.1, etc */ 66 | #define BOARD_SERIAL Serial1 67 | static inline void board_begin(void) { 68 | // configure TX as open-drain 69 | CORE_PIN1_CONFIG |= PORT_PCR_ODE; 70 | } 71 | static inline void board_set_tx_enabled(bool enabled) { 72 | // the TX and RX are tied together and we'll just drop any loopback frames 73 | } 74 | static inline void board_set_even_parity(bool enabled) { 75 | if (enabled) { 76 | serial_format(SERIAL_8E1); 77 | } else { 78 | serial_format(SERIAL_8N1); 79 | } 80 | } 81 | 82 | #else 83 | #error "Board not supported!" 84 | #endif 85 | 86 | #endif // __BOARD_H__ 87 | -------------------------------------------------------------------------------- /utility/crc.c: -------------------------------------------------------------------------------- 1 | #include "crc.h" 2 | 3 | void crc8_calculate_byte_streaming(const uint8_t data, uint8_t *crc) { 4 | // Optimal polynomial chosen based on 5 | // http://users.ece.cmu.edu/~koopman/roses/dsn04/koopman04_crc_poly_embedded.pdf 6 | // Note that this is different than the standard CRC-8 polynomial, because the 7 | // standard CRC-8 polynomial is not particularly good. 8 | 9 | // nibble lookup table for (x^8 + x^5 + x^3 + x^2 + x + 1) 10 | static const uint8_t lookup_table[] = 11 | { 0, 47, 94, 113, 188, 147, 226, 205, 87, 120, 9, 38, 235, 196, 12 | 181, 154 }; 13 | 14 | int i; 15 | for (i = 2; i > 0; i--) { 16 | uint8_t nibble = data; 17 | if (i % 2 == 0) { 18 | nibble >>= 4; 19 | } 20 | int index = nibble ^ (*crc >> 4); 21 | *crc = lookup_table[index & 0xf] ^ (*crc << 4); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /utility/crc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | void crc8_calculate_byte_streaming(const uint8_t data, uint8_t *crc); 6 | -------------------------------------------------------------------------------- /utility/encoding.c: -------------------------------------------------------------------------------- 1 | #include "encoding.h" 2 | 3 | void encoding_streaming_decode_reset(EncodingStreamingContext *ctx) { 4 | ctx->escape = false; 5 | } 6 | 7 | bool encoding_streaming_decode(EncodingStreamingContext *ctx, uint8_t *data, bool *should_store, 8 | bool *encoding_error) { 9 | bool is_complete = false; 10 | *encoding_error = false; 11 | *should_store = false; 12 | if (*data == ENCODING_FLAG) { 13 | if (ctx->escape) { 14 | // extra escape character before flag 15 | ctx->escape = false; 16 | *encoding_error = true; 17 | } 18 | // we've reached the end of the frame 19 | is_complete = true; 20 | } else if (*data == ENCODING_ESCAPE) { 21 | if (ctx->escape) { 22 | // invalid sequence 23 | ctx->escape = false; 24 | *encoding_error = true; 25 | } else { 26 | // ignore this character and escape the next one 27 | ctx->escape = true; 28 | } 29 | } else { 30 | if (ctx->escape) { 31 | *data ^= ENCODING_ESCAPE_MASK; 32 | ctx->escape = false; 33 | } 34 | *should_store = true; 35 | } 36 | 37 | return is_complete; 38 | } 39 | 40 | bool encoding_encode(uint8_t *data) { 41 | if (*data == ENCODING_FLAG || *data == ENCODING_ESCAPE) { 42 | *data ^= ENCODING_ESCAPE_MASK; 43 | return true; 44 | } 45 | return false; 46 | } 47 | -------------------------------------------------------------------------------- /utility/encoding.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | static const uint8_t ENCODING_FLAG = 0x7E; 7 | static const uint8_t ENCODING_ESCAPE = 0x7D; 8 | static const uint8_t ENCODING_ESCAPE_MASK = 0x20; 9 | 10 | typedef struct { 11 | bool escape; 12 | } EncodingStreamingContext; 13 | 14 | void encoding_streaming_decode_reset(EncodingStreamingContext *ctx); 15 | bool encoding_streaming_decode(EncodingStreamingContext *ctx, uint8_t *data, bool *complete, 16 | bool *is_invalid); 17 | bool encoding_encode(uint8_t *data); 18 | --------------------------------------------------------------------------------