From f63deabf1067a919ca1fb814a3a5455f3784cc65 Mon Sep 17 00:00:00 2001 From: Scott Bezek Date: Mon, 10 Jul 2023 00:38:28 -0700 Subject: [PATCH] Demo improvements: LED hue config, press state reporting, custom display modes for timeline demo --- firmware/src/display_task.cpp | 160 ++++++++++++------ firmware/src/interface_callbacks.h | 8 + firmware/src/interface_task.cpp | 82 ++++++--- firmware/src/interface_task.h | 8 + firmware/src/motor_task.cpp | 2 - firmware/src/proto_gen/smartknob.pb.h | 28 ++- .../src/serial/serial_protocol_plaintext.cpp | 2 +- .../src/serial/serial_protocol_plaintext.h | 5 +- .../src/serial/serial_protocol_protobuf.cpp | 6 +- .../src/serial/serial_protocol_protobuf.h | 7 +- firmware/src/util.h | 4 + proto/smartknob.proto | 9 + .../example-webserial-basic/src/App.tsx | 2 + .../example-webserial-timeline/src/App.tsx | 50 +++++- .../example-webserial-timeline/src/util.ts | 2 +- .../smartknobjs-webserial/src/index.ts | 21 ++- software/python/proto_gen/smartknob_pb2.py | 30 ++-- software/python/smartknob_io.py | 4 + 18 files changed, 308 insertions(+), 122 deletions(-) create mode 100644 firmware/src/interface_callbacks.h diff --git a/firmware/src/display_task.cpp b/firmware/src/display_task.cpp index 0a78536..7fbe24b 100644 --- a/firmware/src/display_task.cpp +++ b/firmware/src/display_task.cpp @@ -1,6 +1,7 @@ #if SK_DISPLAY #include "display_task.h" #include "semaphore_guard.h" +#include "util.h" #include "font/roboto_light_60.h" @@ -19,6 +20,15 @@ DisplayTask::~DisplayTask() { vSemaphoreDelete(mutex_); } +static void drawPlayButton(TFT_eSprite& spr, int x, int y, int width, int height, uint16_t color) { + spr.fillTriangle( + x, y - height / 2, + x, y + height / 2, + x + width, y, + color + ); +} + void DisplayTask::run() { tft_.begin(); tft_.invertDisplay(1); @@ -56,43 +66,6 @@ void DisplayTask::run() { spr_.fillSprite(TFT_BLACK); int32_t num_positions = state.config.max_position - state.config.min_position + 1; - if (num_positions > 1) { - int32_t height = (state.current_position - state.config.min_position) * TFT_HEIGHT / (state.config.max_position - state.config.min_position); - spr_.fillRect(0, TFT_HEIGHT - height, TFT_WIDTH, height, FILL_COLOR); - } - - spr_.setFreeFont(&Roboto_Light_60); - spr_.drawNumber(state.current_position, TFT_WIDTH / 2, TFT_HEIGHT / 2 - VALUE_OFFSET, 1); - spr_.setFreeFont(&DESCRIPTION_FONT); - int32_t line_y = TFT_HEIGHT / 2 + DESCRIPTION_Y_OFFSET; - char* start = state.config.text; - char* end = start + strlen(state.config.text); - while (start < end) { - char* newline = strchr(start, '\n'); - if (newline == nullptr) { - newline = end; - } - - char buf[sizeof(state.config.text)] = {}; - strncat(buf, start, min(sizeof(buf) - 1, (size_t)(newline - start))); - spr_.drawString(String(buf), TFT_WIDTH / 2, line_y, 1); - start = newline + 1; - line_y += spr_.fontHeight(1); - } - - float left_bound = PI / 2; - - if (num_positions > 0) { - float range_radians = (state.config.max_position - state.config.min_position) * state.config.position_width_radians; - left_bound = PI / 2 + range_radians / 2; - float right_bound = PI / 2 - range_radians / 2; - spr_.drawLine(TFT_WIDTH/2 + RADIUS * cosf(left_bound), TFT_HEIGHT/2 - RADIUS * sinf(left_bound), TFT_WIDTH/2 + (RADIUS - 10) * cosf(left_bound), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(left_bound), TFT_WHITE); - spr_.drawLine(TFT_WIDTH/2 + RADIUS * cosf(right_bound), TFT_HEIGHT/2 - RADIUS * sinf(right_bound), TFT_WIDTH/2 + (RADIUS - 10) * cosf(right_bound), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(right_bound), TFT_WHITE); - } - if (DRAW_ARC) { - spr_.drawCircle(TFT_WIDTH/2, TFT_HEIGHT/2, RADIUS, TFT_DARKGREY); - } - float adjusted_sub_position = state.sub_position_unit * state.config.position_width_radians; if (num_positions > 0) { if (state.current_position == state.config.min_position && state.sub_position_unit < 0) { @@ -102,25 +75,114 @@ void DisplayTask::run() { } } + float left_bound = PI / 2; + float right_bound = 0; + if (num_positions > 0) { + float range_radians = (state.config.max_position - state.config.min_position) * state.config.position_width_radians; + left_bound = PI / 2 + range_radians / 2; + right_bound = PI / 2 - range_radians / 2; + } float raw_angle = left_bound - (state.current_position - state.config.min_position) * state.config.position_width_radians; float adjusted_angle = raw_angle - adjusted_sub_position; + + bool sk_demo_mode = strncmp(state.config.text, "SKDEMO_", 7) == 0; - if (num_positions > 0 && ((state.current_position == state.config.min_position && state.sub_position_unit < 0) || (state.current_position == state.config.max_position && state.sub_position_unit > 0))) { + if (!sk_demo_mode) { + if (num_positions > 1) { + int32_t height = (state.current_position - state.config.min_position) * TFT_HEIGHT / (state.config.max_position - state.config.min_position); + spr_.fillRect(0, TFT_HEIGHT - height, TFT_WIDTH, height, FILL_COLOR); + } - spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(raw_angle), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(raw_angle), 5, DOT_COLOR); - if (raw_angle < adjusted_angle) { - for (float r = raw_angle; r <= adjusted_angle; r += 2 * PI / 180) { - spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(r), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(r), 2, DOT_COLOR); + spr_.setFreeFont(&Roboto_Light_60); + spr_.drawNumber(state.current_position, TFT_WIDTH / 2, TFT_HEIGHT / 2 - VALUE_OFFSET, 1); + spr_.setFreeFont(&DESCRIPTION_FONT); + int32_t line_y = TFT_HEIGHT / 2 + DESCRIPTION_Y_OFFSET; + char* start = state.config.text; + char* end = start + strlen(state.config.text); + while (start < end) { + char* newline = strchr(start, '\n'); + if (newline == nullptr) { + newline = end; + } + + char buf[sizeof(state.config.text)] = {}; + strncat(buf, start, min(sizeof(buf) - 1, (size_t)(newline - start))); + spr_.drawString(String(buf), TFT_WIDTH / 2, line_y, 1); + start = newline + 1; + line_y += spr_.fontHeight(1); + } + + if (num_positions > 0) { + spr_.drawLine(TFT_WIDTH/2 + RADIUS * cosf(left_bound), TFT_HEIGHT/2 - RADIUS * sinf(left_bound), TFT_WIDTH/2 + (RADIUS - 10) * cosf(left_bound), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(left_bound), TFT_WHITE); + spr_.drawLine(TFT_WIDTH/2 + RADIUS * cosf(right_bound), TFT_HEIGHT/2 - RADIUS * sinf(right_bound), TFT_WIDTH/2 + (RADIUS - 10) * cosf(right_bound), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(right_bound), TFT_WHITE); + } + if (DRAW_ARC) { + spr_.drawCircle(TFT_WIDTH/2, TFT_HEIGHT/2, RADIUS, TFT_DARKGREY); + } + + if (num_positions > 0 && ((state.current_position == state.config.min_position && state.sub_position_unit < 0) || (state.current_position == state.config.max_position && state.sub_position_unit > 0))) { + spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(raw_angle), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(raw_angle), 5, DOT_COLOR); + if (raw_angle < adjusted_angle) { + for (float r = raw_angle; r <= adjusted_angle; r += 2 * PI / 180) { + spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(r), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(r), 2, DOT_COLOR); + } + spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(adjusted_angle), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(adjusted_angle), 2, DOT_COLOR); + } else { + for (float r = raw_angle; r >= adjusted_angle; r -= 2 * PI / 180) { + spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(r), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(r), 2, DOT_COLOR); + } + spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(adjusted_angle), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(adjusted_angle), 2, DOT_COLOR); } - spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(adjusted_angle), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(adjusted_angle), 2, DOT_COLOR); } else { - for (float r = raw_angle; r >= adjusted_angle; r -= 2 * PI / 180) { - spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(r), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(r), 2, DOT_COLOR); - } - spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(adjusted_angle), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(adjusted_angle), 2, DOT_COLOR); + spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(adjusted_angle), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(adjusted_angle), 5, DOT_COLOR); } } else { - spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(adjusted_angle), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(adjusted_angle), 5, DOT_COLOR); + if (strncmp(state.config.text, "SKDEMO_Scroll", 13) == 0) { + spr_.fillRect(0, 0, TFT_WIDTH, TFT_HEIGHT, spr_.color565(150, 0, 0)); + spr_.setFreeFont(&Roboto_Thin_24); + spr_.drawString("Scroll", TFT_WIDTH / 2, TFT_HEIGHT / 2, 1); + bool detent = false; + for (uint8_t i = 0; i < state.config.detent_positions_count; i++) { + if (state.config.detent_positions[i] == state.current_position) { + detent = true; + break; + } + } + spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 16) * cosf(adjusted_angle), TFT_HEIGHT/2 - (RADIUS - 16) * sinf(adjusted_angle), detent ? 8 : 5, TFT_WHITE); + } else if (strncmp(state.config.text, "SKDEMO_Frames", 13) == 0) { + int32_t width = (state.current_position - state.config.min_position) * TFT_WIDTH / (state.config.max_position - state.config.min_position); + spr_.fillRect(0, 0, width, TFT_HEIGHT, spr_.color565(0, 150, 0)); + spr_.setFreeFont(&Roboto_Light_60); + spr_.drawNumber(state.current_position, TFT_WIDTH / 2, TFT_HEIGHT / 2, 1); + spr_.setFreeFont(&Roboto_Thin_24); + spr_.drawString("Frame", TFT_WIDTH / 2, TFT_HEIGHT / 2 - DESCRIPTION_Y_OFFSET - VALUE_OFFSET, 1); + } else if (strncmp(state.config.text, "SKDEMO_Speed", 12) == 0) { + spr_.fillRect(0, 0, TFT_WIDTH, TFT_HEIGHT, spr_.color565(0, 0, 150)); + + float normalizedFractional = sgn(state.sub_position_unit) * + CLAMP(lerp(state.sub_position_unit * sgn(state.sub_position_unit), 0.1, 0.9, 0, 1), (float)0, (float)1); + float normalized = state.current_position + normalizedFractional; + float speed = sgn(normalized) * powf(2, fabsf(normalized) - 1); + float roundedSpeed = truncf(speed * 10) / 10; + + spr_.setFreeFont(&Roboto_Thin_24); + if (roundedSpeed == 0) { + spr_.drawString("Paused", TFT_WIDTH / 2, TFT_HEIGHT / 2 + DESCRIPTION_Y_OFFSET + VALUE_OFFSET, 1); + + spr_.fillRect(TFT_WIDTH / 2 + 5, TFT_HEIGHT / 2 - 20, 10, 40, TFT_WHITE); + spr_.fillRect(TFT_WIDTH / 2 - 5 - 10, TFT_HEIGHT / 2 - 20, 10, 40, TFT_WHITE); + } else { + char buf[10]; + snprintf(buf, sizeof(buf), "%0.1fx", roundedSpeed); + spr_.drawString(buf, TFT_WIDTH / 2, TFT_HEIGHT / 2 + DESCRIPTION_Y_OFFSET + VALUE_OFFSET, 1); + + uint16_t x = TFT_WIDTH / 2; + for (uint8_t i = 0; i < max(1, abs(state.current_position)); i++) { + drawPlayButton(spr_, x, TFT_HEIGHT / 2, sgn(roundedSpeed) * 20, 40, TFT_WHITE); + x += sgn(roundedSpeed) * 20; + } + } + } } spr_.pushSprite(0, 0); @@ -129,7 +191,7 @@ void DisplayTask::run() { SemaphoreGuard lock(mutex_); ledcWrite(LEDC_CHANNEL_LCD_BACKLIGHT, brightness_); } - delay(2); + delay(5); } } diff --git a/firmware/src/interface_callbacks.h b/firmware/src/interface_callbacks.h new file mode 100644 index 0000000..cd20981 --- /dev/null +++ b/firmware/src/interface_callbacks.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +#include "proto_gen/smartknob.pb.h" + +typedef std::function ConfigCallback; +typedef std::function MotorCalibrationCallback; diff --git a/firmware/src/interface_task.cpp b/firmware/src/interface_task.cpp index b51a80b..1cb041c 100644 --- a/firmware/src/interface_task.cpp +++ b/firmware/src/interface_task.cpp @@ -40,6 +40,7 @@ static PB_SmartKnobConfig configs[] = { // pb_size_t detent_positions_count; // int32_t detent_positions[5]; // float snap_point_bias; + // int8_t led_hue; { 0, @@ -55,6 +56,7 @@ static PB_SmartKnobConfig configs[] = { 0, {}, 0, + 200, }, { 0, @@ -70,6 +72,7 @@ static PB_SmartKnobConfig configs[] = { 0, {}, 0, + 0, }, { 0, @@ -85,6 +88,7 @@ static PB_SmartKnobConfig configs[] = { 0, {}, 0, + 73, }, { 0, @@ -100,6 +104,7 @@ static PB_SmartKnobConfig configs[] = { 0, {}, 0, + 157, }, { 0, @@ -115,6 +120,7 @@ static PB_SmartKnobConfig configs[] = { 0, {}, 0, + 45, }, { 127, @@ -130,6 +136,7 @@ static PB_SmartKnobConfig configs[] = { 0, {}, 0, + 219, }, { 127, @@ -145,6 +152,7 @@ static PB_SmartKnobConfig configs[] = { 0, {}, 0, + 25, }, { 0, @@ -160,6 +168,7 @@ static PB_SmartKnobConfig configs[] = { 0, {}, 0, + 200, }, { 0, @@ -175,6 +184,7 @@ static PB_SmartKnobConfig configs[] = { 0, {}, 0, + 0, }, { 0, @@ -190,6 +200,7 @@ static PB_SmartKnobConfig configs[] = { 4, {2, 10, 21, 22}, 0, + 73, }, { 0, @@ -204,7 +215,8 @@ static PB_SmartKnobConfig configs[] = { "Return-to-center\nwith detents", 0, {}, - 0.4 + 0.4, + 157, }, }; @@ -213,12 +225,17 @@ InterfaceTask::InterfaceTask(const uint8_t task_core, MotorTask& motor_task, Dis stream_(), motor_task_(motor_task), display_task_(display_task), - plaintext_protocol_(stream_, motor_task_), - proto_protocol_(stream_, motor_task_) { + plaintext_protocol_(stream_, [this] () { + motor_task_.runCalibration(); + }), + proto_protocol_(stream_, [this] (PB_SmartKnobConfig& config) { + applyConfig(config, true); + }) { #if SK_DISPLAY assert(display_task != nullptr); #endif + log_queue_ = xQueueCreate(10, sizeof(std::string *)); assert(log_queue_ != NULL); @@ -257,7 +274,7 @@ void InterfaceTask::run() { } #endif - motor_task_.setConfig(configs[0]); + applyConfig(configs[0], false); motor_task_.addListener(knob_state_queue_); plaintext_protocol_.init([this] () { @@ -291,15 +308,15 @@ void InterfaceTask::run() { }); // Start in legacy protocol mode - SerialProtocol* current_protocol = &plaintext_protocol_; + current_protocol_ = &plaintext_protocol_; - ProtocolChangeCallback protocol_change_callback = [this, ¤t_protocol] (uint8_t protocol) { + ProtocolChangeCallback protocol_change_callback = [this] (uint8_t protocol) { switch (protocol) { case SERIAL_PROTOCOL_LEGACY: - current_protocol = &plaintext_protocol_; + current_protocol_ = &plaintext_protocol_; break; case SERIAL_PROTOCOL_PROTO: - current_protocol = &proto_protocol_; + current_protocol_ = &proto_protocol_; break; default: log("Unknown protocol requested"); @@ -312,16 +329,15 @@ void InterfaceTask::run() { // Interface loop: while (1) { - PB_SmartKnobState state; - if (xQueueReceive(knob_state_queue_, &state, 0) == pdTRUE) { - current_protocol->handleState(state); + if (xQueueReceive(knob_state_queue_, &latest_state_, 0) == pdTRUE) { + publishState(); } - current_protocol->loop(); + current_protocol_->loop(); std::string* log_string; while (xQueueReceive(log_queue_, &log_string, 0) == pdTRUE) { - current_protocol->log(log_string->c_str()); + current_protocol_->log(log_string->c_str()); delete log_string; } @@ -360,7 +376,7 @@ void InterfaceTask::changeConfig(bool next) { snprintf(buf_, sizeof(buf_), "Changing config to %d -- %s", current_config_, configs[current_config_].text); log(buf_); - motor_task_.setConfig(configs[current_config_]); + applyConfig(configs[current_config_], false); } void InterfaceTask::updateHardware() { @@ -380,6 +396,7 @@ void InterfaceTask::updateHardware() { } #endif + static bool pressed; #if SK_STRAIN if (scale.wait_ready_timeout(100)) { strain_reading_ = scale.read(); @@ -396,23 +413,26 @@ void InterfaceTask::updateHardware() { // Ignore readings that are way out of expected bounds if (-1 < press_value_unit && press_value_unit < 2) { - static bool pressed; - static uint8_t press_count; - if (!pressed && press_value_unit > 0.75) { - press_count++; - if (press_count > 2) { + static uint8_t press_readings; + if (!pressed && press_value_unit > 1) { + press_readings++; + if (press_readings > 2) { motor_task_.playHaptic(true); pressed = true; - changeConfig(true); + press_count_++; + publishState(); + if (!remote_controlled_) { + changeConfig(true); + } } - } else if (pressed && press_value_unit < 0.25) { - press_count++; - if (press_count > 2) { + } else if (pressed && press_value_unit < 0.5) { + press_readings++; + if (press_readings > 2) { motor_task_.playHaptic(false); pressed = false; } } else { - press_count = 0; + press_readings = 0; } } } @@ -440,7 +460,7 @@ void InterfaceTask::updateHardware() { #if SK_LEDS for (uint8_t i = 0; i < NUM_LEDS; i++) { - leds[i].setHSV(200 * CLAMP(press_value_unit, (float)0, (float)1), 255, brightness >> 8); + leds[i].setHSV(latest_config_.led_hue, 255 - 180*CLAMP(press_value_unit, (float)0, (float)1) - 75*pressed, brightness >> 8); // Gamma adjustment leds[i].r = dim8_video(leds[i].r); @@ -455,3 +475,15 @@ void InterfaceTask::setConfiguration(Configuration* configuration) { SemaphoreGuard lock(mutex_); configuration_ = configuration; } + +void InterfaceTask::publishState() { + // Apply local state before publishing to serial + latest_state_.press_nonce = press_count_; + current_protocol_->handleState(latest_state_); +} + +void InterfaceTask::applyConfig(PB_SmartKnobConfig& config, bool from_remote) { + remote_controlled_ = from_remote; + latest_config_ = config; + motor_task_.setConfig(config); +} diff --git a/firmware/src/interface_task.h b/firmware/src/interface_task.h index 194a965..24b2bd8 100644 --- a/firmware/src/interface_task.h +++ b/firmware/src/interface_task.h @@ -44,7 +44,13 @@ class InterfaceTask : public Task, public Logger { uint8_t strain_calibration_step_ = 0; int32_t strain_reading_ = 0; + SerialProtocol* current_protocol_ = nullptr; + bool remote_controlled_ = false; int current_config_ = 0; + uint8_t press_count_ = 1; + + PB_SmartKnobState latest_state_ = {}; + PB_SmartKnobConfig latest_config_ = {}; QueueHandle_t log_queue_; QueueHandle_t knob_state_queue_; @@ -53,4 +59,6 @@ class InterfaceTask : public Task, public Logger { void changeConfig(bool next); void updateHardware(); + void publishState(); + void applyConfig(PB_SmartKnobConfig& config, bool from_remote); }; diff --git a/firmware/src/motor_task.cpp b/firmware/src/motor_task.cpp index 5787e30..1b77da0 100644 --- a/firmware/src/motor_task.cpp +++ b/firmware/src/motor_task.cpp @@ -295,8 +295,6 @@ void MotorTask::run() { last_publish = millis(); } - motor.monitor(); - delay(1); } } diff --git a/firmware/src/proto_gen/smartknob.pb.h b/firmware/src/proto_gen/smartknob.pb.h index c40f35e..dbed108 100644 --- a/firmware/src/proto_gen/smartknob.pb.h +++ b/firmware/src/proto_gen/smartknob.pb.h @@ -38,6 +38,10 @@ typedef struct _PB_SmartKnobConfig { pb_size_t detent_positions_count; int32_t detent_positions[5]; float snap_point_bias; + /* * + Hue (0-255) for all 8 ring LEDs, if supported. Note: this will likely be replaced + with more configurability in a future protocol version. */ + int16_t led_hue; } PB_SmartKnobConfig; typedef struct _PB_SmartKnobState { @@ -45,6 +49,8 @@ typedef struct _PB_SmartKnobState { float sub_position_unit; bool has_config; PB_SmartKnobConfig config; + /* * Value that changes each time the knob is pressed */ + uint8_t press_nonce; } PB_SmartKnobState; /* Message FROM the SmartKnob to the host */ @@ -103,8 +109,8 @@ extern "C" { #define PB_ToSmartknob_init_default {0, 0, 0, {PB_RequestState_init_default}} #define PB_Ack_init_default {0} #define PB_Log_init_default {""} -#define PB_SmartKnobState_init_default {0, 0, false, PB_SmartKnobConfig_init_default} -#define PB_SmartKnobConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, "", 0, {0, 0, 0, 0, 0}, 0} +#define PB_SmartKnobState_init_default {0, 0, false, PB_SmartKnobConfig_init_default, 0} +#define PB_SmartKnobConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, "", 0, {0, 0, 0, 0, 0}, 0, 0} #define PB_RequestState_init_default {0} #define PB_PersistentConfiguration_init_default {0, false, PB_MotorCalibration_init_default, false, PB_StrainCalibration_init_default} #define PB_MotorCalibration_init_default {0, 0, 0, 0} @@ -113,8 +119,8 @@ extern "C" { #define PB_ToSmartknob_init_zero {0, 0, 0, {PB_RequestState_init_zero}} #define PB_Ack_init_zero {0} #define PB_Log_init_zero {""} -#define PB_SmartKnobState_init_zero {0, 0, false, PB_SmartKnobConfig_init_zero} -#define PB_SmartKnobConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, "", 0, {0, 0, 0, 0, 0}, 0} +#define PB_SmartKnobState_init_zero {0, 0, false, PB_SmartKnobConfig_init_zero, 0} +#define PB_SmartKnobConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, "", 0, {0, 0, 0, 0, 0}, 0, 0} #define PB_RequestState_init_zero {0} #define PB_PersistentConfiguration_init_zero {0, false, PB_MotorCalibration_init_zero, false, PB_StrainCalibration_init_zero} #define PB_MotorCalibration_init_zero {0, 0, 0, 0} @@ -135,9 +141,11 @@ extern "C" { #define PB_SmartKnobConfig_text_tag 10 #define PB_SmartKnobConfig_detent_positions_tag 11 #define PB_SmartKnobConfig_snap_point_bias_tag 12 +#define PB_SmartKnobConfig_led_hue_tag 13 #define PB_SmartKnobState_current_position_tag 1 #define PB_SmartKnobState_sub_position_unit_tag 2 #define PB_SmartKnobState_config_tag 3 +#define PB_SmartKnobState_press_nonce_tag 4 #define PB_FromSmartKnob_protocol_version_tag 1 #define PB_FromSmartKnob_ack_tag 2 #define PB_FromSmartKnob_log_tag 3 @@ -191,7 +199,8 @@ X(a, STATIC, SINGULAR, STRING, msg, 1) #define PB_SmartKnobState_FIELDLIST(X, a) \ X(a, STATIC, SINGULAR, INT32, current_position, 1) \ X(a, STATIC, SINGULAR, FLOAT, sub_position_unit, 2) \ -X(a, STATIC, OPTIONAL, MESSAGE, config, 3) +X(a, STATIC, OPTIONAL, MESSAGE, config, 3) \ +X(a, STATIC, SINGULAR, UINT32, press_nonce, 4) #define PB_SmartKnobState_CALLBACK NULL #define PB_SmartKnobState_DEFAULT NULL #define PB_SmartKnobState_config_MSGTYPE PB_SmartKnobConfig @@ -208,7 +217,8 @@ X(a, STATIC, SINGULAR, FLOAT, endstop_strength_unit, 8) \ X(a, STATIC, SINGULAR, FLOAT, snap_point, 9) \ X(a, STATIC, SINGULAR, STRING, text, 10) \ X(a, STATIC, REPEATED, INT32, detent_positions, 11) \ -X(a, STATIC, SINGULAR, FLOAT, snap_point_bias, 12) +X(a, STATIC, SINGULAR, FLOAT, snap_point_bias, 12) \ +X(a, STATIC, SINGULAR, INT32, led_hue, 13) #define PB_SmartKnobConfig_CALLBACK NULL #define PB_SmartKnobConfig_DEFAULT NULL @@ -270,10 +280,10 @@ extern const pb_msgdesc_t PB_StrainCalibration_msg; #define PB_MotorCalibration_size 15 #define PB_PersistentConfiguration_size 47 #define PB_RequestState_size 0 -#define PB_SmartKnobConfig_size 173 -#define PB_SmartKnobState_size 192 +#define PB_SmartKnobConfig_size 184 +#define PB_SmartKnobState_size 206 #define PB_StrainCalibration_size 22 -#define PB_ToSmartknob_size 185 +#define PB_ToSmartknob_size 196 #ifdef __cplusplus } /* extern "C" */ diff --git a/firmware/src/serial/serial_protocol_plaintext.cpp b/firmware/src/serial/serial_protocol_plaintext.cpp index ea44d98..2c2e810 100644 --- a/firmware/src/serial/serial_protocol_plaintext.cpp +++ b/firmware/src/serial/serial_protocol_plaintext.cpp @@ -40,7 +40,7 @@ void SerialProtocolPlaintext::loop() { demo_config_change_callback_(); } } else if (b == 'C') { - motor_task_.runCalibration(); + motor_calibration_callback_(); } else if (b == 'S') { if (strain_calibration_callback_) { strain_calibration_callback_(); diff --git a/firmware/src/serial/serial_protocol_plaintext.h b/firmware/src/serial/serial_protocol_plaintext.h index 2f77e53..0748023 100644 --- a/firmware/src/serial/serial_protocol_plaintext.h +++ b/firmware/src/serial/serial_protocol_plaintext.h @@ -2,6 +2,7 @@ #include "../proto_gen/smartknob.pb.h" +#include "interface_callbacks.h" #include "motor_task.h" #include "serial_protocol.h" #include "uart_stream.h" @@ -11,7 +12,7 @@ typedef std::function StrainCalibrationCallback; class SerialProtocolPlaintext : public SerialProtocol { public: - SerialProtocolPlaintext(Stream& stream, MotorTask& motor_task) : SerialProtocol(), stream_(stream), motor_task_(motor_task) {} + SerialProtocolPlaintext(Stream& stream, MotorCalibrationCallback motor_calibration_callback) : SerialProtocol(), stream_(stream), motor_calibration_callback_(motor_calibration_callback) {} ~SerialProtocolPlaintext(){} void log(const char* msg) override; void loop() override; @@ -21,7 +22,7 @@ class SerialProtocolPlaintext : public SerialProtocol { private: Stream& stream_; - MotorTask& motor_task_; + MotorCalibrationCallback motor_calibration_callback_; PB_SmartKnobState latest_state_ = {}; DemoConfigChangeCallback demo_config_change_callback_; StrainCalibrationCallback strain_calibration_callback_; diff --git a/firmware/src/serial/serial_protocol_protobuf.cpp b/firmware/src/serial/serial_protocol_protobuf.cpp index 07f45df..5001821 100644 --- a/firmware/src/serial/serial_protocol_protobuf.cpp +++ b/firmware/src/serial/serial_protocol_protobuf.cpp @@ -14,10 +14,10 @@ static SerialProtocolProtobuf* singleton_for_packet_serial = 0; static const uint16_t MIN_STATE_INTERVAL_MILLIS = 5; static const uint16_t PERIODIC_STATE_INTERVAL_MILLIS = 5000; -SerialProtocolProtobuf::SerialProtocolProtobuf(Stream& stream, MotorTask& motor_task) : +SerialProtocolProtobuf::SerialProtocolProtobuf(Stream& stream, ConfigCallback config_callback) : SerialProtocol(), stream_(stream), - motor_task_(motor_task), + config_callback_(config_callback), packet_serial_() { packet_serial_.setStream(&stream); @@ -125,7 +125,7 @@ void SerialProtocolProtobuf::handlePacket(const uint8_t* buffer, size_t size) { switch (pb_rx_buffer_.which_payload) { case PB_ToSmartknob_smartknob_config_tag: { - motor_task_.setConfig(pb_rx_buffer_.payload.smartknob_config); + config_callback_(pb_rx_buffer_.payload.smartknob_config); break; } default: { diff --git a/firmware/src/serial/serial_protocol_protobuf.h b/firmware/src/serial/serial_protocol_protobuf.h index 82ffe4c..8637cb5 100644 --- a/firmware/src/serial/serial_protocol_protobuf.h +++ b/firmware/src/serial/serial_protocol_protobuf.h @@ -4,21 +4,22 @@ #include "../proto_gen/smartknob.pb.h" +#include "interface_callbacks.h" #include "motor_task.h" #include "serial_protocol.h" #include "uart_stream.h" class SerialProtocolProtobuf : public SerialProtocol { public: - SerialProtocolProtobuf(Stream& stream, MotorTask& motor_task); - ~SerialProtocolProtobuf(){} + SerialProtocolProtobuf(Stream& stream, ConfigCallback config_callback); + ~SerialProtocolProtobuf(){}; void log(const char* msg) override; void loop() override; void handleState(const PB_SmartKnobState& state) override; private: Stream& stream_; - MotorTask& motor_task_; + ConfigCallback config_callback_; PB_FromSmartKnob pb_tx_buffer_; PB_ToSmartknob pb_rx_buffer_; diff --git a/firmware/src/util.h b/firmware/src/util.h index 3aa5916..3918ffb 100644 --- a/firmware/src/util.h +++ b/firmware/src/util.h @@ -9,3 +9,7 @@ template T CLAMP(const T& value, const T& low, const T& high) #define COUNT_OF(A) (sizeof(A) / sizeof(A[0])) float lerp(const float value, const float inMin, const float inMax, const float min, const float max); + +template int sgn(T val) { + return (T(0) < val) - (val < T(0)); +} diff --git a/proto/smartknob.proto b/proto/smartknob.proto index 5ecae8d..a96ee51 100644 --- a/proto/smartknob.proto +++ b/proto/smartknob.proto @@ -41,6 +41,9 @@ message SmartKnobState { int32 current_position = 1; float sub_position_unit = 2; SmartKnobConfig config = 3; + + /** Value that changes each time the knob is pressed */ + uint32 press_nonce = 4 [(nanopb).int_size = IS_8]; } @@ -66,6 +69,12 @@ message SmartKnobConfig { string text = 10 [(nanopb).max_length = 50]; repeated int32 detent_positions = 11 [(nanopb).max_count = 5]; float snap_point_bias = 12; + + /** + * Hue (0-255) for all 8 ring LEDs, if supported. Note: this will likely be replaced + * with more configurability in a future protocol version. + */ + int32 led_hue = 13 [(nanopb).int_size = IS_16]; } message RequestState {} diff --git a/software/js/packages/example-webserial-basic/src/App.tsx b/software/js/packages/example-webserial-basic/src/App.tsx index df04c93..f87b5be 100644 --- a/software/js/packages/example-webserial-basic/src/App.tsx +++ b/software/js/packages/example-webserial-basic/src/App.tsx @@ -21,6 +21,7 @@ const defaultConfig: Config = { text: 'Hello from\nweb serial!', detentPositions: [], snapPointBias: 0, + ledHue: 0, } export type AppProps = object @@ -125,6 +126,7 @@ export const App: React.FC = () => { text: pendingSmartKnobConfig.text, detentPositions: [], snapPointBias: parseFloat(pendingSmartKnobConfig.snapPointBias) || 0, + ledHue: 0, }) }} > diff --git a/software/js/packages/example-webserial-timeline/src/App.tsx b/software/js/packages/example-webserial-timeline/src/App.tsx index 95aaf09..3b727c2 100644 --- a/software/js/packages/example-webserial-timeline/src/App.tsx +++ b/software/js/packages/example-webserial-timeline/src/App.tsx @@ -17,9 +17,9 @@ const MAX_ZOOM = 60 const PIXELS_PER_POSITION = 5 enum Mode { - Scroll = 'Scroll', - Frames = 'Frames', - Speed = 'Speed', + Scroll = 'SKDEMO_Scroll', + Frames = 'SKDEMO_Frames', + Speed = 'SKDEMO_Speed', } type PlaybackState = { @@ -58,6 +58,7 @@ export const App: React.FC = ({info}) => { text: Mode.Scroll, detentPositions: [], snapPointBias: 0, + ledHue: 0, zoomTimelinePixelsPerFrame: 0.1, }) @@ -78,6 +79,7 @@ export const App: React.FC = ({info}) => { smartKnobConfig.text, smartKnobConfig.detentPositions, smartKnobConfig.snapPointBias, + smartKnobConfig.ledHue, ]) const [playbackState, setPlaybackState] = useState({ speed: 0, @@ -139,6 +141,7 @@ export const App: React.FC = ({info}) => { text: Mode.Scroll, detentPositions: findNClosest(Object.keys(detentPositions).map(parseInt), position, 5), snapPointBias: 0, + ledHue: 0, zoomTimelinePixelsPerFrame: curConfig.zoomTimelinePixelsPerFrame, } @@ -158,6 +161,7 @@ export const App: React.FC = ({info}) => { text: Mode.Frames, detentPositions: [], snapPointBias: 0, + ledHue: (120 * 255) / 360, zoomTimelinePixelsPerFrame: curConfig.zoomTimelinePixelsPerFrame, } @@ -177,6 +181,7 @@ export const App: React.FC = ({info}) => { text: Mode.Speed, detentPositions: [], snapPointBias: 0.4, + ledHue: (240 * 255) / 360, zoomTimelinePixelsPerFrame: curConfig.zoomTimelinePixelsPerFrame, } @@ -187,6 +192,21 @@ export const App: React.FC = ({info}) => { }, [detentPositions, info.totalFrames, playbackState], ) + const nextMode = useCallback(() => { + const curMode = smartKnobConfig.text as unknown as Mode + console.log('nextMode', curMode) + if (curMode === Mode.Scroll) { + changeMode(Mode.Frames) + } else if (curMode === Mode.Frames) { + changeMode(Mode.Speed) + } else if (curMode === Mode.Speed) { + changeMode(Mode.Scroll) + } else { + exhaustiveCheck(curMode) + } + }, [smartKnobConfig.text, changeMode]) + + // Initialize to Scroll mode useEffect(() => { changeMode(Mode.Scroll) }, []) @@ -288,6 +308,21 @@ export const App: React.FC = ({info}) => { interfaceState.zoomTimelinePixelsPerFrame, ]) + // Change mode when pressed + const receivedPressNonceRef = useRef(false) + const previousPressNonceRef = useRef(0) + useEffect(() => { + if (previousPressNonceRef.current !== smartKnobState.pressNonce) { + if (!receivedPressNonceRef.current) { + // Ignore first nonce change + receivedPressNonceRef.current = true + } else { + nextMode() + } + } + previousPressNonceRef.current = smartKnobState.pressNonce + }, [smartKnobState.pressNonce, nextMode]) + const refreshInterval = 20 const updateFrame = useCallback(() => { const fps = info.frameRate * playbackState.speed @@ -326,6 +361,9 @@ export const App: React.FC = ({info}) => { const connectToSerial = async () => { try { if (navigator.serial) { + previousPressNonceRef.current = 0 + receivedPressNonceRef.current = false + const serialPort = await navigator.serial.requestPort({ filters: SmartKnobWebSerial.USB_DEVICE_FILTERS, }) @@ -376,9 +414,9 @@ export const App: React.FC = ({info}) => { }} aria-label="Mode" > - {Object.keys(Mode).map((mode) => ( - - {mode} + {Object.entries(Mode).map((mode_entry) => ( + + {mode_entry[0]} ))} diff --git a/software/js/packages/example-webserial-timeline/src/util.ts b/software/js/packages/example-webserial-timeline/src/util.ts index 9bba99d..0b42080 100644 --- a/software/js/packages/example-webserial-timeline/src/util.ts +++ b/software/js/packages/example-webserial-timeline/src/util.ts @@ -1,5 +1,5 @@ export const exhaustiveCheck = (x: never): never => { - throw new Error("Didn't expect to get here", x) + throw new Error(`Unexpected value: ${x}`, x) } export const isSome = (v: T | null | undefined): v is T => { diff --git a/software/js/packages/smartknobjs-webserial/src/index.ts b/software/js/packages/smartknobjs-webserial/src/index.ts index 6868964..1e823cb 100644 --- a/software/js/packages/smartknobjs-webserial/src/index.ts +++ b/software/js/packages/smartknobjs-webserial/src/index.ts @@ -6,12 +6,7 @@ export class SmartKnobWebSerial extends SmartKnobCore { constructor(port: SerialPort, onMessage: MessageCallback) { super(onMessage, (packet: Uint8Array) => { - this.writer?.write(packet).catch((e) => { - console.error('Error writing serial', e) - this.port?.close() - this.port = null - this.portAvailable = false - }) + this.writer?.write(packet).catch(this.onError) }) this.port = port this.portAvailable = true @@ -33,7 +28,9 @@ export class SmartKnobWebSerial extends SmartKnobCore { const reader = this.port.readable.getReader() try { - this.writer = this.port.writable.getWriter() + const writer = this.port.writable.getWriter() + writer.write(Uint8Array.from([0, 0, 0, 0, 0, 0, 0, 0])).catch(this.onError) + this.writer = writer try { // eslint-disable-next-line no-constant-condition while (true) { @@ -47,11 +44,19 @@ export class SmartKnobWebSerial extends SmartKnobCore { } } finally { console.log('Releasing writer') - this.writer?.releaseLock() + writer?.releaseLock() } } finally { console.log('Releasing reader') reader.releaseLock() } } + + private onError(e: unknown) { + console.error('Error writing serial', e) + this.port?.close() + this.port = null + this.portAvailable = false + this.writer = undefined + } } diff --git a/software/python/proto_gen/smartknob_pb2.py b/software/python/proto_gen/smartknob_pb2.py index c8d9944..e0da562 100644 --- a/software/python/proto_gen/smartknob_pb2.py +++ b/software/python/proto_gen/smartknob_pb2.py @@ -15,7 +15,7 @@ _sym_db = _symbol_database.Default() import nanopb_pb2 as nanopb__pb2 -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0fsmartknob.proto\x12\x02PB\x1a\x0cnanopb.proto\"\x9a\x01\n\rFromSmartKnob\x12\x1f\n\x10protocol_version\x18\x01 \x01(\rB\x05\x92?\x02\x38\x08\x12\x16\n\x03\x61\x63k\x18\x02 \x01(\x0b\x32\x07.PB.AckH\x00\x12\x16\n\x03log\x18\x03 \x01(\x0b\x32\x07.PB.LogH\x00\x12-\n\x0fsmartknob_state\x18\x04 \x01(\x0b\x32\x12.PB.SmartKnobStateH\x00\x42\t\n\x07payload\"\xa4\x01\n\x0bToSmartknob\x12\x1f\n\x10protocol_version\x18\x01 \x01(\rB\x05\x92?\x02\x38\x08\x12\r\n\x05nonce\x18\x02 \x01(\r\x12)\n\rrequest_state\x18\x03 \x01(\x0b\x32\x10.PB.RequestStateH\x00\x12/\n\x10smartknob_config\x18\x04 \x01(\x0b\x32\x13.PB.SmartKnobConfigH\x00\x42\t\n\x07payload\"\x14\n\x03\x41\x63k\x12\r\n\x05nonce\x18\x01 \x01(\r\"\x1a\n\x03Log\x12\x13\n\x03msg\x18\x01 \x01(\tB\x06\x92?\x03p\xff\x01\"j\n\x0eSmartKnobState\x12\x18\n\x10\x63urrent_position\x18\x01 \x01(\x05\x12\x19\n\x11sub_position_unit\x18\x02 \x01(\x02\x12#\n\x06\x63onfig\x18\x03 \x01(\x0b\x32\x13.PB.SmartKnobConfig\"\xc9\x02\n\x0fSmartKnobConfig\x12\x10\n\x08position\x18\x01 \x01(\x05\x12\x19\n\x11sub_position_unit\x18\x02 \x01(\x02\x12\x1d\n\x0eposition_nonce\x18\x03 \x01(\rB\x05\x92?\x02\x38\x08\x12\x14\n\x0cmin_position\x18\x04 \x01(\x05\x12\x14\n\x0cmax_position\x18\x05 \x01(\x05\x12\x1e\n\x16position_width_radians\x18\x06 \x01(\x02\x12\x1c\n\x14\x64\x65tent_strength_unit\x18\x07 \x01(\x02\x12\x1d\n\x15\x65ndstop_strength_unit\x18\x08 \x01(\x02\x12\x12\n\nsnap_point\x18\t \x01(\x02\x12\x13\n\x04text\x18\n \x01(\tB\x05\x92?\x02p2\x12\x1f\n\x10\x64\x65tent_positions\x18\x0b \x03(\x05\x42\x05\x92?\x02\x10\x05\x12\x17\n\x0fsnap_point_bias\x18\x0c \x01(\x02\"\x0e\n\x0cRequestState\"v\n\x17PersistentConfiguration\x12\x0f\n\x07version\x18\x01 \x01(\r\x12#\n\x05motor\x18\x02 \x01(\x0b\x32\x14.PB.MotorCalibration\x12%\n\x06strain\x18\x03 \x01(\x0b\x32\x15.PB.StrainCalibration\"p\n\x10MotorCalibration\x12\x12\n\ncalibrated\x18\x01 \x01(\x08\x12\x1e\n\x16zero_electrical_offset\x18\x02 \x01(\x02\x12\x14\n\x0c\x64irection_cw\x18\x03 \x01(\x08\x12\x12\n\npole_pairs\x18\x04 \x01(\r\"<\n\x11StrainCalibration\x12\x12\n\nidle_value\x18\x01 \x01(\x05\x12\x13\n\x0bpress_delta\x18\x02 \x01(\x05\x62\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0fsmartknob.proto\x12\x02PB\x1a\x0cnanopb.proto\"\x9a\x01\n\rFromSmartKnob\x12\x1f\n\x10protocol_version\x18\x01 \x01(\rB\x05\x92?\x02\x38\x08\x12\x16\n\x03\x61\x63k\x18\x02 \x01(\x0b\x32\x07.PB.AckH\x00\x12\x16\n\x03log\x18\x03 \x01(\x0b\x32\x07.PB.LogH\x00\x12-\n\x0fsmartknob_state\x18\x04 \x01(\x0b\x32\x12.PB.SmartKnobStateH\x00\x42\t\n\x07payload\"\xa4\x01\n\x0bToSmartknob\x12\x1f\n\x10protocol_version\x18\x01 \x01(\rB\x05\x92?\x02\x38\x08\x12\r\n\x05nonce\x18\x02 \x01(\r\x12)\n\rrequest_state\x18\x03 \x01(\x0b\x32\x10.PB.RequestStateH\x00\x12/\n\x10smartknob_config\x18\x04 \x01(\x0b\x32\x13.PB.SmartKnobConfigH\x00\x42\t\n\x07payload\"\x14\n\x03\x41\x63k\x12\r\n\x05nonce\x18\x01 \x01(\r\"\x1a\n\x03Log\x12\x13\n\x03msg\x18\x01 \x01(\tB\x06\x92?\x03p\xff\x01\"\x86\x01\n\x0eSmartKnobState\x12\x18\n\x10\x63urrent_position\x18\x01 \x01(\x05\x12\x19\n\x11sub_position_unit\x18\x02 \x01(\x02\x12#\n\x06\x63onfig\x18\x03 \x01(\x0b\x32\x13.PB.SmartKnobConfig\x12\x1a\n\x0bpress_nonce\x18\x04 \x01(\rB\x05\x92?\x02\x38\x08\"\xe1\x02\n\x0fSmartKnobConfig\x12\x10\n\x08position\x18\x01 \x01(\x05\x12\x19\n\x11sub_position_unit\x18\x02 \x01(\x02\x12\x1d\n\x0eposition_nonce\x18\x03 \x01(\rB\x05\x92?\x02\x38\x08\x12\x14\n\x0cmin_position\x18\x04 \x01(\x05\x12\x14\n\x0cmax_position\x18\x05 \x01(\x05\x12\x1e\n\x16position_width_radians\x18\x06 \x01(\x02\x12\x1c\n\x14\x64\x65tent_strength_unit\x18\x07 \x01(\x02\x12\x1d\n\x15\x65ndstop_strength_unit\x18\x08 \x01(\x02\x12\x12\n\nsnap_point\x18\t \x01(\x02\x12\x13\n\x04text\x18\n \x01(\tB\x05\x92?\x02p2\x12\x1f\n\x10\x64\x65tent_positions\x18\x0b \x03(\x05\x42\x05\x92?\x02\x10\x05\x12\x17\n\x0fsnap_point_bias\x18\x0c \x01(\x02\x12\x16\n\x07led_hue\x18\r \x01(\x05\x42\x05\x92?\x02\x38\x10\"\x0e\n\x0cRequestState\"v\n\x17PersistentConfiguration\x12\x0f\n\x07version\x18\x01 \x01(\r\x12#\n\x05motor\x18\x02 \x01(\x0b\x32\x14.PB.MotorCalibration\x12%\n\x06strain\x18\x03 \x01(\x0b\x32\x15.PB.StrainCalibration\"p\n\x10MotorCalibration\x12\x12\n\ncalibrated\x18\x01 \x01(\x08\x12\x1e\n\x16zero_electrical_offset\x18\x02 \x01(\x02\x12\x14\n\x0c\x64irection_cw\x18\x03 \x01(\x08\x12\x12\n\npole_pairs\x18\x04 \x01(\r\"<\n\x11StrainCalibration\x12\x12\n\nidle_value\x18\x01 \x01(\x05\x12\x13\n\x0bpress_delta\x18\x02 \x01(\x05\x62\x06proto3') @@ -108,12 +108,16 @@ if _descriptor._USE_C_DESCRIPTORS == False: _TOSMARTKNOB.fields_by_name['protocol_version']._serialized_options = b'\222?\0028\010' _LOG.fields_by_name['msg']._options = None _LOG.fields_by_name['msg']._serialized_options = b'\222?\003p\377\001' + _SMARTKNOBSTATE.fields_by_name['press_nonce']._options = None + _SMARTKNOBSTATE.fields_by_name['press_nonce']._serialized_options = b'\222?\0028\010' _SMARTKNOBCONFIG.fields_by_name['position_nonce']._options = None _SMARTKNOBCONFIG.fields_by_name['position_nonce']._serialized_options = b'\222?\0028\010' _SMARTKNOBCONFIG.fields_by_name['text']._options = None _SMARTKNOBCONFIG.fields_by_name['text']._serialized_options = b'\222?\002p2' _SMARTKNOBCONFIG.fields_by_name['detent_positions']._options = None _SMARTKNOBCONFIG.fields_by_name['detent_positions']._serialized_options = b'\222?\002\020\005' + _SMARTKNOBCONFIG.fields_by_name['led_hue']._options = None + _SMARTKNOBCONFIG.fields_by_name['led_hue']._serialized_options = b'\222?\0028\020' _FROMSMARTKNOB._serialized_start=38 _FROMSMARTKNOB._serialized_end=192 _TOSMARTKNOB._serialized_start=195 @@ -122,16 +126,16 @@ if _descriptor._USE_C_DESCRIPTORS == False: _ACK._serialized_end=381 _LOG._serialized_start=383 _LOG._serialized_end=409 - _SMARTKNOBSTATE._serialized_start=411 - _SMARTKNOBSTATE._serialized_end=517 - _SMARTKNOBCONFIG._serialized_start=520 - _SMARTKNOBCONFIG._serialized_end=849 - _REQUESTSTATE._serialized_start=851 - _REQUESTSTATE._serialized_end=865 - _PERSISTENTCONFIGURATION._serialized_start=867 - _PERSISTENTCONFIGURATION._serialized_end=985 - _MOTORCALIBRATION._serialized_start=987 - _MOTORCALIBRATION._serialized_end=1099 - _STRAINCALIBRATION._serialized_start=1101 - _STRAINCALIBRATION._serialized_end=1161 + _SMARTKNOBSTATE._serialized_start=412 + _SMARTKNOBSTATE._serialized_end=546 + _SMARTKNOBCONFIG._serialized_start=549 + _SMARTKNOBCONFIG._serialized_end=902 + _REQUESTSTATE._serialized_start=904 + _REQUESTSTATE._serialized_end=918 + _PERSISTENTCONFIGURATION._serialized_start=920 + _PERSISTENTCONFIGURATION._serialized_end=1038 + _MOTORCALIBRATION._serialized_start=1040 + _MOTORCALIBRATION._serialized_end=1152 + _STRAINCALIBRATION._serialized_start=1154 + _STRAINCALIBRATION._serialized_end=1214 # @@protoc_insertion_point(module_scope) diff --git a/software/python/smartknob_io.py b/software/python/smartknob_io.py index 5db4ba3..4e71e21 100644 --- a/software/python/smartknob_io.py +++ b/software/python/smartknob_io.py @@ -34,6 +34,7 @@ sys.path.append(os.path.join(software_root, 'proto_gen')) from proto_gen import smartknob_pb2 SMARTKNOB_BAUD = 921600 +PROTOBUF_PROTOCOL_VERSION = 1 class Smartknob(object): @@ -92,6 +93,8 @@ class Smartknob(object): message = smartknob_pb2.FromSmartKnob() message.ParseFromString(payload) self._logger.debug(message) + if message.protocol_version != PROTOBUF_PROTOCOL_VERSION: + self._logger.warn(f'Invalid protocol version. Expected {PROTOBUF_PROTOCOL_VERSION}, received {message.protocol_version}') payload_type = message.WhichOneof('payload') @@ -145,6 +148,7 @@ class Smartknob(object): nonce = self._next_nonce self._next_nonce += 1 + message.protocol_version = PROTOBUF_PROTOCOL_VERSION message.nonce = nonce payload = bytearray(message.SerializeToString())