Pebble Time Watch Smartstrap Tutorial

3.7K156

Intro: Pebble Time Watch Smartstrap Tutorial

Let's make your Pebble Watch talk to Arduino through serial port.

You will need

  1. ATmega32U4 based Arduino board,(i.e. Micro, Pro Micro, Leonardo)
  2. One Pebble Time charging cable(Don't want to break yours? get a cheap one on Ebay)
  3. Jumper wires
  4. 10K resistor
  5. Soldering iron
  6. Hobby knife
  7. Pebble Time watch with 3.4+ firmware updated

STEP 1: Hack a Charging Cable

Use a sharp hobby knife to cut open the charging cable

The pin layout face down left to right are

Ground, data, power in/out, ground.

We only use the right 3 pins

Solder a SMD 10K resistor to bridge the power and Data pin or use one on a breadboard

STEP 2: Program Arduino

  1. Download and install teensyduino for the Arduino
  2. Download this Library as a .zip by clicking on the "Download ZIP" button on GitHub.
  3. In Arduino, create a new project and go to "Sketch" -> "Include Library" -> "Add .ZIP Library..." Select the .zip you just downloaded.
  4. Create a new Arduino sketch and put the following code to it, select your board type and download the code to the board

#include <ArduinoPebbleSerial.h>

static const char* Button_Values[]= {"NONE","BACK", "UP", "SELECT", "DOWN"};

static const uint16_t SUPPORTED_SERVICES[] = {0x0000, 0x1001}; static const uint8_t NUM_SERVICES = 2; static uint8_t pebble_buffer[GET_PAYLOAD_BUFFER_SIZE(200)]; static String pc_string;

void setup() { // General init Serial.begin(115200); #if defined(__AVR_ATmega32U4__) ArduinoPebbleSerial::begin_hardware(pebble_buffer, sizeof(pebble_buffer), Baud57600, SUPPORTED_SERVICES, NUM_SERVICES); #else #error "This example will only work for those 32u4 based boards, e.g. Arduino Micro" #endif }

void loop() { // Let the ArduinoPebbleSerial code do its processing size_t length; uint16_t service_id; uint16_t attribute_id; RequestType type; if (ArduinoPebbleSerial::feed(&service_id, &attribute_id, &length, &type)) { if ((service_id == 0x1001) && (attribute_id == 0x1001)) { // we have a raw data frame to process if (type == RequestTypeRead) {//Pebble want to read from Arduino // send a response to the Pebble - reuse the same buffer for the response uint32_t current_time = millis(); if(pc_string.length()==0){ pc_string = "EMPTY!!"; } char buf[50]; pc_string.toCharArray(buf, 50); Serial.print("pc_string.length()"); Serial.println(pc_string.length()); Serial.print("buf"); Serial.println(buf); memcpy(pebble_buffer, buf, pc_string.length()); pebble_buffer[pc_string.length()]='\0'; Serial.print("pebble_buffer"); Serial.println(pebble_buffer[0]); ArduinoPebbleSerial::write(true, pebble_buffer, pc_string.length()); Serial.print("Sent message to Pebble"); Serial.println(pc_string); pc_string = ""; } else if (type == RequestTypeWrite) {//Pebble wrote something to Arduino Serial.print("Got New Button event: "); uint8_t button_value = pebble_buffer[0]; uint8_t click_type = pebble_buffer[1]; if(button_value<=4){ Serial.println(Button_Values[button_value]); } } } else { // unsupported attribute - fail the request ArduinoPebbleSerial::write(false, NULL, 0); } } static bool is_connected = false; if (ArduinoPebbleSerial::is_connected()) { if (!is_connected) { Serial.println("Connected to the smartstrap!"); is_connected = true; }

//scan the usb serial port for new input data if(Serial.available()){ char inChar = (char)Serial.read(); pc_string +=inChar; if(inChar=='#'){//stop sign Serial.println("Notifying Pebble: "); ArduinoPebbleSerial::notify(0x1001, 0x1001); } } } else { if (is_connected) { Serial.println("Disconnected from the smartstrap!"); is_connected = false; } } }

STEP 3: Programming Pebble Time

  1. Set up a pebble account on cloudpebble

  2. Make sure the PC and Smartphone are on the same local network then turn on the developer Connection on your smartphone Pebble time app Settings--> Developer Connection
  3. Create a new empty project, add a new source file named "main.c" then put the following code to it

#include <pebble.h>

#define TIMEOUT_MS 1000 #define MAX_READ_SIZE 100 static Window *s_main_window; static TextLayer *s_status_layer; static TextLayer *s_attr_text_layer; static char s_text_buffer1[20]; static SmartstrapAttribute *s_raw_attribute; static SmartstrapAttribute *s_attr_attribute; static void prv_update_text(void) { if (smartstrap_service_is_available(0x1001)) { text_layer_set_text(s_status_layer, "Connected!"); } else { text_layer_set_text(s_status_layer, "Connecting..."); } } static void prv_did_read(SmartstrapAttribute *attr, SmartstrapResult result, const uint8_t *data, size_t length) { char str[50] = " "; if (attr == s_attr_attribute) { APP_LOG(APP_LOG_LEVEL_DEBUG, "did_read(s_attr_attribute, %d, %d)", result, length); if (result == SmartstrapResultOk ) { memcpy(str, data, length); str[length] = '\0'; snprintf(s_text_buffer1, 50, "%s", str); text_layer_set_text(s_attr_text_layer, s_text_buffer1); APP_LOG(APP_LOG_LEVEL_DEBUG, "message: %s)", str); } }else { APP_LOG(APP_LOG_LEVEL_ERROR, "did_read(<%p>, %d)", attr, result); } } static void prv_did_write(SmartstrapAttribute *attr, SmartstrapResult result) { if (attr == s_attr_attribute) { APP_LOG(APP_LOG_LEVEL_DEBUG, "did_write(s_attr_attribute, %d)", result); } else { APP_LOG(APP_LOG_LEVEL_ERROR, "did_write(<%p>, %d)", attr, result); } } static void prv_write_button_action(uint8_t button_value, uint8_t click_type) { SmartstrapResult result; if (!smartstrap_service_is_available(smartstrap_attribute_get_service_id(s_attr_attribute))) { APP_LOG(APP_LOG_LEVEL_DEBUG, "s_attr_attribute is not available"); return; } // get the write buffer uint8_t *buffer = NULL; size_t length = 0; result = smartstrap_attribute_begin_write(s_attr_attribute, &buffer, &length); if (result != SmartstrapResultOk) { APP_LOG(APP_LOG_LEVEL_ERROR, "Write of s_attr_attribute failed with result %d", result); return; } // write the data into the buffer buffer[0] = button_value; buffer[1] = click_type; // send it off result = smartstrap_attribute_end_write(s_attr_attribute, 2, false); if (result != SmartstrapResultOk) { APP_LOG(APP_LOG_LEVEL_ERROR, "Write of s_attr_attribute failed with result %d", result); } } static void prv_availablility_status_changed(SmartstrapServiceId service_id, bool is_available) { APP_LOG(APP_LOG_LEVEL_DEBUG, "Availability for 0x%x is %d", service_id, is_available); prv_update_text(); } static void prv_notified(SmartstrapAttribute *attr) { SmartstrapResult result; if (attr == s_attr_attribute) { APP_LOG(APP_LOG_LEVEL_DEBUG, "notified(s_attr_attribute)"); result = smartstrap_attribute_read(s_attr_attribute); if(result != SmartstrapResultOk) { APP_LOG(APP_LOG_LEVEL_ERROR, "Error reading attribute (<%p>, %d)", attr, result); } } else { APP_LOG(APP_LOG_LEVEL_ERROR, "notified(<%p>)", attr); } } static void up_click_handler(ClickRecognizerRef recognizer, void *context) { prv_write_button_action(0x02, 0x01); } static void select_click_handler(ClickRecognizerRef recognizer, void *context) { prv_write_button_action(0x03, 0x01); } static void down_click_handler(ClickRecognizerRef recognizer, void *context) { prv_write_button_action(0x04, 0x01); } static void click_config_provider(void *context) { window_single_click_subscribe(BUTTON_ID_UP, up_click_handler); window_single_click_subscribe(BUTTON_ID_SELECT, select_click_handler); window_single_click_subscribe(BUTTON_ID_DOWN, down_click_handler); } static void prv_main_window_load(Window *window) { s_status_layer = text_layer_create(GRect(0, 15, 144, 40)); text_layer_set_font(s_status_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28)); prv_update_text(); text_layer_set_text_color(s_status_layer, GColorBlack); text_layer_set_background_color(s_status_layer, GColorClear); text_layer_set_text_alignment(s_status_layer, GTextAlignmentCenter); text_layer_set_overflow_mode(s_status_layer, GTextOverflowModeWordWrap); layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_status_layer)); s_attr_text_layer = text_layer_create(GRect(0, 60, 144, 40)); text_layer_set_font(s_attr_text_layer, fonts_get_system_font(FONT_KEY_GOTHIC_28)); text_layer_set_text(s_attr_text_layer, "-"); text_layer_set_text_color(s_attr_text_layer, GColorBlack); text_layer_set_background_color(s_attr_text_layer, GColorClear); text_layer_set_text_alignment(s_attr_text_layer, GTextAlignmentCenter); text_layer_set_overflow_mode(s_attr_text_layer, GTextOverflowModeWordWrap); layer_add_child(window_get_root_layer(window), text_layer_get_layer(s_attr_text_layer)); } static void prv_main_window_unload(Window *window) { text_layer_destroy(s_status_layer); } static void prv_init(void) { s_main_window = window_create(); window_set_click_config_provider(s_main_window, click_config_provider); window_set_window_handlers(s_main_window, (WindowHandlers) { .load = prv_main_window_load, .unload = prv_main_window_unload }); window_stack_push(s_main_window, true); SmartstrapHandlers handlers = (SmartstrapHandlers) { .availability_did_change = prv_availablility_status_changed, .did_write = prv_did_write, .did_read = prv_did_read, .notified = prv_notified }; smartstrap_subscribe(handlers); smartstrap_set_timeout(50); s_raw_attribute = smartstrap_attribute_create(0, 0, 2000); s_attr_attribute = smartstrap_attribute_create(0x1001, 0x1001, 20); } static void prv_deinit(void) { window_destroy(s_main_window); smartstrap_unsubscribe(); smartstrap_attribute_destroy(s_raw_attribute); smartstrap_attribute_destroy(s_attr_attribute); }

int main(void) { prv_init(); APP_LOG(APP_LOG_LEVEL_DEBUG, "STARTING APP"); if (s_attr_attribute && s_raw_attribute) { app_event_loop(); } APP_LOG(APP_LOG_LEVEL_DEBUG, "ENDING APP"); prv_deinit(); }

4.In the Settings tab deselect Build Aplite since we are building to run on Pebble time watches

5. In Compilation tab select Phone instead of Emulator to program our real watch

6. Run the code by clicking the play button

STEP 4: Demo

What this demo does?

  1. Keep Arduino connected to computer, use a serial monitor to open Arduino's serial port
  2. Attach the hacked charging cable to Pebble watch
  3. Run the Pebble app, the watch will show "connected!"
  4. Send a string ends with '#' (e.g. "123abc#" ) to your Arduino's serial port and watch it displaying on pebble watch.
  5. Click any of the up/down/select button on pebble and watch it showing on the computer screen.

How it works?

Arduino is receiving string end with‘#’ sign from its usb serial port(Serial) and notify the Watch when '#' sign is detected. The Pebble Watch will then read the string from Arduino.
On the other way the Pebble is sending its 3 button click data to the Arduino, Arduino will print this on the usb serial port.

Watch the demo

6 Comments

Very helpful to get me started - thanks!

BTW, this works fine with the Arduino Uno using software serial to talk to the watch.

Heya, any chance you can help out with my attempt to get this working. I have a pro micro 5v and am trying to get this running, but if I tell it that its a teensy 2, arduino does the whole uploading thing, but then pops up the teensyduino box telling me to click the button. There is no button with the pro micro, so I short the reset and ground to emulate this. The light stops flashing (by default it apparently runs blinky ) but then after a pause it starts running the default program again. If i tell it that its an arduino micro, I get: "Teensy did not respond to a USB-based request to automatically reboot.

Please press the PROGRAM MODE BUTTON on your Teensy to upload your sketch.", but again, shorting those pins doesn't help. This is arduino-1.6.7 . Any help would be greatly appreciated, I have been banging my head against it for a few hours now. Thanks!

Oh, sorry, I forgot to mention, I also got the pro micro driver installed, the sparkfun pro micro one, but that gets the program mode popup too. thx

Heya - Sorry for the bother - installed Arduino on a new copy of windows. Set to Leonardo and it uploaded fine, with no hotswapping usb or installation of Teensy. Thanks for your guide - this was really useful. Regards, Rob

Can you tell me the size of the SMD resistor please?

Which size would be the best: 0603? 0805? 1206?

Thank you for you project, really usefull ;)