Demo improvements (#135)

- LED hue config
- press state reporting
- custom display modes for timeline demo
This commit is contained in:
Scott Bezek 2023-09-10 15:44:00 -07:00 committed by GitHub
parent 3cff3533d7
commit 590ba8850c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 308 additions and 122 deletions

View File

@ -1,6 +1,7 @@
#if SK_DISPLAY #if SK_DISPLAY
#include "display_task.h" #include "display_task.h"
#include "semaphore_guard.h" #include "semaphore_guard.h"
#include "util.h"
#include "font/roboto_light_60.h" #include "font/roboto_light_60.h"
@ -19,6 +20,15 @@ DisplayTask::~DisplayTask() {
vSemaphoreDelete(mutex_); 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() { void DisplayTask::run() {
tft_.begin(); tft_.begin();
tft_.invertDisplay(1); tft_.invertDisplay(1);
@ -56,43 +66,6 @@ void DisplayTask::run() {
spr_.fillSprite(TFT_BLACK); spr_.fillSprite(TFT_BLACK);
int32_t num_positions = state.config.max_position - state.config.min_position + 1; 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; float adjusted_sub_position = state.sub_position_unit * state.config.position_width_radians;
if (num_positions > 0) { if (num_positions > 0) {
if (state.current_position == state.config.min_position && state.sub_position_unit < 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 raw_angle = left_bound - (state.current_position - state.config.min_position) * state.config.position_width_radians;
float adjusted_angle = raw_angle - adjusted_sub_position; 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); spr_.setFreeFont(&Roboto_Light_60);
if (raw_angle < adjusted_angle) { spr_.drawNumber(state.current_position, TFT_WIDTH / 2, TFT_HEIGHT / 2 - VALUE_OFFSET, 1);
for (float r = raw_angle; r <= adjusted_angle; r += 2 * PI / 180) { spr_.setFreeFont(&DESCRIPTION_FONT);
spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(r), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(r), 2, DOT_COLOR); 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 { } else {
for (float r = raw_angle; r >= adjusted_angle; r -= 2 * PI / 180) { spr_.fillCircle(TFT_WIDTH/2 + (RADIUS - 10) * cosf(adjusted_angle), TFT_HEIGHT/2 - (RADIUS - 10) * sinf(adjusted_angle), 5, DOT_COLOR);
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 { } 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); spr_.pushSprite(0, 0);
@ -129,7 +191,7 @@ void DisplayTask::run() {
SemaphoreGuard lock(mutex_); SemaphoreGuard lock(mutex_);
ledcWrite(LEDC_CHANNEL_LCD_BACKLIGHT, brightness_); ledcWrite(LEDC_CHANNEL_LCD_BACKLIGHT, brightness_);
} }
delay(2); delay(5);
} }
} }

View File

@ -0,0 +1,8 @@
#pragma once
#include <functional>
#include "proto_gen/smartknob.pb.h"
typedef std::function<void(PB_SmartKnobConfig&)> ConfigCallback;
typedef std::function<void(void)> MotorCalibrationCallback;

View File

@ -40,6 +40,7 @@ static PB_SmartKnobConfig configs[] = {
// pb_size_t detent_positions_count; // pb_size_t detent_positions_count;
// int32_t detent_positions[5]; // int32_t detent_positions[5];
// float snap_point_bias; // float snap_point_bias;
// int8_t led_hue;
{ {
0, 0,
@ -55,6 +56,7 @@ static PB_SmartKnobConfig configs[] = {
0, 0,
{}, {},
0, 0,
200,
}, },
{ {
0, 0,
@ -70,6 +72,7 @@ static PB_SmartKnobConfig configs[] = {
0, 0,
{}, {},
0, 0,
0,
}, },
{ {
0, 0,
@ -85,6 +88,7 @@ static PB_SmartKnobConfig configs[] = {
0, 0,
{}, {},
0, 0,
73,
}, },
{ {
0, 0,
@ -100,6 +104,7 @@ static PB_SmartKnobConfig configs[] = {
0, 0,
{}, {},
0, 0,
157,
}, },
{ {
0, 0,
@ -115,6 +120,7 @@ static PB_SmartKnobConfig configs[] = {
0, 0,
{}, {},
0, 0,
45,
}, },
{ {
127, 127,
@ -130,6 +136,7 @@ static PB_SmartKnobConfig configs[] = {
0, 0,
{}, {},
0, 0,
219,
}, },
{ {
127, 127,
@ -145,6 +152,7 @@ static PB_SmartKnobConfig configs[] = {
0, 0,
{}, {},
0, 0,
25,
}, },
{ {
0, 0,
@ -160,6 +168,7 @@ static PB_SmartKnobConfig configs[] = {
0, 0,
{}, {},
0, 0,
200,
}, },
{ {
0, 0,
@ -175,6 +184,7 @@ static PB_SmartKnobConfig configs[] = {
0, 0,
{}, {},
0, 0,
0,
}, },
{ {
0, 0,
@ -190,6 +200,7 @@ static PB_SmartKnobConfig configs[] = {
4, 4,
{2, 10, 21, 22}, {2, 10, 21, 22},
0, 0,
73,
}, },
{ {
0, 0,
@ -204,7 +215,8 @@ static PB_SmartKnobConfig configs[] = {
"Return-to-center\nwith detents", "Return-to-center\nwith detents",
0, 0,
{}, {},
0.4 0.4,
157,
}, },
}; };
@ -213,12 +225,17 @@ InterfaceTask::InterfaceTask(const uint8_t task_core, MotorTask& motor_task, Dis
stream_(), stream_(),
motor_task_(motor_task), motor_task_(motor_task),
display_task_(display_task), display_task_(display_task),
plaintext_protocol_(stream_, motor_task_), plaintext_protocol_(stream_, [this] () {
proto_protocol_(stream_, motor_task_) { motor_task_.runCalibration();
}),
proto_protocol_(stream_, [this] (PB_SmartKnobConfig& config) {
applyConfig(config, true);
}) {
#if SK_DISPLAY #if SK_DISPLAY
assert(display_task != nullptr); assert(display_task != nullptr);
#endif #endif
log_queue_ = xQueueCreate(10, sizeof(std::string *)); log_queue_ = xQueueCreate(10, sizeof(std::string *));
assert(log_queue_ != NULL); assert(log_queue_ != NULL);
@ -257,7 +274,7 @@ void InterfaceTask::run() {
} }
#endif #endif
motor_task_.setConfig(configs[0]); applyConfig(configs[0], false);
motor_task_.addListener(knob_state_queue_); motor_task_.addListener(knob_state_queue_);
plaintext_protocol_.init([this] () { plaintext_protocol_.init([this] () {
@ -291,15 +308,15 @@ void InterfaceTask::run() {
}); });
// Start in legacy protocol mode // Start in legacy protocol mode
SerialProtocol* current_protocol = &plaintext_protocol_; current_protocol_ = &plaintext_protocol_;
ProtocolChangeCallback protocol_change_callback = [this, &current_protocol] (uint8_t protocol) { ProtocolChangeCallback protocol_change_callback = [this] (uint8_t protocol) {
switch (protocol) { switch (protocol) {
case SERIAL_PROTOCOL_LEGACY: case SERIAL_PROTOCOL_LEGACY:
current_protocol = &plaintext_protocol_; current_protocol_ = &plaintext_protocol_;
break; break;
case SERIAL_PROTOCOL_PROTO: case SERIAL_PROTOCOL_PROTO:
current_protocol = &proto_protocol_; current_protocol_ = &proto_protocol_;
break; break;
default: default:
log("Unknown protocol requested"); log("Unknown protocol requested");
@ -312,16 +329,15 @@ void InterfaceTask::run() {
// Interface loop: // Interface loop:
while (1) { while (1) {
PB_SmartKnobState state; if (xQueueReceive(knob_state_queue_, &latest_state_, 0) == pdTRUE) {
if (xQueueReceive(knob_state_queue_, &state, 0) == pdTRUE) { publishState();
current_protocol->handleState(state);
} }
current_protocol->loop(); current_protocol_->loop();
std::string* log_string; std::string* log_string;
while (xQueueReceive(log_queue_, &log_string, 0) == pdTRUE) { 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; 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); snprintf(buf_, sizeof(buf_), "Changing config to %d -- %s", current_config_, configs[current_config_].text);
log(buf_); log(buf_);
motor_task_.setConfig(configs[current_config_]); applyConfig(configs[current_config_], false);
} }
void InterfaceTask::updateHardware() { void InterfaceTask::updateHardware() {
@ -380,6 +396,7 @@ void InterfaceTask::updateHardware() {
} }
#endif #endif
static bool pressed;
#if SK_STRAIN #if SK_STRAIN
if (scale.wait_ready_timeout(100)) { if (scale.wait_ready_timeout(100)) {
strain_reading_ = scale.read(); strain_reading_ = scale.read();
@ -396,23 +413,26 @@ void InterfaceTask::updateHardware() {
// Ignore readings that are way out of expected bounds // Ignore readings that are way out of expected bounds
if (-1 < press_value_unit && press_value_unit < 2) { if (-1 < press_value_unit && press_value_unit < 2) {
static bool pressed; static uint8_t press_readings;
static uint8_t press_count; if (!pressed && press_value_unit > 1) {
if (!pressed && press_value_unit > 0.75) { press_readings++;
press_count++; if (press_readings > 2) {
if (press_count > 2) {
motor_task_.playHaptic(true); motor_task_.playHaptic(true);
pressed = true; pressed = true;
changeConfig(true); press_count_++;
publishState();
if (!remote_controlled_) {
changeConfig(true);
}
} }
} else if (pressed && press_value_unit < 0.25) { } else if (pressed && press_value_unit < 0.5) {
press_count++; press_readings++;
if (press_count > 2) { if (press_readings > 2) {
motor_task_.playHaptic(false); motor_task_.playHaptic(false);
pressed = false; pressed = false;
} }
} else { } else {
press_count = 0; press_readings = 0;
} }
} }
} }
@ -440,7 +460,7 @@ void InterfaceTask::updateHardware() {
#if SK_LEDS #if SK_LEDS
for (uint8_t i = 0; i < NUM_LEDS; i++) { 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 // Gamma adjustment
leds[i].r = dim8_video(leds[i].r); leds[i].r = dim8_video(leds[i].r);
@ -455,3 +475,15 @@ void InterfaceTask::setConfiguration(Configuration* configuration) {
SemaphoreGuard lock(mutex_); SemaphoreGuard lock(mutex_);
configuration_ = configuration; 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);
}

View File

@ -44,7 +44,13 @@ class InterfaceTask : public Task<InterfaceTask>, public Logger {
uint8_t strain_calibration_step_ = 0; uint8_t strain_calibration_step_ = 0;
int32_t strain_reading_ = 0; int32_t strain_reading_ = 0;
SerialProtocol* current_protocol_ = nullptr;
bool remote_controlled_ = false;
int current_config_ = 0; int current_config_ = 0;
uint8_t press_count_ = 1;
PB_SmartKnobState latest_state_ = {};
PB_SmartKnobConfig latest_config_ = {};
QueueHandle_t log_queue_; QueueHandle_t log_queue_;
QueueHandle_t knob_state_queue_; QueueHandle_t knob_state_queue_;
@ -53,4 +59,6 @@ class InterfaceTask : public Task<InterfaceTask>, public Logger {
void changeConfig(bool next); void changeConfig(bool next);
void updateHardware(); void updateHardware();
void publishState();
void applyConfig(PB_SmartKnobConfig& config, bool from_remote);
}; };

View File

@ -295,8 +295,6 @@ void MotorTask::run() {
last_publish = millis(); last_publish = millis();
} }
motor.monitor();
delay(1); delay(1);
} }
} }

View File

@ -38,6 +38,10 @@ typedef struct _PB_SmartKnobConfig {
pb_size_t detent_positions_count; pb_size_t detent_positions_count;
int32_t detent_positions[5]; int32_t detent_positions[5];
float snap_point_bias; 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; } PB_SmartKnobConfig;
typedef struct _PB_SmartKnobState { typedef struct _PB_SmartKnobState {
@ -45,6 +49,8 @@ typedef struct _PB_SmartKnobState {
float sub_position_unit; float sub_position_unit;
bool has_config; bool has_config;
PB_SmartKnobConfig config; PB_SmartKnobConfig config;
/* * Value that changes each time the knob is pressed */
uint8_t press_nonce;
} PB_SmartKnobState; } PB_SmartKnobState;
/* Message FROM the SmartKnob to the host */ /* 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_ToSmartknob_init_default {0, 0, 0, {PB_RequestState_init_default}}
#define PB_Ack_init_default {0} #define PB_Ack_init_default {0}
#define PB_Log_init_default {""} #define PB_Log_init_default {""}
#define PB_SmartKnobState_init_default {0, 0, false, PB_SmartKnobConfig_init_default} #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} #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_RequestState_init_default {0}
#define PB_PersistentConfiguration_init_default {0, false, PB_MotorCalibration_init_default, false, PB_StrainCalibration_init_default} #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} #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_ToSmartknob_init_zero {0, 0, 0, {PB_RequestState_init_zero}}
#define PB_Ack_init_zero {0} #define PB_Ack_init_zero {0}
#define PB_Log_init_zero {""} #define PB_Log_init_zero {""}
#define PB_SmartKnobState_init_zero {0, 0, false, PB_SmartKnobConfig_init_zero} #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} #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_RequestState_init_zero {0}
#define PB_PersistentConfiguration_init_zero {0, false, PB_MotorCalibration_init_zero, false, PB_StrainCalibration_init_zero} #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} #define PB_MotorCalibration_init_zero {0, 0, 0, 0}
@ -135,9 +141,11 @@ extern "C" {
#define PB_SmartKnobConfig_text_tag 10 #define PB_SmartKnobConfig_text_tag 10
#define PB_SmartKnobConfig_detent_positions_tag 11 #define PB_SmartKnobConfig_detent_positions_tag 11
#define PB_SmartKnobConfig_snap_point_bias_tag 12 #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_current_position_tag 1
#define PB_SmartKnobState_sub_position_unit_tag 2 #define PB_SmartKnobState_sub_position_unit_tag 2
#define PB_SmartKnobState_config_tag 3 #define PB_SmartKnobState_config_tag 3
#define PB_SmartKnobState_press_nonce_tag 4
#define PB_FromSmartKnob_protocol_version_tag 1 #define PB_FromSmartKnob_protocol_version_tag 1
#define PB_FromSmartKnob_ack_tag 2 #define PB_FromSmartKnob_ack_tag 2
#define PB_FromSmartKnob_log_tag 3 #define PB_FromSmartKnob_log_tag 3
@ -191,7 +199,8 @@ X(a, STATIC, SINGULAR, STRING, msg, 1)
#define PB_SmartKnobState_FIELDLIST(X, a) \ #define PB_SmartKnobState_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, INT32, current_position, 1) \ X(a, STATIC, SINGULAR, INT32, current_position, 1) \
X(a, STATIC, SINGULAR, FLOAT, sub_position_unit, 2) \ 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_CALLBACK NULL
#define PB_SmartKnobState_DEFAULT NULL #define PB_SmartKnobState_DEFAULT NULL
#define PB_SmartKnobState_config_MSGTYPE PB_SmartKnobConfig #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, FLOAT, snap_point, 9) \
X(a, STATIC, SINGULAR, STRING, text, 10) \ X(a, STATIC, SINGULAR, STRING, text, 10) \
X(a, STATIC, REPEATED, INT32, detent_positions, 11) \ 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_CALLBACK NULL
#define PB_SmartKnobConfig_DEFAULT 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_MotorCalibration_size 15
#define PB_PersistentConfiguration_size 47 #define PB_PersistentConfiguration_size 47
#define PB_RequestState_size 0 #define PB_RequestState_size 0
#define PB_SmartKnobConfig_size 173 #define PB_SmartKnobConfig_size 184
#define PB_SmartKnobState_size 192 #define PB_SmartKnobState_size 206
#define PB_StrainCalibration_size 22 #define PB_StrainCalibration_size 22
#define PB_ToSmartknob_size 185 #define PB_ToSmartknob_size 196
#ifdef __cplusplus #ifdef __cplusplus
} /* extern "C" */ } /* extern "C" */

View File

@ -40,7 +40,7 @@ void SerialProtocolPlaintext::loop() {
demo_config_change_callback_(); demo_config_change_callback_();
} }
} else if (b == 'C') { } else if (b == 'C') {
motor_task_.runCalibration(); motor_calibration_callback_();
} else if (b == 'S') { } else if (b == 'S') {
if (strain_calibration_callback_) { if (strain_calibration_callback_) {
strain_calibration_callback_(); strain_calibration_callback_();

View File

@ -2,6 +2,7 @@
#include "../proto_gen/smartknob.pb.h" #include "../proto_gen/smartknob.pb.h"
#include "interface_callbacks.h"
#include "motor_task.h" #include "motor_task.h"
#include "serial_protocol.h" #include "serial_protocol.h"
#include "uart_stream.h" #include "uart_stream.h"
@ -11,7 +12,7 @@ typedef std::function<void(void)> StrainCalibrationCallback;
class SerialProtocolPlaintext : public SerialProtocol { class SerialProtocolPlaintext : public SerialProtocol {
public: 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(){} ~SerialProtocolPlaintext(){}
void log(const char* msg) override; void log(const char* msg) override;
void loop() override; void loop() override;
@ -21,7 +22,7 @@ class SerialProtocolPlaintext : public SerialProtocol {
private: private:
Stream& stream_; Stream& stream_;
MotorTask& motor_task_; MotorCalibrationCallback motor_calibration_callback_;
PB_SmartKnobState latest_state_ = {}; PB_SmartKnobState latest_state_ = {};
DemoConfigChangeCallback demo_config_change_callback_; DemoConfigChangeCallback demo_config_change_callback_;
StrainCalibrationCallback strain_calibration_callback_; StrainCalibrationCallback strain_calibration_callback_;

View File

@ -14,10 +14,10 @@ static SerialProtocolProtobuf* singleton_for_packet_serial = 0;
static const uint16_t MIN_STATE_INTERVAL_MILLIS = 5; static const uint16_t MIN_STATE_INTERVAL_MILLIS = 5;
static const uint16_t PERIODIC_STATE_INTERVAL_MILLIS = 5000; static const uint16_t PERIODIC_STATE_INTERVAL_MILLIS = 5000;
SerialProtocolProtobuf::SerialProtocolProtobuf(Stream& stream, MotorTask& motor_task) : SerialProtocolProtobuf::SerialProtocolProtobuf(Stream& stream, ConfigCallback config_callback) :
SerialProtocol(), SerialProtocol(),
stream_(stream), stream_(stream),
motor_task_(motor_task), config_callback_(config_callback),
packet_serial_() { packet_serial_() {
packet_serial_.setStream(&stream); packet_serial_.setStream(&stream);
@ -125,7 +125,7 @@ void SerialProtocolProtobuf::handlePacket(const uint8_t* buffer, size_t size) {
switch (pb_rx_buffer_.which_payload) { switch (pb_rx_buffer_.which_payload) {
case PB_ToSmartknob_smartknob_config_tag: { case PB_ToSmartknob_smartknob_config_tag: {
motor_task_.setConfig(pb_rx_buffer_.payload.smartknob_config); config_callback_(pb_rx_buffer_.payload.smartknob_config);
break; break;
} }
default: { default: {

View File

@ -4,21 +4,22 @@
#include "../proto_gen/smartknob.pb.h" #include "../proto_gen/smartknob.pb.h"
#include "interface_callbacks.h"
#include "motor_task.h" #include "motor_task.h"
#include "serial_protocol.h" #include "serial_protocol.h"
#include "uart_stream.h" #include "uart_stream.h"
class SerialProtocolProtobuf : public SerialProtocol { class SerialProtocolProtobuf : public SerialProtocol {
public: public:
SerialProtocolProtobuf(Stream& stream, MotorTask& motor_task); SerialProtocolProtobuf(Stream& stream, ConfigCallback config_callback);
~SerialProtocolProtobuf(){} ~SerialProtocolProtobuf(){};
void log(const char* msg) override; void log(const char* msg) override;
void loop() override; void loop() override;
void handleState(const PB_SmartKnobState& state) override; void handleState(const PB_SmartKnobState& state) override;
private: private:
Stream& stream_; Stream& stream_;
MotorTask& motor_task_; ConfigCallback config_callback_;
PB_FromSmartKnob pb_tx_buffer_; PB_FromSmartKnob pb_tx_buffer_;
PB_ToSmartknob pb_rx_buffer_; PB_ToSmartknob pb_rx_buffer_;

View File

@ -9,3 +9,7 @@ template <typename T> T CLAMP(const T& value, const T& low, const T& high)
#define COUNT_OF(A) (sizeof(A) / sizeof(A[0])) #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); float lerp(const float value, const float inMin, const float inMax, const float min, const float max);
template <typename T> int sgn(T val) {
return (T(0) < val) - (val < T(0));
}

View File

@ -41,6 +41,9 @@ message SmartKnobState {
int32 current_position = 1; int32 current_position = 1;
float sub_position_unit = 2; float sub_position_unit = 2;
SmartKnobConfig config = 3; 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]; string text = 10 [(nanopb).max_length = 50];
repeated int32 detent_positions = 11 [(nanopb).max_count = 5]; repeated int32 detent_positions = 11 [(nanopb).max_count = 5];
float snap_point_bias = 12; 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 {} message RequestState {}

View File

@ -21,6 +21,7 @@ const defaultConfig: Config = {
text: 'Hello from\nweb serial!', text: 'Hello from\nweb serial!',
detentPositions: [], detentPositions: [],
snapPointBias: 0, snapPointBias: 0,
ledHue: 0,
} }
export type AppProps = object export type AppProps = object
@ -125,6 +126,7 @@ export const App: React.FC<AppProps> = () => {
text: pendingSmartKnobConfig.text, text: pendingSmartKnobConfig.text,
detentPositions: [], detentPositions: [],
snapPointBias: parseFloat(pendingSmartKnobConfig.snapPointBias) || 0, snapPointBias: parseFloat(pendingSmartKnobConfig.snapPointBias) || 0,
ledHue: 0,
}) })
}} }}
> >

View File

@ -17,9 +17,9 @@ const MAX_ZOOM = 60
const PIXELS_PER_POSITION = 5 const PIXELS_PER_POSITION = 5
enum Mode { enum Mode {
Scroll = 'Scroll', Scroll = 'SKDEMO_Scroll',
Frames = 'Frames', Frames = 'SKDEMO_Frames',
Speed = 'Speed', Speed = 'SKDEMO_Speed',
} }
type PlaybackState = { type PlaybackState = {
@ -58,6 +58,7 @@ export const App: React.FC<AppProps> = ({info}) => {
text: Mode.Scroll, text: Mode.Scroll,
detentPositions: [], detentPositions: [],
snapPointBias: 0, snapPointBias: 0,
ledHue: 0,
zoomTimelinePixelsPerFrame: 0.1, zoomTimelinePixelsPerFrame: 0.1,
}) })
@ -78,6 +79,7 @@ export const App: React.FC<AppProps> = ({info}) => {
smartKnobConfig.text, smartKnobConfig.text,
smartKnobConfig.detentPositions, smartKnobConfig.detentPositions,
smartKnobConfig.snapPointBias, smartKnobConfig.snapPointBias,
smartKnobConfig.ledHue,
]) ])
const [playbackState, setPlaybackState] = useState<PlaybackState>({ const [playbackState, setPlaybackState] = useState<PlaybackState>({
speed: 0, speed: 0,
@ -139,6 +141,7 @@ export const App: React.FC<AppProps> = ({info}) => {
text: Mode.Scroll, text: Mode.Scroll,
detentPositions: findNClosest(Object.keys(detentPositions).map(parseInt), position, 5), detentPositions: findNClosest(Object.keys(detentPositions).map(parseInt), position, 5),
snapPointBias: 0, snapPointBias: 0,
ledHue: 0,
zoomTimelinePixelsPerFrame: curConfig.zoomTimelinePixelsPerFrame, zoomTimelinePixelsPerFrame: curConfig.zoomTimelinePixelsPerFrame,
} }
@ -158,6 +161,7 @@ export const App: React.FC<AppProps> = ({info}) => {
text: Mode.Frames, text: Mode.Frames,
detentPositions: [], detentPositions: [],
snapPointBias: 0, snapPointBias: 0,
ledHue: (120 * 255) / 360,
zoomTimelinePixelsPerFrame: curConfig.zoomTimelinePixelsPerFrame, zoomTimelinePixelsPerFrame: curConfig.zoomTimelinePixelsPerFrame,
} }
@ -177,6 +181,7 @@ export const App: React.FC<AppProps> = ({info}) => {
text: Mode.Speed, text: Mode.Speed,
detentPositions: [], detentPositions: [],
snapPointBias: 0.4, snapPointBias: 0.4,
ledHue: (240 * 255) / 360,
zoomTimelinePixelsPerFrame: curConfig.zoomTimelinePixelsPerFrame, zoomTimelinePixelsPerFrame: curConfig.zoomTimelinePixelsPerFrame,
} }
@ -187,6 +192,21 @@ export const App: React.FC<AppProps> = ({info}) => {
}, },
[detentPositions, info.totalFrames, playbackState], [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(() => { useEffect(() => {
changeMode(Mode.Scroll) changeMode(Mode.Scroll)
}, []) }, [])
@ -288,6 +308,21 @@ export const App: React.FC<AppProps> = ({info}) => {
interfaceState.zoomTimelinePixelsPerFrame, interfaceState.zoomTimelinePixelsPerFrame,
]) ])
// Change mode when pressed
const receivedPressNonceRef = useRef<boolean>(false)
const previousPressNonceRef = useRef<number>(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 refreshInterval = 20
const updateFrame = useCallback(() => { const updateFrame = useCallback(() => {
const fps = info.frameRate * playbackState.speed const fps = info.frameRate * playbackState.speed
@ -326,6 +361,9 @@ export const App: React.FC<AppProps> = ({info}) => {
const connectToSerial = async () => { const connectToSerial = async () => {
try { try {
if (navigator.serial) { if (navigator.serial) {
previousPressNonceRef.current = 0
receivedPressNonceRef.current = false
const serialPort = await navigator.serial.requestPort({ const serialPort = await navigator.serial.requestPort({
filters: SmartKnobWebSerial.USB_DEVICE_FILTERS, filters: SmartKnobWebSerial.USB_DEVICE_FILTERS,
}) })
@ -376,9 +414,9 @@ export const App: React.FC<AppProps> = ({info}) => {
}} }}
aria-label="Mode" aria-label="Mode"
> >
{Object.keys(Mode).map((mode) => ( {Object.entries(Mode).map((mode_entry) => (
<ToggleButton value={mode} key={mode}> <ToggleButton value={mode_entry[1]} key={mode_entry[1]}>
{mode} {mode_entry[0]}
</ToggleButton> </ToggleButton>
))} ))}
</ToggleButtonGroup> </ToggleButtonGroup>

View File

@ -1,5 +1,5 @@
export const exhaustiveCheck = (x: never): never => { 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 = <T>(v: T | null | undefined): v is T => { export const isSome = <T>(v: T | null | undefined): v is T => {

View File

@ -6,12 +6,7 @@ export class SmartKnobWebSerial extends SmartKnobCore {
constructor(port: SerialPort, onMessage: MessageCallback) { constructor(port: SerialPort, onMessage: MessageCallback) {
super(onMessage, (packet: Uint8Array) => { super(onMessage, (packet: Uint8Array) => {
this.writer?.write(packet).catch((e) => { this.writer?.write(packet).catch(this.onError)
console.error('Error writing serial', e)
this.port?.close()
this.port = null
this.portAvailable = false
})
}) })
this.port = port this.port = port
this.portAvailable = true this.portAvailable = true
@ -33,7 +28,9 @@ export class SmartKnobWebSerial extends SmartKnobCore {
const reader = this.port.readable.getReader() const reader = this.port.readable.getReader()
try { 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 { try {
// eslint-disable-next-line no-constant-condition // eslint-disable-next-line no-constant-condition
while (true) { while (true) {
@ -47,11 +44,19 @@ export class SmartKnobWebSerial extends SmartKnobCore {
} }
} finally { } finally {
console.log('Releasing writer') console.log('Releasing writer')
this.writer?.releaseLock() writer?.releaseLock()
} }
} finally { } finally {
console.log('Releasing reader') console.log('Releasing reader')
reader.releaseLock() reader.releaseLock()
} }
} }
private onError(e: unknown) {
console.error('Error writing serial', e)
this.port?.close()
this.port = null
this.portAvailable = false
this.writer = undefined
}
} }

View File

@ -15,7 +15,7 @@ _sym_db = _symbol_database.Default()
import nanopb_pb2 as nanopb__pb2 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' _TOSMARTKNOB.fields_by_name['protocol_version']._serialized_options = b'\222?\0028\010'
_LOG.fields_by_name['msg']._options = None _LOG.fields_by_name['msg']._options = None
_LOG.fields_by_name['msg']._serialized_options = b'\222?\003p\377\001' _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']._options = None
_SMARTKNOBCONFIG.fields_by_name['position_nonce']._serialized_options = b'\222?\0028\010' _SMARTKNOBCONFIG.fields_by_name['position_nonce']._serialized_options = b'\222?\0028\010'
_SMARTKNOBCONFIG.fields_by_name['text']._options = None _SMARTKNOBCONFIG.fields_by_name['text']._options = None
_SMARTKNOBCONFIG.fields_by_name['text']._serialized_options = b'\222?\002p2' _SMARTKNOBCONFIG.fields_by_name['text']._serialized_options = b'\222?\002p2'
_SMARTKNOBCONFIG.fields_by_name['detent_positions']._options = None _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['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_start=38
_FROMSMARTKNOB._serialized_end=192 _FROMSMARTKNOB._serialized_end=192
_TOSMARTKNOB._serialized_start=195 _TOSMARTKNOB._serialized_start=195
@ -122,16 +126,16 @@ if _descriptor._USE_C_DESCRIPTORS == False:
_ACK._serialized_end=381 _ACK._serialized_end=381
_LOG._serialized_start=383 _LOG._serialized_start=383
_LOG._serialized_end=409 _LOG._serialized_end=409
_SMARTKNOBSTATE._serialized_start=411 _SMARTKNOBSTATE._serialized_start=412
_SMARTKNOBSTATE._serialized_end=517 _SMARTKNOBSTATE._serialized_end=546
_SMARTKNOBCONFIG._serialized_start=520 _SMARTKNOBCONFIG._serialized_start=549
_SMARTKNOBCONFIG._serialized_end=849 _SMARTKNOBCONFIG._serialized_end=902
_REQUESTSTATE._serialized_start=851 _REQUESTSTATE._serialized_start=904
_REQUESTSTATE._serialized_end=865 _REQUESTSTATE._serialized_end=918
_PERSISTENTCONFIGURATION._serialized_start=867 _PERSISTENTCONFIGURATION._serialized_start=920
_PERSISTENTCONFIGURATION._serialized_end=985 _PERSISTENTCONFIGURATION._serialized_end=1038
_MOTORCALIBRATION._serialized_start=987 _MOTORCALIBRATION._serialized_start=1040
_MOTORCALIBRATION._serialized_end=1099 _MOTORCALIBRATION._serialized_end=1152
_STRAINCALIBRATION._serialized_start=1101 _STRAINCALIBRATION._serialized_start=1154
_STRAINCALIBRATION._serialized_end=1161 _STRAINCALIBRATION._serialized_end=1214
# @@protoc_insertion_point(module_scope) # @@protoc_insertion_point(module_scope)

View File

@ -34,6 +34,7 @@ sys.path.append(os.path.join(software_root, 'proto_gen'))
from proto_gen import smartknob_pb2 from proto_gen import smartknob_pb2
SMARTKNOB_BAUD = 921600 SMARTKNOB_BAUD = 921600
PROTOBUF_PROTOCOL_VERSION = 1
class Smartknob(object): class Smartknob(object):
@ -92,6 +93,8 @@ class Smartknob(object):
message = smartknob_pb2.FromSmartKnob() message = smartknob_pb2.FromSmartKnob()
message.ParseFromString(payload) message.ParseFromString(payload)
self._logger.debug(message) 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') payload_type = message.WhichOneof('payload')
@ -145,6 +148,7 @@ class Smartknob(object):
nonce = self._next_nonce nonce = self._next_nonce
self._next_nonce += 1 self._next_nonce += 1
message.protocol_version = PROTOBUF_PROTOCOL_VERSION
message.nonce = nonce message.nonce = nonce
payload = bytearray(message.SerializeToString()) payload = bytearray(message.SerializeToString())