MiLightClient.cpp 20 KB

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