MiLightClient.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  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. #include <ParsedColor.h>
  8. #include <MiLightCommands.h>
  9. #include <functional>
  10. using namespace std::placeholders;
  11. static const uint8_t STATUS_UNDEFINED = 255;
  12. const char* MiLightClient::FIELD_ORDERINGS[] = {
  13. // These are handled manually
  14. // GroupStateFieldNames::STATE,
  15. // GroupStateFieldNames::STATUS,
  16. GroupStateFieldNames::HUE,
  17. GroupStateFieldNames::SATURATION,
  18. GroupStateFieldNames::KELVIN,
  19. GroupStateFieldNames::TEMPERATURE,
  20. GroupStateFieldNames::COLOR_TEMP,
  21. GroupStateFieldNames::MODE,
  22. GroupStateFieldNames::EFFECT,
  23. GroupStateFieldNames::COLOR,
  24. // Level/Brightness must be processed last because they're specific to a particular bulb mode.
  25. // So make sure bulb mode is set before applying level/brightness.
  26. GroupStateFieldNames::LEVEL,
  27. GroupStateFieldNames::BRIGHTNESS,
  28. GroupStateFieldNames::COMMAND,
  29. GroupStateFieldNames::COMMANDS
  30. };
  31. const std::map<const char*, std::function<void(MiLightClient*, JsonVariant)>, MiLightClient::cmp_str> MiLightClient::FIELD_SETTERS = {
  32. {
  33. GroupStateFieldNames::STATUS,
  34. [](MiLightClient* client, JsonVariant val) {
  35. client->updateStatus(parseMilightStatus(val));
  36. }
  37. },
  38. {GroupStateFieldNames::LEVEL, &MiLightClient::updateBrightness},
  39. {
  40. GroupStateFieldNames::BRIGHTNESS,
  41. [](MiLightClient* client, uint16_t arg) {
  42. client->updateBrightness(Units::rescale<uint16_t, uint16_t>(arg, 100, 255));
  43. }
  44. },
  45. {GroupStateFieldNames::HUE, &MiLightClient::updateHue},
  46. {GroupStateFieldNames::SATURATION, &MiLightClient::updateSaturation},
  47. {GroupStateFieldNames::KELVIN, &MiLightClient::updateTemperature},
  48. {GroupStateFieldNames::TEMPERATURE, &MiLightClient::updateTemperature},
  49. {
  50. GroupStateFieldNames::COLOR_TEMP,
  51. [](MiLightClient* client, uint16_t arg) {
  52. client->updateTemperature(Units::miredsToWhiteVal(arg, 100));
  53. }
  54. },
  55. {GroupStateFieldNames::MODE, &MiLightClient::updateMode},
  56. {GroupStateFieldNames::COLOR, &MiLightClient::updateColor},
  57. {GroupStateFieldNames::EFFECT, &MiLightClient::handleEffect},
  58. {GroupStateFieldNames::COMMAND, &MiLightClient::handleCommand},
  59. {GroupStateFieldNames::COMMANDS, &MiLightClient::handleCommands}
  60. };
  61. MiLightClient::MiLightClient(
  62. RadioSwitchboard& radioSwitchboard,
  63. PacketSender& packetSender,
  64. GroupStateStore* stateStore,
  65. Settings& settings,
  66. TransitionController& transitions
  67. ) : radioSwitchboard(radioSwitchboard)
  68. , updateBeginHandler(NULL)
  69. , updateEndHandler(NULL)
  70. , stateStore(stateStore)
  71. , settings(settings)
  72. , packetSender(packetSender)
  73. , transitions(transitions)
  74. , repeatsOverride(0)
  75. { }
  76. void MiLightClient::setHeld(bool held) {
  77. currentRemote->packetFormatter->setHeld(held);
  78. }
  79. void MiLightClient::prepare(
  80. const MiLightRemoteConfig* config,
  81. const uint16_t deviceId,
  82. const uint8_t groupId
  83. ) {
  84. this->currentRemote = config;
  85. if (deviceId >= 0 && groupId >= 0) {
  86. currentRemote->packetFormatter->prepare(deviceId, groupId);
  87. }
  88. this->currentState = stateStore->get(deviceId, groupId, config->type);
  89. }
  90. void MiLightClient::prepare(
  91. const MiLightRemoteType type,
  92. const uint16_t deviceId,
  93. const uint8_t groupId
  94. ) {
  95. prepare(MiLightRemoteConfig::fromType(type), deviceId, groupId);
  96. }
  97. void MiLightClient::updateColorRaw(const uint8_t color) {
  98. #ifdef DEBUG_CLIENT_COMMANDS
  99. Serial.printf_P(PSTR("MiLightClient::updateColorRaw: Change color to %d\n"), color);
  100. #endif
  101. currentRemote->packetFormatter->updateColorRaw(color);
  102. flushPacket();
  103. }
  104. void MiLightClient::updateHue(const uint16_t hue) {
  105. #ifdef DEBUG_CLIENT_COMMANDS
  106. Serial.printf_P(PSTR("MiLightClient::updateHue: Change hue to %d\n"), hue);
  107. #endif
  108. currentRemote->packetFormatter->updateHue(hue);
  109. flushPacket();
  110. }
  111. void MiLightClient::updateBrightness(const uint8_t brightness) {
  112. #ifdef DEBUG_CLIENT_COMMANDS
  113. Serial.printf_P(PSTR("MiLightClient::updateBrightness: Change brightness to %d\n"), brightness);
  114. #endif
  115. currentRemote->packetFormatter->updateBrightness(brightness);
  116. flushPacket();
  117. }
  118. void MiLightClient::updateMode(uint8_t mode) {
  119. #ifdef DEBUG_CLIENT_COMMANDS
  120. Serial.printf_P(PSTR("MiLightClient::updateMode: Change mode to %d\n"), mode);
  121. #endif
  122. currentRemote->packetFormatter->updateMode(mode);
  123. flushPacket();
  124. }
  125. void MiLightClient::nextMode() {
  126. #ifdef DEBUG_CLIENT_COMMANDS
  127. Serial.println(F("MiLightClient::nextMode: Switch to next mode"));
  128. #endif
  129. currentRemote->packetFormatter->nextMode();
  130. flushPacket();
  131. }
  132. void MiLightClient::previousMode() {
  133. #ifdef DEBUG_CLIENT_COMMANDS
  134. Serial.println(F("MiLightClient::previousMode: Switch to previous mode"));
  135. #endif
  136. currentRemote->packetFormatter->previousMode();
  137. flushPacket();
  138. }
  139. void MiLightClient::modeSpeedDown() {
  140. #ifdef DEBUG_CLIENT_COMMANDS
  141. Serial.println(F("MiLightClient::modeSpeedDown: Speed down\n"));
  142. #endif
  143. currentRemote->packetFormatter->modeSpeedDown();
  144. flushPacket();
  145. }
  146. void MiLightClient::modeSpeedUp() {
  147. #ifdef DEBUG_CLIENT_COMMANDS
  148. Serial.println(F("MiLightClient::modeSpeedUp: Speed up"));
  149. #endif
  150. currentRemote->packetFormatter->modeSpeedUp();
  151. flushPacket();
  152. }
  153. void MiLightClient::updateStatus(MiLightStatus status, uint8_t groupId) {
  154. #ifdef DEBUG_CLIENT_COMMANDS
  155. Serial.printf_P(PSTR("MiLightClient::updateStatus: Status %s, groupId %d\n"), status == MiLightStatus::OFF ? "OFF" : "ON", groupId);
  156. #endif
  157. currentRemote->packetFormatter->updateStatus(status, groupId);
  158. flushPacket();
  159. }
  160. void MiLightClient::updateStatus(MiLightStatus status) {
  161. #ifdef DEBUG_CLIENT_COMMANDS
  162. Serial.printf_P(PSTR("MiLightClient::updateStatus: Status %s\n"), status == MiLightStatus::OFF ? "OFF" : "ON");
  163. #endif
  164. currentRemote->packetFormatter->updateStatus(status);
  165. flushPacket();
  166. }
  167. void MiLightClient::updateSaturation(const uint8_t value) {
  168. #ifdef DEBUG_CLIENT_COMMANDS
  169. Serial.printf_P(PSTR("MiLightClient::updateSaturation: Saturation %d\n"), value);
  170. #endif
  171. currentRemote->packetFormatter->updateSaturation(value);
  172. flushPacket();
  173. }
  174. void MiLightClient::updateColorWhite() {
  175. #ifdef DEBUG_CLIENT_COMMANDS
  176. Serial.println(F("MiLightClient::updateColorWhite: Color white"));
  177. #endif
  178. currentRemote->packetFormatter->updateColorWhite();
  179. flushPacket();
  180. }
  181. void MiLightClient::enableNightMode() {
  182. #ifdef DEBUG_CLIENT_COMMANDS
  183. Serial.println(F("MiLightClient::enableNightMode: Night mode"));
  184. #endif
  185. currentRemote->packetFormatter->enableNightMode();
  186. flushPacket();
  187. }
  188. void MiLightClient::pair() {
  189. #ifdef DEBUG_CLIENT_COMMANDS
  190. Serial.println(F("MiLightClient::pair: Pair"));
  191. #endif
  192. currentRemote->packetFormatter->pair();
  193. flushPacket();
  194. }
  195. void MiLightClient::unpair() {
  196. #ifdef DEBUG_CLIENT_COMMANDS
  197. Serial.println(F("MiLightClient::unpair: Unpair"));
  198. #endif
  199. currentRemote->packetFormatter->unpair();
  200. flushPacket();
  201. }
  202. void MiLightClient::increaseBrightness() {
  203. #ifdef DEBUG_CLIENT_COMMANDS
  204. Serial.println(F("MiLightClient::increaseBrightness: Increase brightness"));
  205. #endif
  206. currentRemote->packetFormatter->increaseBrightness();
  207. flushPacket();
  208. }
  209. void MiLightClient::decreaseBrightness() {
  210. #ifdef DEBUG_CLIENT_COMMANDS
  211. Serial.println(F("MiLightClient::decreaseBrightness: Decrease brightness"));
  212. #endif
  213. currentRemote->packetFormatter->decreaseBrightness();
  214. flushPacket();
  215. }
  216. void MiLightClient::increaseTemperature() {
  217. #ifdef DEBUG_CLIENT_COMMANDS
  218. Serial.println(F("MiLightClient::increaseTemperature: Increase temperature"));
  219. #endif
  220. currentRemote->packetFormatter->increaseTemperature();
  221. flushPacket();
  222. }
  223. void MiLightClient::decreaseTemperature() {
  224. #ifdef DEBUG_CLIENT_COMMANDS
  225. Serial.println(F("MiLightClient::decreaseTemperature: Decrease temperature"));
  226. #endif
  227. currentRemote->packetFormatter->decreaseTemperature();
  228. flushPacket();
  229. }
  230. void MiLightClient::updateTemperature(const uint8_t temperature) {
  231. #ifdef DEBUG_CLIENT_COMMANDS
  232. Serial.printf_P(PSTR("MiLightClient::updateTemperature: Set temperature to %d\n"), temperature);
  233. #endif
  234. currentRemote->packetFormatter->updateTemperature(temperature);
  235. flushPacket();
  236. }
  237. void MiLightClient::command(uint8_t command, uint8_t arg) {
  238. #ifdef DEBUG_CLIENT_COMMANDS
  239. Serial.printf_P(PSTR("MiLightClient::command: Execute command %d, argument %d\n"), command, arg);
  240. #endif
  241. currentRemote->packetFormatter->command(command, arg);
  242. flushPacket();
  243. }
  244. void MiLightClient::toggleStatus() {
  245. #ifdef DEBUG_CLIENT_COMMANDS
  246. Serial.printf_P(PSTR("MiLightClient::toggleStatus"));
  247. #endif
  248. currentRemote->packetFormatter->toggleStatus();
  249. flushPacket();
  250. }
  251. void MiLightClient::updateColor(JsonVariant json) {
  252. ParsedColor color = ParsedColor::fromJson(json);
  253. if (!color.success) {
  254. Serial.println(F("Error parsing color field, unrecognized format"));
  255. return;
  256. }
  257. // We consider an RGB color "white" if all color intensities are roughly the
  258. // same value. An unscientific value of 10 (~4%) is chosen.
  259. if ( abs(color.r - color.g) < RGB_WHITE_THRESHOLD
  260. && abs(color.g - color.b) < RGB_WHITE_THRESHOLD
  261. && abs(color.r - color.b) < RGB_WHITE_THRESHOLD) {
  262. this->updateColorWhite();
  263. } else {
  264. this->updateHue(color.hue);
  265. this->updateSaturation(color.saturation);
  266. }
  267. }
  268. void MiLightClient::update(JsonObject request) {
  269. if (this->updateBeginHandler) {
  270. this->updateBeginHandler();
  271. }
  272. const JsonVariant status = this->extractStatus(request);
  273. const uint8_t parsedStatus = this->parseStatus(status);
  274. const JsonVariant jsonTransition = request[RequestKeys::TRANSITION];
  275. float transition = 0;
  276. if (!jsonTransition.isNull()) {
  277. if (jsonTransition.is<float>()) {
  278. transition = jsonTransition.as<float>();
  279. } else if (jsonTransition.is<size_t>()) {
  280. transition = jsonTransition.as<size_t>();
  281. } else {
  282. Serial.println(F("MiLightClient - WARN: unsupported transition type. Must be float or int."));
  283. }
  284. }
  285. JsonVariant brightness = request[GroupStateFieldNames::BRIGHTNESS];
  286. JsonVariant level = request[GroupStateFieldNames::LEVEL];
  287. const bool isBrightnessDefined = !brightness.isUndefined() || !level.isUndefined();
  288. // Always turn on first
  289. if (parsedStatus == ON) {
  290. if (transition == 0) {
  291. this->updateStatus(ON);
  292. }
  293. // Don't do an "On" transition if the bulb is already on. The reasons for this are:
  294. // * Ambiguous what the behavior should be. Should it ramp to full brightness?
  295. // * HomeAssistant is only capable of sending transitions via the `light.turn_on`
  296. // service call, which ends up sending `{"status":"ON"}`. So transitions which
  297. // have nothing to do with the status will include an "ON" command.
  298. // If the user wants to transition brightness, they can just specify a brightness in
  299. // the same command. This avoids the need to make arbitrary calls on what the
  300. // behavior should be.
  301. else if (!currentState->isSetState() || !currentState->isOn()) {
  302. // If a brightness is defined, we'll want to transition to that. Status
  303. // transitions only ramp up/down to the max/min. Otherwise, just turn the bulb on
  304. // and let field transitions handle the rest.
  305. if (!isBrightnessDefined) {
  306. handleTransition(GroupStateField::STATUS, status, transition, 0);
  307. } else {
  308. this->updateStatus(ON);
  309. if (! brightness.isUndefined()) {
  310. handleTransition(GroupStateField::BRIGHTNESS, brightness, transition, 0);
  311. } else if (! level.isUndefined()) {
  312. handleTransition(GroupStateField::LEVEL, level, transition, 0);
  313. }
  314. }
  315. }
  316. }
  317. for (const char* fieldName : FIELD_ORDERINGS) {
  318. if (request.containsKey(fieldName)) {
  319. auto handler = FIELD_SETTERS.find(fieldName);
  320. JsonVariant value = request[fieldName];
  321. if (handler != FIELD_SETTERS.end()) {
  322. // No transition -- set field directly
  323. if (transition == 0) {
  324. handler->second(this, value);
  325. } else {
  326. GroupStateField field = GroupStateFieldHelpers::getFieldByName(fieldName);
  327. if ( !GroupStateFieldHelpers::isBrightnessField(field) // If field isn't brightness
  328. || parsedStatus == STATUS_UNDEFINED // or if there was not a status field
  329. || currentState->isOn() // or if bulb was already on
  330. ) {
  331. handleTransition(field, value, transition);
  332. }
  333. }
  334. }
  335. }
  336. }
  337. // Raw packet command/args
  338. if (request.containsKey("button_id") && request.containsKey("argument")) {
  339. this->command(request["button_id"], request["argument"]);
  340. }
  341. // Always turn off last
  342. if (parsedStatus == OFF) {
  343. if (transition == 0) {
  344. this->updateStatus(OFF);
  345. } else {
  346. handleTransition(GroupStateField::STATUS, status, transition);
  347. }
  348. }
  349. if (this->updateEndHandler) {
  350. this->updateEndHandler();
  351. }
  352. }
  353. void MiLightClient::handleCommands(JsonArray commands) {
  354. if (! commands.isNull()) {
  355. for (size_t i = 0; i < commands.size(); i++) {
  356. this->handleCommand(commands[i]);
  357. }
  358. }
  359. }
  360. void MiLightClient::handleCommand(JsonVariant command) {
  361. String cmdName;
  362. JsonObject args;
  363. if (command.is<JsonObject>()) {
  364. JsonObject cmdObj = command.as<JsonObject>();
  365. cmdName = cmdObj[GroupStateFieldNames::COMMAND].as<const char*>();
  366. args = cmdObj["args"];
  367. } else if (command.is<const char*>()) {
  368. cmdName = command.as<const char*>();
  369. }
  370. if (cmdName == MiLightCommandNames::UNPAIR) {
  371. this->unpair();
  372. } else if (cmdName == MiLightCommandNames::PAIR) {
  373. this->pair();
  374. } else if (cmdName == MiLightCommandNames::SET_WHITE) {
  375. this->updateColorWhite();
  376. } else if (cmdName == MiLightCommandNames::NIGHT_MODE) {
  377. this->enableNightMode();
  378. } else if (cmdName == MiLightCommandNames::LEVEL_UP) {
  379. this->increaseBrightness();
  380. } else if (cmdName == MiLightCommandNames::LEVEL_DOWN) {
  381. this->decreaseBrightness();
  382. } else if (cmdName == MiLightCommandNames::TEMPERATURE_UP) {
  383. this->increaseTemperature();
  384. } else if (cmdName == MiLightCommandNames::TEMPERATURE_DOWN) {
  385. this->decreaseTemperature();
  386. } else if (cmdName == MiLightCommandNames::NEXT_MODE) {
  387. this->nextMode();
  388. } else if (cmdName == MiLightCommandNames::PREVIOUS_MODE) {
  389. this->previousMode();
  390. } else if (cmdName == MiLightCommandNames::MODE_SPEED_DOWN) {
  391. this->modeSpeedDown();
  392. } else if (cmdName == MiLightCommandNames::MODE_SPEED_UP) {
  393. this->modeSpeedUp();
  394. } else if (cmdName == MiLightCommandNames::TOGGLE) {
  395. this->toggleStatus();
  396. } else if (cmdName == MiLightCommandNames::TRANSITION) {
  397. StaticJsonDocument<100> fakedoc;
  398. this->handleTransition(args, fakedoc);
  399. }
  400. }
  401. void MiLightClient::handleTransition(GroupStateField field, JsonVariant value, float duration, int16_t startValue) {
  402. BulbId bulbId = currentRemote->packetFormatter->currentBulbId();
  403. std::shared_ptr<Transition::Builder> transitionBuilder = nullptr;
  404. if (currentState == nullptr) {
  405. Serial.println(F("Error planning transition: could not find current bulb state."));
  406. return;
  407. }
  408. if (!currentState->isSetField(field)) {
  409. Serial.println(F("Error planning transition: current state for field could not be determined"));
  410. return;
  411. }
  412. if (field == GroupStateField::COLOR) {
  413. ParsedColor currentColor = currentState->getColor();
  414. ParsedColor endColor = ParsedColor::fromJson(value);
  415. transitionBuilder = transitions.buildColorTransition(
  416. bulbId,
  417. currentColor,
  418. endColor
  419. );
  420. } else if (field == GroupStateField::STATUS || field == GroupStateField::STATE) {
  421. uint8_t startLevel;
  422. MiLightStatus status = parseMilightStatus(value);
  423. if (startValue == FETCH_VALUE_FROM_STATE || currentState->isOn()) {
  424. startLevel = currentState->getBrightness();
  425. } else {
  426. startLevel = startValue;
  427. }
  428. transitionBuilder = transitions.buildStatusTransition(bulbId, status, startLevel);
  429. } else {
  430. uint16_t currentValue;
  431. uint16_t endValue = value;
  432. if (startValue == FETCH_VALUE_FROM_STATE || currentState->isOn()) {
  433. currentValue = currentState->getParsedFieldValue(field);
  434. } else {
  435. currentValue = startValue;
  436. }
  437. transitionBuilder = transitions.buildFieldTransition(
  438. bulbId,
  439. field,
  440. currentValue,
  441. endValue
  442. );
  443. }
  444. if (transitionBuilder == nullptr) {
  445. Serial.printf_P(PSTR("Unsupported transition field: %s\n"), GroupStateFieldHelpers::getFieldName(field));
  446. return;
  447. }
  448. transitionBuilder->setDuration(duration);
  449. transitions.addTransition(transitionBuilder->build());
  450. }
  451. bool MiLightClient::handleTransition(JsonObject args, JsonDocument& responseObj) {
  452. if (! args.containsKey(FS(TransitionParams::FIELD))
  453. || ! args.containsKey(FS(TransitionParams::END_VALUE))) {
  454. responseObj[F("error")] = F("Ignoring transition missing required arguments");
  455. return false;
  456. }
  457. const BulbId& bulbId = currentRemote->packetFormatter->currentBulbId();
  458. const char* fieldName = args[FS(TransitionParams::FIELD)];
  459. JsonVariant startValue = args[FS(TransitionParams::START_VALUE)];
  460. JsonVariant endValue = args[FS(TransitionParams::END_VALUE)];
  461. GroupStateField field = GroupStateFieldHelpers::getFieldByName(fieldName);
  462. std::shared_ptr<Transition::Builder> transitionBuilder = nullptr;
  463. if (field == GroupStateField::UNKNOWN) {
  464. char errorMsg[30];
  465. sprintf_P(errorMsg, PSTR("Unknown transition field: %s\n"), fieldName);
  466. responseObj[F("error")] = errorMsg;
  467. return false;
  468. }
  469. // These fields can be transitioned directly.
  470. switch (field) {
  471. case GroupStateField::HUE:
  472. case GroupStateField::SATURATION:
  473. case GroupStateField::BRIGHTNESS:
  474. case GroupStateField::LEVEL:
  475. case GroupStateField::KELVIN:
  476. case GroupStateField::COLOR_TEMP:
  477. transitionBuilder = transitions.buildFieldTransition(
  478. bulbId,
  479. field,
  480. startValue.isUndefined()
  481. ? currentState->getParsedFieldValue(field)
  482. : startValue.as<uint16_t>(),
  483. endValue
  484. );
  485. break;
  486. default:
  487. break;
  488. }
  489. // Color can be decomposed into hue/saturation and these can be transitioned separately
  490. if (field == GroupStateField::COLOR) {
  491. ParsedColor _startValue = startValue.isUndefined()
  492. ? currentState->getColor()
  493. : ParsedColor::fromJson(startValue);
  494. ParsedColor endColor = ParsedColor::fromJson(endValue);
  495. if (! _startValue.success) {
  496. responseObj[F("error")] = F("Transition - error parsing start color");
  497. return false;
  498. }
  499. if (! endColor.success) {
  500. responseObj[F("error")] = F("Transition - error parsing end color");
  501. return false;
  502. }
  503. transitionBuilder = transitions.buildColorTransition(
  504. bulbId,
  505. _startValue,
  506. endColor
  507. );
  508. }
  509. // Status is handled a little differently
  510. if (field == GroupStateField::STATUS || field == GroupStateField::STATE) {
  511. MiLightStatus toStatus = parseMilightStatus(endValue);
  512. uint8_t startLevel;
  513. if (currentState->isSetBrightness()) {
  514. startLevel = currentState->getBrightness();
  515. } else if (toStatus == ON) {
  516. startLevel = 0;
  517. } else {
  518. startLevel = 100;
  519. }
  520. transitionBuilder = transitions.buildStatusTransition(bulbId, toStatus, startLevel);
  521. }
  522. if (transitionBuilder == nullptr) {
  523. char errorMsg[30];
  524. sprintf_P(errorMsg, PSTR("Recognized, but unsupported transition field: %s\n"), fieldName);
  525. responseObj[F("error")] = errorMsg;
  526. return false;
  527. }
  528. if (args.containsKey(FS(TransitionParams::DURATION))) {
  529. transitionBuilder->setDuration(args[FS(TransitionParams::DURATION)]);
  530. }
  531. if (args.containsKey(FS(TransitionParams::PERIOD))) {
  532. transitionBuilder->setPeriod(args[FS(TransitionParams::PERIOD)]);
  533. }
  534. transitions.addTransition(transitionBuilder->build());
  535. return true;
  536. }
  537. void MiLightClient::handleEffect(const String& effect) {
  538. if (effect == MiLightCommandNames::NIGHT_MODE) {
  539. this->enableNightMode();
  540. } else if (effect == "white" || effect == "white_mode") {
  541. this->updateColorWhite();
  542. } else { // assume we're trying to set mode
  543. this->updateMode(effect.toInt());
  544. }
  545. }
  546. JsonVariant MiLightClient::extractStatus(JsonObject object) {
  547. JsonVariant status;
  548. if (object.containsKey(FS(GroupStateFieldNames::STATUS))) {
  549. return object[FS(GroupStateFieldNames::STATUS)];
  550. } else {
  551. return object[FS(GroupStateFieldNames::STATE)];
  552. }
  553. }
  554. uint8_t MiLightClient::parseStatus(JsonVariant val) {
  555. if (val.isUndefined()) {
  556. return STATUS_UNDEFINED;
  557. }
  558. return parseMilightStatus(val);
  559. }
  560. void MiLightClient::setRepeatsOverride(size_t repeats) {
  561. this->repeatsOverride = repeats;
  562. }
  563. void MiLightClient::clearRepeatsOverride() {
  564. this->repeatsOverride = PacketSender::DEFAULT_PACKET_SENDS_VALUE;
  565. }
  566. void MiLightClient::flushPacket() {
  567. PacketStream& stream = currentRemote->packetFormatter->buildPackets();
  568. while (stream.hasNext()) {
  569. packetSender.enqueue(stream.next(), currentRemote, repeatsOverride);
  570. }
  571. currentRemote->packetFormatter->reset();
  572. }
  573. void MiLightClient::onUpdateBegin(EventHandler handler) {
  574. this->updateBeginHandler = handler;
  575. }
  576. void MiLightClient::onUpdateEnd(EventHandler handler) {
  577. this->updateEndHandler = handler;
  578. }