interface_task.cpp 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  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 "util.h"
  12. #define COUNT_OF(A) (sizeof(A) / sizeof(A[0]))
  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 num_positions;
  24. // int32_t position;
  25. // float position_width_radians;
  26. // float detent_strength_unit;
  27. // float endstop_strength_unit;
  28. // float snap_point;
  29. // char text[51];
  30. {
  31. 0,
  32. 0,
  33. 10 * PI / 180,
  34. 0,
  35. 1,
  36. 1.1,
  37. "Unbounded\nNo detents",
  38. },
  39. {
  40. 11,
  41. 0,
  42. 10 * PI / 180,
  43. 0,
  44. 1,
  45. 1.1,
  46. "Bounded 0-10\nNo detents",
  47. },
  48. {
  49. 73,
  50. 0,
  51. 10 * PI / 180,
  52. 0,
  53. 1,
  54. 1.1,
  55. "Multi-rev\nNo detents",
  56. },
  57. {
  58. 2,
  59. 0,
  60. 60 * PI / 180,
  61. 1,
  62. 1,
  63. 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)
  64. "On/off\nStrong detent",
  65. },
  66. {
  67. 1,
  68. 0,
  69. 60 * PI / 180,
  70. 0.01,
  71. 0.6,
  72. 1.1,
  73. "Return-to-center",
  74. },
  75. {
  76. 256,
  77. 127,
  78. 1 * PI / 180,
  79. 0,
  80. 1,
  81. 1.1,
  82. "Fine values\nNo detents",
  83. },
  84. {
  85. 256,
  86. 127,
  87. 1 * PI / 180,
  88. 1,
  89. 1,
  90. 1.1,
  91. "Fine values\nWith detents",
  92. },
  93. {
  94. 32,
  95. 0,
  96. 8.225806452 * PI / 180,
  97. 2,
  98. 1,
  99. 1.1,
  100. "Coarse values\nStrong detents",
  101. },
  102. {
  103. 32,
  104. 0,
  105. 8.225806452 * PI / 180,
  106. 0.2,
  107. 1,
  108. 1.1,
  109. "Coarse values\nWeak detents",
  110. },
  111. };
  112. InterfaceTask::InterfaceTask(const uint8_t task_core, MotorTask& motor_task, DisplayTask* display_task) :
  113. Task("Interface", 3000, 1, task_core),
  114. stream_(),
  115. motor_task_(motor_task),
  116. display_task_(display_task),
  117. plaintext_protocol_(stream_, motor_task_),
  118. proto_protocol_(stream_, motor_task_) {
  119. #if SK_DISPLAY
  120. assert(display_task != nullptr);
  121. #endif
  122. log_queue_ = xQueueCreate(10, sizeof(std::string *));
  123. assert(log_queue_ != NULL);
  124. knob_state_queue_ = xQueueCreate(1, sizeof(PB_SmartKnobState));
  125. assert(knob_state_queue_ != NULL);
  126. }
  127. void InterfaceTask::run() {
  128. stream_.begin();
  129. #if SK_LEDS
  130. FastLED.addLeds<SK6812, PIN_LED_DATA, GRB>(leds, NUM_LEDS);
  131. #endif
  132. #if SK_ALS && PIN_SDA >= 0 && PIN_SCL >= 0
  133. Wire.begin(PIN_SDA, PIN_SCL);
  134. Wire.setClock(400000);
  135. #endif
  136. #if SK_STRAIN
  137. scale.begin(38, 2);
  138. #endif
  139. #if SK_ALS
  140. if (veml.begin()) {
  141. veml.setGain(VEML7700_GAIN_2);
  142. veml.setIntegrationTime(VEML7700_IT_400MS);
  143. } else {
  144. log("ALS sensor not found!");
  145. }
  146. #endif
  147. motor_task_.setConfig(configs[0]);
  148. motor_task_.addListener(knob_state_queue_);
  149. // Start in legacy protocol mode
  150. plaintext_protocol_.init([this] () {
  151. changeConfig(true);
  152. });
  153. SerialProtocol* current_protocol = &plaintext_protocol_;
  154. ProtocolChangeCallback protocol_change_callback = [this, &current_protocol] (uint8_t protocol) {
  155. switch (protocol) {
  156. case SERIAL_PROTOCOL_LEGACY:
  157. current_protocol = &plaintext_protocol_;
  158. break;
  159. case SERIAL_PROTOCOL_PROTO:
  160. current_protocol = &proto_protocol_;
  161. break;
  162. default:
  163. log("Unknown protocol requested");
  164. break;
  165. }
  166. };
  167. plaintext_protocol_.setProtocolChangeCallback(protocol_change_callback);
  168. proto_protocol_.setProtocolChangeCallback(protocol_change_callback);
  169. // Interface loop:
  170. while (1) {
  171. PB_SmartKnobState state;
  172. if (xQueueReceive(knob_state_queue_, &state, 0) == pdTRUE) {
  173. current_protocol->handleState(state);
  174. }
  175. current_protocol->loop();
  176. std::string* log_string;
  177. while (xQueueReceive(log_queue_, &log_string, 0) == pdTRUE) {
  178. current_protocol->log(log_string->c_str());
  179. delete log_string;
  180. }
  181. updateHardware();
  182. delay(1);
  183. }
  184. }
  185. void InterfaceTask::log(const char* msg) {
  186. // Allocate a string for the duration it's in the queue; it is free'd by the queue consumer
  187. std::string* msg_str = new std::string(msg);
  188. // Put string in queue (or drop if full to avoid blocking)
  189. xQueueSendToBack(log_queue_, &msg_str, 0);
  190. }
  191. void InterfaceTask::changeConfig(bool next) {
  192. if (next) {
  193. current_config_ = (current_config_ + 1) % COUNT_OF(configs);
  194. } else {
  195. if (current_config_ == 0) {
  196. current_config_ = COUNT_OF(configs) - 1;
  197. } else {
  198. current_config_ --;
  199. }
  200. }
  201. char buf_[256];
  202. snprintf(buf_, sizeof(buf_), "Changing config to %d -- %s", current_config_, configs[current_config_].text);
  203. log(buf_);
  204. motor_task_.setConfig(configs[current_config_]);
  205. }
  206. void InterfaceTask::updateHardware() {
  207. // How far button is pressed, in range [0, 1]
  208. float press_value_unit = 0;
  209. #if SK_ALS
  210. const float LUX_ALPHA = 0.005;
  211. static float lux_avg;
  212. float lux = veml.readLux();
  213. lux_avg = lux * LUX_ALPHA + lux_avg * (1 - LUX_ALPHA);
  214. static uint32_t last_als;
  215. if (millis() - last_als > 1000) {
  216. snprintf(buf_, sizeof(buf_), "millilux: %.2f", lux*1000);
  217. log(buf_);
  218. last_als = millis();
  219. }
  220. #endif
  221. #if SK_STRAIN
  222. if (scale.wait_ready_timeout(100)) {
  223. int32_t reading = scale.read();
  224. static uint32_t last_reading_display;
  225. if (millis() - last_reading_display > 1000) {
  226. snprintf(buf_, sizeof(buf_), "HX711 reading: %d", reading);
  227. log(buf_);
  228. last_reading_display = millis();
  229. }
  230. // TODO: calibrate and track (long term moving average) zero point (lower); allow calibration of set point offset
  231. const int32_t lower = 950000;
  232. const int32_t upper = 1800000;
  233. // Ignore readings that are way out of expected bounds
  234. if (reading >= lower - (upper - lower) && reading < upper + (upper - lower)*2) {
  235. long value = CLAMP(reading, lower, upper);
  236. press_value_unit = 1. * (value - lower) / (upper - lower);
  237. static bool pressed;
  238. if (!pressed && press_value_unit > 0.75) {
  239. motor_task_.playHaptic(true);
  240. pressed = true;
  241. changeConfig(true);
  242. } else if (pressed && press_value_unit < 0.25) {
  243. motor_task_.playHaptic(false);
  244. pressed = false;
  245. }
  246. }
  247. } else {
  248. log("HX711 not found.");
  249. #if SK_LEDS
  250. for (uint8_t i = 0; i < NUM_LEDS; i++) {
  251. leds[i] = CRGB::Red;
  252. }
  253. FastLED.show();
  254. #endif
  255. }
  256. #endif
  257. uint16_t brightness = UINT16_MAX;
  258. // TODO: brightness scale factor should be configurable (depends on reflectivity of surface)
  259. #if SK_ALS
  260. brightness = (uint16_t)CLAMP(lux_avg * 13000, (float)1280, (float)UINT16_MAX);
  261. #endif
  262. #if SK_DISPLAY
  263. display_task_->setBrightness(brightness); // TODO: apply gamma correction
  264. #endif
  265. #if SK_LEDS
  266. for (uint8_t i = 0; i < NUM_LEDS; i++) {
  267. leds[i].setHSV(200 * press_value_unit, 255, brightness >> 8);
  268. // Gamma adjustment
  269. leds[i].r = dim8_video(leds[i].r);
  270. leds[i].g = dim8_video(leds[i].g);
  271. leds[i].b = dim8_video(leds[i].b);
  272. }
  273. FastLED.show();
  274. #endif
  275. }