MiLightClient.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. #include <MiLightClient.h>
  2. #include <MiLightRadioConfig.h>
  3. #include <Arduino.h>
  4. #include <RGBConverter.h>
  5. #include <Units.h>
  6. #include <TokenIterator.h>
  7. MiLightClient::MiLightClient(
  8. MiLightRadioFactory* radioFactory,
  9. GroupStateStore* stateStore,
  10. Settings* settings
  11. )
  12. : baseResendCount(MILIGHT_DEFAULT_RESEND_COUNT),
  13. currentRadio(NULL),
  14. currentRemote(NULL),
  15. numRadios(MiLightRadioConfig::NUM_CONFIGS),
  16. packetSentHandler(NULL),
  17. updateBeginHandler(NULL),
  18. updateEndHandler(NULL),
  19. stateStore(stateStore),
  20. settings(settings),
  21. lastSend(0)
  22. {
  23. radios = new MiLightRadio*[numRadios];
  24. for (size_t i = 0; i < numRadios; i++) {
  25. radios[i] = radioFactory->create(MiLightRadioConfig::ALL_CONFIGS[i]);
  26. }
  27. }
  28. void MiLightClient::begin() {
  29. for (size_t i = 0; i < numRadios; i++) {
  30. radios[i]->begin();
  31. }
  32. switchRadio(static_cast<size_t>(0));
  33. // Little gross to do this here as it's relying on global state. A better alternative
  34. // would be to statically construct remote config factories which take in a stateStore
  35. // and settings pointer. The objects could then be initialized by calling the factory
  36. // in main.
  37. for (size_t i = 0; i < MiLightRemoteConfig::NUM_REMOTES; i++) {
  38. MiLightRemoteConfig::ALL_REMOTES[i]->packetFormatter->initialize(stateStore, settings);
  39. }
  40. }
  41. void MiLightClient::setHeld(bool held) {
  42. currentRemote->packetFormatter->setHeld(held);
  43. }
  44. size_t MiLightClient::getNumRadios() const {
  45. return numRadios;
  46. }
  47. MiLightRadio* MiLightClient::switchRadio(size_t radioIx) {
  48. if (radioIx >= getNumRadios()) {
  49. return NULL;
  50. }
  51. if (this->currentRadio != radios[radioIx]) {
  52. this->currentRadio = radios[radioIx];
  53. this->currentRadio->configure();
  54. }
  55. return this->currentRadio;
  56. }
  57. MiLightRadio* MiLightClient::switchRadio(const MiLightRemoteConfig* remoteConfig) {
  58. MiLightRadio* radio;
  59. for (int i = 0; i < numRadios; i++) {
  60. if (&this->radios[i]->config() == &remoteConfig->radioConfig) {
  61. radio = switchRadio(i);
  62. break;
  63. }
  64. }
  65. return radio;
  66. }
  67. void MiLightClient::prepare(const MiLightRemoteConfig* config,
  68. const uint16_t deviceId,
  69. const uint8_t groupId
  70. ) {
  71. switchRadio(config);
  72. this->currentRemote = config;
  73. if (deviceId >= 0 && groupId >= 0) {
  74. currentRemote->packetFormatter->prepare(deviceId, groupId);
  75. }
  76. }
  77. void MiLightClient::prepare(const MiLightRemoteType type,
  78. const uint16_t deviceId,
  79. const uint8_t groupId
  80. ) {
  81. prepare(MiLightRemoteConfig::fromType(type), deviceId, groupId);
  82. }
  83. void MiLightClient::setResendCount(const unsigned int resendCount) {
  84. this->baseResendCount = resendCount;
  85. this->currentResendCount = resendCount;
  86. this->throttleMultiplier = ceil((settings->packetRepeatThrottleSensitivity / 1000.0) * this->baseResendCount);
  87. }
  88. bool MiLightClient::available() {
  89. if (currentRadio == NULL) {
  90. return false;
  91. }
  92. return currentRadio->available();
  93. }
  94. size_t MiLightClient::read(uint8_t packet[]) {
  95. if (currentRadio == NULL) {
  96. return 0;
  97. }
  98. size_t length;
  99. currentRadio->read(packet, length);
  100. return length;
  101. }
  102. void MiLightClient::write(uint8_t packet[]) {
  103. if (currentRadio == NULL) {
  104. return;
  105. }
  106. #ifdef DEBUG_PRINTF
  107. Serial.printf_P(PSTR("Sending packet (%d repeats): \n"), this->currentResendCount);
  108. for (int i = 0; i < currentRemote->packetFormatter->getPacketLength(); i++) {
  109. Serial.printf_P(PSTR("%02X "), packet[i]);
  110. }
  111. Serial.println();
  112. int iStart = millis();
  113. #endif
  114. // send the packet out (multiple times for "reliability")
  115. for (int i = 0; i < this->currentResendCount; i++) {
  116. currentRadio->write(packet, currentRemote->packetFormatter->getPacketLength());
  117. }
  118. // if we have a packetSendHandler defined (see MiLightClient::onPacketSent), call it now that
  119. // the packet has been dispatched
  120. if (this->packetSentHandler) {
  121. this->packetSentHandler(packet, *currentRemote);
  122. }
  123. #ifdef DEBUG_PRINTF
  124. int iElapsed = millis() - iStart;
  125. Serial.print("Elapsed: ");
  126. Serial.println(iElapsed);
  127. #endif
  128. }
  129. void MiLightClient::updateColorRaw(const uint8_t color) {
  130. #ifdef DEBUG_CLIENT_COMMANDS
  131. Serial.printf_P(PSTR("MiLightClient::updateColorRaw: Change color to %d\n"), color);
  132. #endif
  133. currentRemote->packetFormatter->updateColorRaw(color);
  134. flushPacket();
  135. }
  136. void MiLightClient::updateHue(const uint16_t hue) {
  137. #ifdef DEBUG_CLIENT_COMMANDS
  138. Serial.printf_P(PSTR("MiLightClient::updateHue: Change hue to %d\n"), hue);
  139. #endif
  140. currentRemote->packetFormatter->updateHue(hue);
  141. flushPacket();
  142. }
  143. void MiLightClient::updateBrightness(const uint8_t brightness) {
  144. #ifdef DEBUG_CLIENT_COMMANDS
  145. Serial.printf_P(PSTR("MiLightClient::updateBrightness: Change brightness to %d\n"), brightness);
  146. #endif
  147. currentRemote->packetFormatter->updateBrightness(brightness);
  148. flushPacket();
  149. }
  150. void MiLightClient::updateMode(uint8_t mode) {
  151. #ifdef DEBUG_CLIENT_COMMANDS
  152. Serial.printf_P(PSTR("MiLightClient::updateMode: Change mode to %d\n"), mode);
  153. #endif
  154. currentRemote->packetFormatter->updateMode(mode);
  155. flushPacket();
  156. }
  157. void MiLightClient::nextMode() {
  158. #ifdef DEBUG_CLIENT_COMMANDS
  159. Serial.println(F("MiLightClient::nextMode: Switch to next mode"));
  160. #endif
  161. currentRemote->packetFormatter->nextMode();
  162. flushPacket();
  163. }
  164. void MiLightClient::previousMode() {
  165. #ifdef DEBUG_CLIENT_COMMANDS
  166. Serial.println(F("MiLightClient::previousMode: Switch to previous mode"));
  167. #endif
  168. currentRemote->packetFormatter->previousMode();
  169. flushPacket();
  170. }
  171. void MiLightClient::modeSpeedDown() {
  172. #ifdef DEBUG_CLIENT_COMMANDS
  173. Serial.println(F("MiLightClient::modeSpeedDown: Speed down\n"));
  174. #endif
  175. currentRemote->packetFormatter->modeSpeedDown();
  176. flushPacket();
  177. }
  178. void MiLightClient::modeSpeedUp() {
  179. #ifdef DEBUG_CLIENT_COMMANDS
  180. Serial.println(F("MiLightClient::modeSpeedUp: Speed up"));
  181. #endif
  182. currentRemote->packetFormatter->modeSpeedUp();
  183. flushPacket();
  184. }
  185. void MiLightClient::updateStatus(MiLightStatus status, uint8_t groupId) {
  186. #ifdef DEBUG_CLIENT_COMMANDS
  187. Serial.printf_P(PSTR("MiLightClient::updateStatus: Status %s, groupId %d\n"), status == MiLightStatus::OFF ? "OFF" : "ON", groupId);
  188. #endif
  189. currentRemote->packetFormatter->updateStatus(status, groupId);
  190. flushPacket();
  191. }
  192. void MiLightClient::updateStatus(MiLightStatus status) {
  193. #ifdef DEBUG_CLIENT_COMMANDS
  194. Serial.printf_P(PSTR("MiLightClient::updateStatus: Status %s\n"), status == MiLightStatus::OFF ? "OFF" : "ON");
  195. #endif
  196. currentRemote->packetFormatter->updateStatus(status);
  197. flushPacket();
  198. }
  199. void MiLightClient::updateSaturation(const uint8_t value) {
  200. #ifdef DEBUG_CLIENT_COMMANDS
  201. Serial.printf_P(PSTR("MiLightClient::updateSaturation: Saturation %d\n"), value);
  202. #endif
  203. currentRemote->packetFormatter->updateSaturation(value);
  204. flushPacket();
  205. }
  206. void MiLightClient::updateColorWhite() {
  207. #ifdef DEBUG_CLIENT_COMMANDS
  208. Serial.println(F("MiLightClient::updateColorWhite: Color white"));
  209. #endif
  210. currentRemote->packetFormatter->updateColorWhite();
  211. flushPacket();
  212. }
  213. void MiLightClient::enableNightMode() {
  214. #ifdef DEBUG_CLIENT_COMMANDS
  215. Serial.println(F("MiLightClient::enableNightMode: Night mode"));
  216. #endif
  217. currentRemote->packetFormatter->enableNightMode();
  218. flushPacket();
  219. }
  220. void MiLightClient::pair() {
  221. #ifdef DEBUG_CLIENT_COMMANDS
  222. Serial.println(F("MiLightClient::pair: Pair"));
  223. #endif
  224. currentRemote->packetFormatter->pair();
  225. flushPacket();
  226. }
  227. void MiLightClient::unpair() {
  228. #ifdef DEBUG_CLIENT_COMMANDS
  229. Serial.println(F("MiLightClient::unpair: Unpair"));
  230. #endif
  231. currentRemote->packetFormatter->unpair();
  232. flushPacket();
  233. }
  234. void MiLightClient::increaseBrightness() {
  235. #ifdef DEBUG_CLIENT_COMMANDS
  236. Serial.println(F("MiLightClient::increaseBrightness: Increase brightness"));
  237. #endif
  238. currentRemote->packetFormatter->increaseBrightness();
  239. flushPacket();
  240. }
  241. void MiLightClient::decreaseBrightness() {
  242. #ifdef DEBUG_CLIENT_COMMANDS
  243. Serial.println(F("MiLightClient::decreaseBrightness: Decrease brightness"));
  244. #endif
  245. currentRemote->packetFormatter->decreaseBrightness();
  246. flushPacket();
  247. }
  248. void MiLightClient::increaseTemperature() {
  249. #ifdef DEBUG_CLIENT_COMMANDS
  250. Serial.println(F("MiLightClient::increaseTemperature: Increase temperature"));
  251. #endif
  252. currentRemote->packetFormatter->increaseTemperature();
  253. flushPacket();
  254. }
  255. void MiLightClient::decreaseTemperature() {
  256. #ifdef DEBUG_CLIENT_COMMANDS
  257. Serial.println(F("MiLightClient::decreaseTemperature: Decrease temperature"));
  258. #endif
  259. currentRemote->packetFormatter->decreaseTemperature();
  260. flushPacket();
  261. }
  262. void MiLightClient::updateTemperature(const uint8_t temperature) {
  263. #ifdef DEBUG_CLIENT_COMMANDS
  264. Serial.printf_P(PSTR("MiLightClient::updateTemperature: Set temperature to %d\n"), temperature);
  265. #endif
  266. currentRemote->packetFormatter->updateTemperature(temperature);
  267. flushPacket();
  268. }
  269. void MiLightClient::command(uint8_t command, uint8_t arg) {
  270. #ifdef DEBUG_CLIENT_COMMANDS
  271. Serial.printf_P(PSTR("MiLightClient::command: Execute command %d, argument %d\n"), command, arg);
  272. #endif
  273. currentRemote->packetFormatter->command(command, arg);
  274. flushPacket();
  275. }
  276. void MiLightClient::toggleStatus() {
  277. #ifdef DEBUG_CLIENT_COMMANDS
  278. Serial.printf_P(PSTR("MiLightClient::toggleStatus"));
  279. #endif
  280. currentRemote->packetFormatter->toggleStatus();
  281. flushPacket();
  282. }
  283. void MiLightClient::update(const JsonObject& request) {
  284. if (this->updateBeginHandler) {
  285. this->updateBeginHandler();
  286. }
  287. const uint8_t parsedStatus = this->parseStatus(request);
  288. // Always turn on first
  289. if (parsedStatus == ON) {
  290. this->updateStatus(ON);
  291. }
  292. if (request.containsKey("command")) {
  293. this->handleCommand(request["command"]);
  294. }
  295. if (request.containsKey("commands")) {
  296. JsonArray& commands = request["commands"];
  297. if (commands.success()) {
  298. for (size_t i = 0; i < commands.size(); i++) {
  299. this->handleCommand(commands.get<String>(i));
  300. }
  301. }
  302. }
  303. //Homeassistant - Handle effect
  304. if (request.containsKey("effect")) {
  305. this->handleEffect(request["effect"]);
  306. }
  307. if (request.containsKey("hue")) {
  308. this->updateHue(request["hue"]);
  309. }
  310. if (request.containsKey("saturation")) {
  311. this->updateSaturation(request["saturation"]);
  312. }
  313. // Convert RGB to HSV
  314. if (request.containsKey("color")) {
  315. uint16_t r, g, b;
  316. if (request["color"].is<JsonObject>()) {
  317. JsonObject& color = request["color"];
  318. r = color["r"];
  319. g = color["g"];
  320. b = color["b"];
  321. } else if (request["color"].is<const char*>()) {
  322. String colorStr = request["color"];
  323. char colorCStr[colorStr.length()];
  324. uint8_t parsedRgbColors[3] = {0, 0, 0};
  325. strcpy(colorCStr, colorStr.c_str());
  326. TokenIterator colorValueItr(colorCStr, strlen(colorCStr), ',');
  327. for (size_t i = 0; i < 3 && colorValueItr.hasNext(); ++i) {
  328. parsedRgbColors[i] = atoi(colorValueItr.nextToken());
  329. }
  330. r = parsedRgbColors[0];
  331. g = parsedRgbColors[1];
  332. b = parsedRgbColors[2];
  333. } else {
  334. Serial.println(F("Unknown format for `color' command"));
  335. return;
  336. }
  337. // We consider an RGB color "white" if all color intensities are roughly the
  338. // same value. An unscientific value of 10 (~4%) is chosen.
  339. if ( abs(r - g) < RGB_WHITE_THRESHOLD
  340. && abs(g - b) < RGB_WHITE_THRESHOLD
  341. && abs(r - b) < RGB_WHITE_THRESHOLD) {
  342. this->updateColorWhite();
  343. } else {
  344. double hsv[3];
  345. RGBConverter converter;
  346. converter.rgbToHsv(r, g, b, hsv);
  347. uint16_t hue = round(hsv[0]*360);
  348. uint8_t saturation = round(hsv[1]*100);
  349. this->updateHue(hue);
  350. this->updateSaturation(saturation);
  351. }
  352. }
  353. if (request.containsKey("level")) {
  354. this->updateBrightness(request["level"]);
  355. }
  356. // HomeAssistant
  357. if (request.containsKey("brightness")) {
  358. uint8_t scaledBrightness = Units::rescale(request.get<uint8_t>("brightness"), 100, 255);
  359. this->updateBrightness(scaledBrightness);
  360. }
  361. if (request.containsKey("temperature")) {
  362. this->updateTemperature(request["temperature"]);
  363. }
  364. // HomeAssistant
  365. if (request.containsKey("color_temp")) {
  366. this->updateTemperature(
  367. Units::miredsToWhiteVal(request["color_temp"], 100)
  368. );
  369. }
  370. if (request.containsKey("mode")) {
  371. this->updateMode(request["mode"]);
  372. }
  373. // Raw packet command/args
  374. if (request.containsKey("button_id") && request.containsKey("argument")) {
  375. this->command(request["button_id"], request["argument"]);
  376. }
  377. // Always turn off last
  378. if (parsedStatus == OFF) {
  379. this->updateStatus(OFF);
  380. }
  381. if (this->updateEndHandler) {
  382. this->updateEndHandler();
  383. }
  384. }
  385. void MiLightClient::handleCommand(const String& command) {
  386. if (command == "unpair") {
  387. this->unpair();
  388. } else if (command == "pair") {
  389. this->pair();
  390. } else if (command == "set_white") {
  391. this->updateColorWhite();
  392. } else if (command == "night_mode") {
  393. this->enableNightMode();
  394. } else if (command == "level_up") {
  395. this->increaseBrightness();
  396. } else if (command == "level_down") {
  397. this->decreaseBrightness();
  398. } else if (command == "temperature_up") {
  399. this->increaseTemperature();
  400. } else if (command == "temperature_down") {
  401. this->decreaseTemperature();
  402. } else if (command == "next_mode") {
  403. this->nextMode();
  404. } else if (command == "previous_mode") {
  405. this->previousMode();
  406. } else if (command == "mode_speed_down") {
  407. this->modeSpeedDown();
  408. } else if (command == "mode_speed_up") {
  409. this->modeSpeedUp();
  410. } else if (command == "toggle") {
  411. this->toggleStatus();
  412. }
  413. }
  414. void MiLightClient::handleEffect(const String& effect) {
  415. if (effect == "night_mode") {
  416. this->enableNightMode();
  417. } else if (effect == "white" || effect == "white_mode") {
  418. this->updateColorWhite();
  419. } else { // assume we're trying to set mode
  420. this->updateMode(effect.toInt());
  421. }
  422. }
  423. uint8_t MiLightClient::parseStatus(const JsonObject& object) {
  424. String strStatus;
  425. if (object.containsKey("status")) {
  426. strStatus = object.get<char*>("status");
  427. } else if (object.containsKey("state")) {
  428. strStatus = object.get<char*>("state");
  429. } else {
  430. return 255;
  431. }
  432. return (strStatus.equalsIgnoreCase("on") || strStatus.equalsIgnoreCase("true")) ? ON : OFF;
  433. }
  434. void MiLightClient::updateResendCount() {
  435. unsigned long now = millis();
  436. long millisSinceLastSend = now - lastSend;
  437. long x = (millisSinceLastSend - settings->packetRepeatThrottleThreshold);
  438. long delta = x * throttleMultiplier;
  439. this->currentResendCount = constrain(this->currentResendCount + delta, settings->packetRepeatMinimum, this->baseResendCount);
  440. this->lastSend = now;
  441. }
  442. void MiLightClient::flushPacket() {
  443. PacketStream& stream = currentRemote->packetFormatter->buildPackets();
  444. updateResendCount();
  445. while (stream.hasNext()) {
  446. write(stream.next());
  447. if (stream.hasNext()) {
  448. delay(10);
  449. }
  450. }
  451. currentRemote->packetFormatter->reset();
  452. }
  453. /*
  454. Register a callback for when packets are sent
  455. */
  456. void MiLightClient::onPacketSent(PacketSentHandler handler) {
  457. this->packetSentHandler = handler;
  458. }
  459. void MiLightClient::onUpdateBegin(EventHandler handler) {
  460. this->updateBeginHandler = handler;
  461. }
  462. void MiLightClient::onUpdateEnd(EventHandler handler) {
  463. this->updateEndHandler = handler;
  464. }