| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489 |
- #if SK_LEDS
- #include <FastLED.h>
- #endif
- #if SK_STRAIN
- #include <HX711.h>
- #endif
- #if SK_ALS
- #include <Adafruit_VEML7700.h>
- #endif
- #include "interface_task.h"
- #include "semaphore_guard.h"
- #include "util.h"
- #if SK_LEDS
- CRGB leds[NUM_LEDS];
- #endif
- #if SK_STRAIN
- HX711 scale;
- #endif
- #if SK_ALS
- Adafruit_VEML7700 veml = Adafruit_VEML7700();
- #endif
- static PB_SmartKnobConfig configs[] = {
- // int32_t position;
- // float sub_position_unit;
- // uint8_t position_nonce;
- // int32_t min_position;
- // int32_t max_position;
- // float position_width_radians;
- // float detent_strength_unit;
- // float endstop_strength_unit;
- // float snap_point;
- // char text[51];
- // pb_size_t detent_positions_count;
- // int32_t detent_positions[5];
- // float snap_point_bias;
- // int8_t led_hue;
- {
- 0,
- 0,
- 0,
- 0,
- -1, // max position < min position indicates no bounds
- 10 * PI / 180,
- 0,
- 1,
- 1.1,
- "Unbounded\nNo detents",
- 0,
- {},
- 0,
- 200,
- },
- {
- 0,
- 0,
- 1,
- 0,
- 10,
- 10 * PI / 180,
- 0,
- 1,
- 1.1,
- "Bounded 0-10\nNo detents",
- 0,
- {},
- 0,
- 0,
- },
- {
- 0,
- 0,
- 2,
- 0,
- 72,
- 10 * PI / 180,
- 0,
- 1,
- 1.1,
- "Multi-rev\nNo detents",
- 0,
- {},
- 0,
- 73,
- },
- {
- 0,
- 0,
- 3,
- 0,
- 1,
- 60 * PI / 180,
- 1,
- 1,
- 0.55, // Note the snap point is slightly past the midpoint (0.5); compare to normal detents which use a snap point *past* the next value (i.e. > 1)
- "On/off\nStrong detent",
- 0,
- {},
- 0,
- 157,
- },
- {
- 0,
- 0,
- 4,
- 0,
- 0,
- 60 * PI / 180,
- 0.01,
- 0.6,
- 1.1,
- "Return-to-center",
- 0,
- {},
- 0,
- 45,
- },
- {
- 127,
- 0,
- 5,
- 0,
- 255,
- 1 * PI / 180,
- 0,
- 1,
- 1.1,
- "Fine values\nNo detents",
- 0,
- {},
- 0,
- 219,
- },
- {
- 127,
- 0,
- 5,
- 0,
- 255,
- 1 * PI / 180,
- 1,
- 1,
- 1.1,
- "Fine values\nWith detents",
- 0,
- {},
- 0,
- 25,
- },
- {
- 0,
- 0,
- 6,
- 0,
- 31,
- 8.225806452 * PI / 180,
- 2,
- 1,
- 1.1,
- "Coarse values\nStrong detents",
- 0,
- {},
- 0,
- 200,
- },
- {
- 0,
- 0,
- 6,
- 0,
- 31,
- 8.225806452 * PI / 180,
- 0.2,
- 1,
- 1.1,
- "Coarse values\nWeak detents",
- 0,
- {},
- 0,
- 0,
- },
- {
- 0,
- 0,
- 7,
- 0,
- 31,
- 7 * PI / 180,
- 2.5,
- 1,
- 0.7,
- "Magnetic detents",
- 4,
- {2, 10, 21, 22},
- 0,
- 73,
- },
- {
- 0,
- 0,
- 8,
- -6,
- 6,
- 60 * PI / 180,
- 1,
- 1,
- 0.55,
- "Return-to-center\nwith detents",
- 0,
- {},
- 0.4,
- 157,
- },
- };
- InterfaceTask::InterfaceTask(const uint8_t task_core, MotorTask& motor_task, DisplayTask* display_task) :
- Task("Interface", 3400, 1, task_core),
- stream_(),
- motor_task_(motor_task),
- display_task_(display_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);
- knob_state_queue_ = xQueueCreate(1, sizeof(PB_SmartKnobState));
- assert(knob_state_queue_ != NULL);
- mutex_ = xSemaphoreCreateMutex();
- assert(mutex_ != NULL);
- }
- InterfaceTask::~InterfaceTask() {
- vSemaphoreDelete(mutex_);
- }
- void InterfaceTask::run() {
- stream_.begin();
-
- #if SK_LEDS
- FastLED.addLeds<SK6812, PIN_LED_DATA, GRB>(leds, NUM_LEDS);
- #endif
- #if SK_ALS && PIN_SDA >= 0 && PIN_SCL >= 0
- Wire.begin(PIN_SDA, PIN_SCL);
- Wire.setClock(400000);
- #endif
- #if SK_STRAIN
- scale.begin(38, 2);
- #endif
- #if SK_ALS
- if (veml.begin()) {
- veml.setGain(VEML7700_GAIN_2);
- veml.setIntegrationTime(VEML7700_IT_400MS);
- } else {
- log("ALS sensor not found!");
- }
- #endif
- applyConfig(configs[0], false);
- motor_task_.addListener(knob_state_queue_);
- plaintext_protocol_.init([this] () {
- changeConfig(true);
- }, [this] () {
- if (!configuration_loaded_) {
- return;
- }
- if (strain_calibration_step_ == 0) {
- log("Strain calibration step 1: Don't touch the knob, then press 'S' again");
- strain_calibration_step_ = 1;
- } else if (strain_calibration_step_ == 1) {
- configuration_value_.strain.idle_value = strain_reading_;
- snprintf(buf_, sizeof(buf_), " idle_value=%d", configuration_value_.strain.idle_value);
- log(buf_);
- log("Strain calibration step 2: Push and hold down the knob with medium pressure, and press 'S' again");
- strain_calibration_step_ = 2;
- } else if (strain_calibration_step_ == 2) {
- configuration_value_.strain.press_delta = strain_reading_ - configuration_value_.strain.idle_value;
- configuration_value_.has_strain = true;
- snprintf(buf_, sizeof(buf_), " press_delta=%d", configuration_value_.strain.press_delta);
- log(buf_);
- log("Strain calibration complete! Saving...");
- strain_calibration_step_ = 0;
- if (configuration_->setStrainCalibrationAndSave(configuration_value_.strain)) {
- log(" Saved!");
- } else {
- log(" FAILED to save config!!!");
- }
- }
- });
- // Start in legacy protocol mode
- current_protocol_ = &plaintext_protocol_;
- ProtocolChangeCallback protocol_change_callback = [this] (uint8_t protocol) {
- switch (protocol) {
- case SERIAL_PROTOCOL_LEGACY:
- current_protocol_ = &plaintext_protocol_;
- break;
- case SERIAL_PROTOCOL_PROTO:
- current_protocol_ = &proto_protocol_;
- break;
- default:
- log("Unknown protocol requested");
- break;
- }
- };
- plaintext_protocol_.setProtocolChangeCallback(protocol_change_callback);
- proto_protocol_.setProtocolChangeCallback(protocol_change_callback);
- // Interface loop:
- while (1) {
- if (xQueueReceive(knob_state_queue_, &latest_state_, 0) == pdTRUE) {
- publishState();
- }
- current_protocol_->loop();
- std::string* log_string;
- while (xQueueReceive(log_queue_, &log_string, 0) == pdTRUE) {
- current_protocol_->log(log_string->c_str());
- delete log_string;
- }
- updateHardware();
- if (!configuration_loaded_) {
- SemaphoreGuard lock(mutex_);
- if (configuration_ != nullptr) {
- configuration_value_ = configuration_->get();
- configuration_loaded_ = true;
- }
- }
- delay(1);
- }
- }
- void InterfaceTask::log(const char* msg) {
- // Allocate a string for the duration it's in the queue; it is free'd by the queue consumer
- std::string* msg_str = new std::string(msg);
- // Put string in queue (or drop if full to avoid blocking)
- xQueueSendToBack(log_queue_, &msg_str, 0);
- }
- void InterfaceTask::changeConfig(bool next) {
- if (next) {
- current_config_ = (current_config_ + 1) % COUNT_OF(configs);
- } else {
- if (current_config_ == 0) {
- current_config_ = COUNT_OF(configs) - 1;
- } else {
- current_config_ --;
- }
- }
-
- snprintf(buf_, sizeof(buf_), "Changing config to %d -- %s", current_config_, configs[current_config_].text);
- log(buf_);
- applyConfig(configs[current_config_], false);
- }
- void InterfaceTask::updateHardware() {
- // How far button is pressed, in range [0, 1]
- float press_value_unit = 0;
- #if SK_ALS
- const float LUX_ALPHA = 0.005;
- static float lux_avg;
- float lux = veml.readLux();
- lux_avg = lux * LUX_ALPHA + lux_avg * (1 - LUX_ALPHA);
- static uint32_t last_als;
- if (millis() - last_als > 1000 && strain_calibration_step_ == 0) {
- snprintf(buf_, sizeof(buf_), "millilux: %.2f", lux*1000);
- log(buf_);
- last_als = millis();
- }
- #endif
- static bool pressed;
- #if SK_STRAIN
- if (scale.wait_ready_timeout(100)) {
- strain_reading_ = scale.read();
- static uint32_t last_reading_display;
- if (millis() - last_reading_display > 1000 && strain_calibration_step_ == 0) {
- snprintf(buf_, sizeof(buf_), "HX711 reading: %d", strain_reading_);
- log(buf_);
- last_reading_display = millis();
- }
- if (configuration_loaded_ && configuration_value_.has_strain && strain_calibration_step_ == 0) {
- // TODO: calibrate and track (long term moving average) idle point (lower)
- press_value_unit = lerp(strain_reading_, configuration_value_.strain.idle_value, configuration_value_.strain.idle_value + configuration_value_.strain.press_delta, 0, 1);
- // Ignore readings that are way out of expected bounds
- if (-1 < press_value_unit && press_value_unit < 2) {
- static uint8_t press_readings;
- if (!pressed && press_value_unit > 1) {
- press_readings++;
- if (press_readings > 2) {
- motor_task_.playHaptic(true);
- pressed = true;
- press_count_++;
- publishState();
- if (!remote_controlled_) {
- changeConfig(true);
- }
- }
- } else if (pressed && press_value_unit < 0.5) {
- press_readings++;
- if (press_readings > 2) {
- motor_task_.playHaptic(false);
- pressed = false;
- }
- } else {
- press_readings = 0;
- }
- }
- }
- } else {
- log("HX711 not found.");
- #if SK_LEDS
- for (uint8_t i = 0; i < NUM_LEDS; i++) {
- leds[i] = CRGB::Red;
- }
- FastLED.show();
- #endif
- }
- #endif
- uint16_t brightness = UINT16_MAX;
- // TODO: brightness scale factor should be configurable (depends on reflectivity of surface)
- #if SK_ALS
- brightness = (uint16_t)CLAMP(lux_avg * 13000, (float)1280, (float)UINT16_MAX);
- #endif
- #if SK_DISPLAY
- display_task_->setBrightness(brightness); // TODO: apply gamma correction
- #endif
- #if SK_LEDS
- for (uint8_t i = 0; i < NUM_LEDS; i++) {
- 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);
- leds[i].g = dim8_video(leds[i].g);
- leds[i].b = dim8_video(leds[i].b);
- }
- FastLED.show();
- #endif
- }
- 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);
- }
|