interface_task.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. #if SK_LEDS
  2. #include <FastLED.h>
  3. #endif
  4. #if SK_STRAIN
  5. #include <HX711.h>
  6. #endif
  7. #if SK_ALS
  8. #include <Adafruit_VEML7700.h>
  9. #endif
  10. #include "interface_task.h"
  11. #include "semaphore_guard.h"
  12. #include "util.h"
  13. #if SK_LEDS
  14. CRGB leds[NUM_LEDS];
  15. #endif
  16. #if SK_STRAIN
  17. HX711 scale;
  18. #endif
  19. #if SK_ALS
  20. Adafruit_VEML7700 veml = Adafruit_VEML7700();
  21. #endif
  22. static PB_SmartKnobConfig configs[] = {
  23. // int32_t position;
  24. // float sub_position_unit;
  25. // uint8_t position_nonce;
  26. // int32_t min_position;
  27. // int32_t max_position;
  28. // float position_width_radians;
  29. // float detent_strength_unit;
  30. // float endstop_strength_unit;
  31. // float snap_point;
  32. // char text[51];
  33. // pb_size_t detent_positions_count;
  34. // int32_t detent_positions[5];
  35. // float snap_point_bias;
  36. // int8_t led_hue;
  37. {
  38. 0,
  39. 0,
  40. 0,
  41. 0,
  42. -1, // max position < min position indicates no bounds
  43. 10 * PI / 180,
  44. 0,
  45. 1,
  46. 1.1,
  47. "Unbounded\nNo detents",
  48. 0,
  49. {},
  50. 0,
  51. 200,
  52. },
  53. {
  54. 0,
  55. 0,
  56. 1,
  57. 0,
  58. 10,
  59. 10 * PI / 180,
  60. 0,
  61. 1,
  62. 1.1,
  63. "Bounded 0-10\nNo detents",
  64. 0,
  65. {},
  66. 0,
  67. 0,
  68. },
  69. {
  70. 0,
  71. 0,
  72. 2,
  73. 0,
  74. 72,
  75. 10 * PI / 180,
  76. 0,
  77. 1,
  78. 1.1,
  79. "Multi-rev\nNo detents",
  80. 0,
  81. {},
  82. 0,
  83. 73,
  84. },
  85. {
  86. 0,
  87. 0,
  88. 3,
  89. 0,
  90. 1,
  91. 60 * PI / 180,
  92. 1,
  93. 1,
  94. 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)
  95. "On/off\nStrong detent",
  96. 0,
  97. {},
  98. 0,
  99. 157,
  100. },
  101. {
  102. 0,
  103. 0,
  104. 4,
  105. 0,
  106. 0,
  107. 60 * PI / 180,
  108. 0.01,
  109. 0.6,
  110. 1.1,
  111. "Return-to-center",
  112. 0,
  113. {},
  114. 0,
  115. 45,
  116. },
  117. {
  118. 127,
  119. 0,
  120. 5,
  121. 0,
  122. 255,
  123. 1 * PI / 180,
  124. 0,
  125. 1,
  126. 1.1,
  127. "Fine values\nNo detents",
  128. 0,
  129. {},
  130. 0,
  131. 219,
  132. },
  133. {
  134. 127,
  135. 0,
  136. 5,
  137. 0,
  138. 255,
  139. 1 * PI / 180,
  140. 1,
  141. 1,
  142. 1.1,
  143. "Fine values\nWith detents",
  144. 0,
  145. {},
  146. 0,
  147. 25,
  148. },
  149. {
  150. 0,
  151. 0,
  152. 6,
  153. 0,
  154. 31,
  155. 8.225806452 * PI / 180,
  156. 2,
  157. 1,
  158. 1.1,
  159. "Coarse values\nStrong detents",
  160. 0,
  161. {},
  162. 0,
  163. 200,
  164. },
  165. {
  166. 0,
  167. 0,
  168. 6,
  169. 0,
  170. 31,
  171. 8.225806452 * PI / 180,
  172. 0.2,
  173. 1,
  174. 1.1,
  175. "Coarse values\nWeak detents",
  176. 0,
  177. {},
  178. 0,
  179. 0,
  180. },
  181. {
  182. 0,
  183. 0,
  184. 7,
  185. 0,
  186. 31,
  187. 7 * PI / 180,
  188. 2.5,
  189. 1,
  190. 0.7,
  191. "Magnetic detents",
  192. 4,
  193. {2, 10, 21, 22},
  194. 0,
  195. 73,
  196. },
  197. {
  198. 0,
  199. 0,
  200. 8,
  201. -6,
  202. 6,
  203. 60 * PI / 180,
  204. 1,
  205. 1,
  206. 0.55,
  207. "Return-to-center\nwith detents",
  208. 0,
  209. {},
  210. 0.4,
  211. 157,
  212. },
  213. };
  214. InterfaceTask::InterfaceTask(const uint8_t task_core, MotorTask& motor_task, DisplayTask* display_task) :
  215. Task("Interface", 3400, 1, task_core),
  216. stream_(),
  217. motor_task_(motor_task),
  218. display_task_(display_task),
  219. plaintext_protocol_(stream_, [this] () {
  220. motor_task_.runCalibration();
  221. }),
  222. proto_protocol_(stream_, [this] (PB_SmartKnobConfig& config) {
  223. applyConfig(config, true);
  224. }) {
  225. #if SK_DISPLAY
  226. assert(display_task != nullptr);
  227. #endif
  228. log_queue_ = xQueueCreate(10, sizeof(std::string *));
  229. assert(log_queue_ != NULL);
  230. knob_state_queue_ = xQueueCreate(1, sizeof(PB_SmartKnobState));
  231. assert(knob_state_queue_ != NULL);
  232. mutex_ = xSemaphoreCreateMutex();
  233. assert(mutex_ != NULL);
  234. }
  235. InterfaceTask::~InterfaceTask() {
  236. vSemaphoreDelete(mutex_);
  237. }
  238. void InterfaceTask::run() {
  239. stream_.begin();
  240. #if SK_LEDS
  241. FastLED.addLeds<SK6812, PIN_LED_DATA, GRB>(leds, NUM_LEDS);
  242. #endif
  243. #if SK_ALS && PIN_SDA >= 0 && PIN_SCL >= 0
  244. Wire.begin(PIN_SDA, PIN_SCL);
  245. Wire.setClock(400000);
  246. #endif
  247. #if SK_STRAIN
  248. scale.begin(38, 2);
  249. #endif
  250. #if SK_ALS
  251. if (veml.begin()) {
  252. veml.setGain(VEML7700_GAIN_2);
  253. veml.setIntegrationTime(VEML7700_IT_400MS);
  254. } else {
  255. log("ALS sensor not found!");
  256. }
  257. #endif
  258. applyConfig(configs[0], false);
  259. motor_task_.addListener(knob_state_queue_);
  260. plaintext_protocol_.init([this] () {
  261. changeConfig(true);
  262. }, [this] () {
  263. if (!configuration_loaded_) {
  264. return;
  265. }
  266. if (strain_calibration_step_ == 0) {
  267. log("Strain calibration step 1: Don't touch the knob, then press 'S' again");
  268. strain_calibration_step_ = 1;
  269. } else if (strain_calibration_step_ == 1) {
  270. configuration_value_.strain.idle_value = strain_reading_;
  271. snprintf(buf_, sizeof(buf_), " idle_value=%d", configuration_value_.strain.idle_value);
  272. log(buf_);
  273. log("Strain calibration step 2: Push and hold down the knob with medium pressure, and press 'S' again");
  274. strain_calibration_step_ = 2;
  275. } else if (strain_calibration_step_ == 2) {
  276. configuration_value_.strain.press_delta = strain_reading_ - configuration_value_.strain.idle_value;
  277. configuration_value_.has_strain = true;
  278. snprintf(buf_, sizeof(buf_), " press_delta=%d", configuration_value_.strain.press_delta);
  279. log(buf_);
  280. log("Strain calibration complete! Saving...");
  281. strain_calibration_step_ = 0;
  282. if (configuration_->setStrainCalibrationAndSave(configuration_value_.strain)) {
  283. log(" Saved!");
  284. } else {
  285. log(" FAILED to save config!!!");
  286. }
  287. }
  288. });
  289. // Start in legacy protocol mode
  290. current_protocol_ = &plaintext_protocol_;
  291. ProtocolChangeCallback protocol_change_callback = [this] (uint8_t protocol) {
  292. switch (protocol) {
  293. case SERIAL_PROTOCOL_LEGACY:
  294. current_protocol_ = &plaintext_protocol_;
  295. break;
  296. case SERIAL_PROTOCOL_PROTO:
  297. current_protocol_ = &proto_protocol_;
  298. break;
  299. default:
  300. log("Unknown protocol requested");
  301. break;
  302. }
  303. };
  304. plaintext_protocol_.setProtocolChangeCallback(protocol_change_callback);
  305. proto_protocol_.setProtocolChangeCallback(protocol_change_callback);
  306. // Interface loop:
  307. while (1) {
  308. if (xQueueReceive(knob_state_queue_, &latest_state_, 0) == pdTRUE) {
  309. publishState();
  310. }
  311. current_protocol_->loop();
  312. std::string* log_string;
  313. while (xQueueReceive(log_queue_, &log_string, 0) == pdTRUE) {
  314. current_protocol_->log(log_string->c_str());
  315. delete log_string;
  316. }
  317. updateHardware();
  318. if (!configuration_loaded_) {
  319. SemaphoreGuard lock(mutex_);
  320. if (configuration_ != nullptr) {
  321. configuration_value_ = configuration_->get();
  322. configuration_loaded_ = true;
  323. }
  324. }
  325. delay(1);
  326. }
  327. }
  328. void InterfaceTask::log(const char* msg) {
  329. // Allocate a string for the duration it's in the queue; it is free'd by the queue consumer
  330. std::string* msg_str = new std::string(msg);
  331. // Put string in queue (or drop if full to avoid blocking)
  332. xQueueSendToBack(log_queue_, &msg_str, 0);
  333. }
  334. void InterfaceTask::changeConfig(bool next) {
  335. if (next) {
  336. current_config_ = (current_config_ + 1) % COUNT_OF(configs);
  337. } else {
  338. if (current_config_ == 0) {
  339. current_config_ = COUNT_OF(configs) - 1;
  340. } else {
  341. current_config_ --;
  342. }
  343. }
  344. snprintf(buf_, sizeof(buf_), "Changing config to %d -- %s", current_config_, configs[current_config_].text);
  345. log(buf_);
  346. applyConfig(configs[current_config_], false);
  347. }
  348. void InterfaceTask::updateHardware() {
  349. // How far button is pressed, in range [0, 1]
  350. float press_value_unit = 0;
  351. #if SK_ALS
  352. const float LUX_ALPHA = 0.005;
  353. static float lux_avg;
  354. float lux = veml.readLux();
  355. lux_avg = lux * LUX_ALPHA + lux_avg * (1 - LUX_ALPHA);
  356. static uint32_t last_als;
  357. if (millis() - last_als > 1000 && strain_calibration_step_ == 0) {
  358. snprintf(buf_, sizeof(buf_), "millilux: %.2f", lux*1000);
  359. log(buf_);
  360. last_als = millis();
  361. }
  362. #endif
  363. static bool pressed;
  364. #if SK_STRAIN
  365. if (scale.wait_ready_timeout(100)) {
  366. strain_reading_ = scale.read();
  367. static uint32_t last_reading_display;
  368. if (millis() - last_reading_display > 1000 && strain_calibration_step_ == 0) {
  369. snprintf(buf_, sizeof(buf_), "HX711 reading: %d", strain_reading_);
  370. log(buf_);
  371. last_reading_display = millis();
  372. }
  373. if (configuration_loaded_ && configuration_value_.has_strain && strain_calibration_step_ == 0) {
  374. // TODO: calibrate and track (long term moving average) idle point (lower)
  375. press_value_unit = lerp(strain_reading_, configuration_value_.strain.idle_value, configuration_value_.strain.idle_value + configuration_value_.strain.press_delta, 0, 1);
  376. // Ignore readings that are way out of expected bounds
  377. if (-1 < press_value_unit && press_value_unit < 2) {
  378. static uint8_t press_readings;
  379. if (!pressed && press_value_unit > 1) {
  380. press_readings++;
  381. if (press_readings > 2) {
  382. motor_task_.playHaptic(true);
  383. pressed = true;
  384. press_count_++;
  385. publishState();
  386. if (!remote_controlled_) {
  387. changeConfig(true);
  388. }
  389. }
  390. } else if (pressed && press_value_unit < 0.5) {
  391. press_readings++;
  392. if (press_readings > 2) {
  393. motor_task_.playHaptic(false);
  394. pressed = false;
  395. }
  396. } else {
  397. press_readings = 0;
  398. }
  399. }
  400. }
  401. } else {
  402. log("HX711 not found.");
  403. #if SK_LEDS
  404. for (uint8_t i = 0; i < NUM_LEDS; i++) {
  405. leds[i] = CRGB::Red;
  406. }
  407. FastLED.show();
  408. #endif
  409. }
  410. #endif
  411. uint16_t brightness = UINT16_MAX;
  412. // TODO: brightness scale factor should be configurable (depends on reflectivity of surface)
  413. #if SK_ALS
  414. brightness = (uint16_t)CLAMP(lux_avg * 13000, (float)1280, (float)UINT16_MAX);
  415. #endif
  416. #if SK_DISPLAY
  417. display_task_->setBrightness(brightness); // TODO: apply gamma correction
  418. #endif
  419. #if SK_LEDS
  420. for (uint8_t i = 0; i < NUM_LEDS; i++) {
  421. leds[i].setHSV(latest_config_.led_hue, 255 - 180*CLAMP(press_value_unit, (float)0, (float)1) - 75*pressed, brightness >> 8);
  422. // Gamma adjustment
  423. leds[i].r = dim8_video(leds[i].r);
  424. leds[i].g = dim8_video(leds[i].g);
  425. leds[i].b = dim8_video(leds[i].b);
  426. }
  427. FastLED.show();
  428. #endif
  429. }
  430. void InterfaceTask::setConfiguration(Configuration* configuration) {
  431. SemaphoreGuard lock(mutex_);
  432. configuration_ = configuration;
  433. }
  434. void InterfaceTask::publishState() {
  435. // Apply local state before publishing to serial
  436. latest_state_.press_nonce = press_count_;
  437. current_protocol_->handleState(latest_state_);
  438. }
  439. void InterfaceTask::applyConfig(PB_SmartKnobConfig& config, bool from_remote) {
  440. remote_controlled_ = from_remote;
  441. latest_config_ = config;
  442. motor_task_.setConfig(config);
  443. }