GroupState.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726
  1. #include <GroupState.h>
  2. #include <Units.h>
  3. #include <MiLightRemoteConfig.h>
  4. #include <RGBConverter.h>
  5. const BulbId DEFAULT_BULB_ID;
  6. // Number of units each increment command counts for
  7. static const uint8_t INCREMENT_COMMAND_VALUE = 10;
  8. const GroupState& GroupState::defaultState(MiLightRemoteType remoteType) {
  9. static GroupState instances[MiLightRemoteConfig::NUM_REMOTES];
  10. GroupState& state = instances[remoteType];
  11. switch (remoteType) {
  12. case REMOTE_TYPE_RGB:
  13. state.setBulbMode(BULB_MODE_COLOR);
  14. break;
  15. case REMOTE_TYPE_CCT:
  16. state.setBulbMode(BULB_MODE_WHITE);
  17. break;
  18. }
  19. return state;
  20. }
  21. BulbId::BulbId()
  22. : deviceId(0),
  23. groupId(0),
  24. deviceType(REMOTE_TYPE_UNKNOWN)
  25. { }
  26. BulbId::BulbId(const BulbId &other)
  27. : deviceId(other.deviceId),
  28. groupId(other.groupId),
  29. deviceType(other.deviceType)
  30. { }
  31. BulbId::BulbId(
  32. const uint16_t deviceId, const uint8_t groupId, const MiLightRemoteType deviceType
  33. )
  34. : deviceId(deviceId),
  35. groupId(groupId),
  36. deviceType(deviceType)
  37. { }
  38. void BulbId::operator=(const BulbId &other) {
  39. deviceId = other.deviceId;
  40. groupId = other.groupId;
  41. deviceType = other.deviceType;
  42. }
  43. // determine if now BulbId's are the same. This compared deviceID (the controller/remote ID) and
  44. // groupId (the group number on the controller, 1-4 or 1-8 depending), but ignores the deviceType
  45. // (type of controller/remote) as this doesn't directly affect the identity of the bulb
  46. bool BulbId::operator==(const BulbId &other) {
  47. return deviceId == other.deviceId
  48. && groupId == other.groupId
  49. && deviceType == other.deviceType;
  50. }
  51. GroupState::GroupState() {
  52. state.fields._state = 0;
  53. state.fields._brightness = 0;
  54. state.fields._brightnessColor = 0;
  55. state.fields._brightnessMode = 0;
  56. state.fields._hue = 0;
  57. state.fields._saturation = 0;
  58. state.fields._mode = 0;
  59. state.fields._bulbMode = 0;
  60. state.fields._kelvin = 0;
  61. state.fields._isSetState = 0;
  62. state.fields._isSetHue = 0;
  63. state.fields._isSetBrightness = 0;
  64. state.fields._isSetBrightnessColor = 0;
  65. state.fields._isSetBrightnessMode = 0;
  66. state.fields._isSetSaturation = 0;
  67. state.fields._isSetMode = 0;
  68. state.fields._isSetKelvin = 0;
  69. state.fields._isSetBulbMode = 0;
  70. state.fields._dirty = 1;
  71. state.fields._mqttDirty = 0;
  72. state.fields._isSetNightMode = 0;
  73. state.fields._isNightMode = 0;
  74. scratchpad.fields._isSetBrightnessScratch = 0;
  75. scratchpad.fields._brightnessScratch = 0;
  76. scratchpad.fields._isSetKelvinScratch = 0;
  77. scratchpad.fields._kelvinScratch = 0;
  78. }
  79. bool GroupState::isSetField(GroupStateField field) const {
  80. switch (field) {
  81. case GroupStateField::COMPUTED_COLOR:
  82. // Always set -- either send RGB color or white
  83. return true;
  84. case GroupStateField::DEVICE_ID:
  85. case GroupStateField::GROUP_ID:
  86. case GroupStateField::DEVICE_TYPE:
  87. // These are always defined
  88. return true;
  89. case GroupStateField::STATE:
  90. case GroupStateField::STATUS:
  91. return isSetState();
  92. case GroupStateField::BRIGHTNESS:
  93. case GroupStateField::LEVEL:
  94. return isSetBrightness();
  95. case GroupStateField::COLOR:
  96. case GroupStateField::HUE:
  97. return isSetHue();
  98. case GroupStateField::SATURATION:
  99. return isSetSaturation();
  100. case GroupStateField::MODE:
  101. return isSetMode();
  102. case GroupStateField::EFFECT:
  103. return isSetEffect();
  104. case GroupStateField::KELVIN:
  105. case GroupStateField::COLOR_TEMP:
  106. return isSetKelvin();
  107. case GroupStateField::BULB_MODE:
  108. return isSetBulbMode();
  109. }
  110. Serial.print(F("WARNING: tried to check if unknown field was set: "));
  111. Serial.println(static_cast<unsigned int>(field));
  112. return false;
  113. }
  114. bool GroupState::isSetScratchField(GroupStateField field) const {
  115. switch (field) {
  116. case GroupStateField::BRIGHTNESS:
  117. return scratchpad.fields._isSetBrightnessScratch;
  118. case GroupStateField::KELVIN:
  119. return scratchpad.fields._isSetKelvinScratch;
  120. }
  121. Serial.print(F("WARNING: tried to check if unknown scratch field was set: "));
  122. Serial.println(static_cast<unsigned int>(field));
  123. return false;
  124. }
  125. uint16_t GroupState::getFieldValue(GroupStateField field) const {
  126. switch (field) {
  127. case GroupStateField::STATE:
  128. case GroupStateField::STATUS:
  129. return getState();
  130. case GroupStateField::BRIGHTNESS:
  131. return getBrightness();
  132. case GroupStateField::HUE:
  133. return getHue();
  134. case GroupStateField::SATURATION:
  135. return getSaturation();
  136. case GroupStateField::MODE:
  137. return getMode();
  138. case GroupStateField::KELVIN:
  139. return getKelvin();
  140. case GroupStateField::BULB_MODE:
  141. return getBulbMode();
  142. }
  143. Serial.print(F("WARNING: tried to fetch value for unknown field: "));
  144. Serial.println(static_cast<unsigned int>(field));
  145. return 0;
  146. }
  147. uint16_t GroupState::getScratchFieldValue(GroupStateField field) const {
  148. switch (field) {
  149. case GroupStateField::BRIGHTNESS:
  150. return scratchpad.fields._brightnessScratch;
  151. case GroupStateField::KELVIN:
  152. return scratchpad.fields._kelvinScratch;
  153. }
  154. Serial.print(F("WARNING: tried to fetch value for unknown scratch field: "));
  155. Serial.println(static_cast<unsigned int>(field));
  156. return 0;
  157. }
  158. void GroupState::setFieldValue(GroupStateField field, uint16_t value) {
  159. switch (field) {
  160. case GroupStateField::STATE:
  161. case GroupStateField::STATUS:
  162. setState(static_cast<MiLightStatus>(value));
  163. break;
  164. case GroupStateField::BRIGHTNESS:
  165. setBrightness(value);
  166. break;
  167. case GroupStateField::HUE:
  168. setHue(value);
  169. break;
  170. case GroupStateField::SATURATION:
  171. setSaturation(value);
  172. break;
  173. case GroupStateField::MODE:
  174. setMode(value);
  175. break;
  176. case GroupStateField::KELVIN:
  177. setKelvin(value);
  178. break;
  179. case GroupStateField::BULB_MODE:
  180. setBulbMode(static_cast<BulbMode>(value));
  181. break;
  182. default:
  183. Serial.print(F("WARNING: tried to set value for unknown field: "));
  184. Serial.println(static_cast<unsigned int>(field));
  185. break;
  186. }
  187. }
  188. void GroupState::setScratchFieldValue(GroupStateField field, uint16_t value) {
  189. switch (field) {
  190. case GroupStateField::BRIGHTNESS:
  191. scratchpad.fields._isSetBrightnessScratch = 1;
  192. scratchpad.fields._brightnessScratch = value;
  193. break;
  194. case GroupStateField::KELVIN:
  195. scratchpad.fields._isSetKelvinScratch = 1;
  196. scratchpad.fields._kelvinScratch = value;
  197. break;
  198. default:
  199. Serial.print(F("WARNING: tried to set value for unknown scratch field: "));
  200. Serial.println(static_cast<unsigned int>(field));
  201. break;
  202. }
  203. }
  204. bool GroupState::isSetState() const { return state.fields._isSetState; }
  205. MiLightStatus GroupState::getState() const { return state.fields._state ? ON : OFF; }
  206. bool GroupState::isOn() const {
  207. return !isNightMode() && (!isSetState() || getState() == MiLightStatus::ON);
  208. }
  209. bool GroupState::setState(const MiLightStatus status) {
  210. if (!isNightMode() && isSetState() && getState() == status) {
  211. return false;
  212. }
  213. setDirty();
  214. state.fields._isSetState = 1;
  215. state.fields._state = status == ON ? 1 : 0;
  216. // Changing status will clear night mode
  217. setNightMode(false);
  218. return true;
  219. }
  220. bool GroupState::isSetBrightness() const {
  221. if (! state.fields._isSetBulbMode) {
  222. return state.fields._isSetBrightness;
  223. }
  224. switch (state.fields._bulbMode) {
  225. case BULB_MODE_WHITE:
  226. return state.fields._isSetBrightness;
  227. case BULB_MODE_COLOR:
  228. return state.fields._isSetBrightnessColor;
  229. case BULB_MODE_SCENE:
  230. return state.fields._isSetBrightnessMode;
  231. }
  232. return false;
  233. }
  234. uint8_t GroupState::getBrightness() const {
  235. switch (state.fields._bulbMode) {
  236. case BULB_MODE_WHITE:
  237. return state.fields._brightness;
  238. case BULB_MODE_COLOR:
  239. return state.fields._brightnessColor;
  240. case BULB_MODE_SCENE:
  241. return state.fields._brightnessMode;
  242. }
  243. return 0;
  244. }
  245. bool GroupState::setBrightness(uint8_t brightness) {
  246. if (isSetBrightness() && getBrightness() == brightness) {
  247. return false;
  248. }
  249. setDirty();
  250. uint8_t bulbMode = state.fields._bulbMode;
  251. if (! state.fields._isSetBulbMode) {
  252. bulbMode = BULB_MODE_WHITE;
  253. }
  254. switch (bulbMode) {
  255. case BULB_MODE_WHITE:
  256. state.fields._isSetBrightness = 1;
  257. state.fields._brightness = brightness;
  258. break;
  259. case BULB_MODE_COLOR:
  260. state.fields._isSetBrightnessColor = 1;
  261. state.fields._brightnessColor = brightness;
  262. break;
  263. case BULB_MODE_SCENE:
  264. state.fields._isSetBrightnessMode = 1;
  265. state.fields._brightnessMode = brightness;
  266. default:
  267. return false;
  268. }
  269. return true;
  270. }
  271. bool GroupState::isSetHue() const { return state.fields._isSetHue; }
  272. uint16_t GroupState::getHue() const {
  273. return Units::rescale<uint16_t, uint16_t>(state.fields._hue, 360, 255);
  274. }
  275. bool GroupState::setHue(uint16_t hue) {
  276. if (isSetHue() && getHue() == hue) {
  277. return false;
  278. }
  279. setDirty();
  280. state.fields._isSetHue = 1;
  281. state.fields._hue = Units::rescale<uint16_t, uint16_t>(hue, 255, 360);
  282. return true;
  283. }
  284. bool GroupState::isSetSaturation() const { return state.fields._isSetSaturation; }
  285. uint8_t GroupState::getSaturation() const { return state.fields._saturation; }
  286. bool GroupState::setSaturation(uint8_t saturation) {
  287. if (isSetSaturation() && getSaturation() == saturation) {
  288. return false;
  289. }
  290. setDirty();
  291. state.fields._isSetSaturation = 1;
  292. state.fields._saturation = saturation;
  293. return true;
  294. }
  295. bool GroupState::isSetMode() const { return state.fields._isSetMode; }
  296. bool GroupState::isSetEffect() const {
  297. // only BULB_MODE_COLOR does not have an effect.
  298. return isSetBulbMode() && getBulbMode() != BULB_MODE_COLOR;
  299. }
  300. uint8_t GroupState::getMode() const { return state.fields._mode; }
  301. bool GroupState::setMode(uint8_t mode) {
  302. if (isSetMode() && getMode() == mode) {
  303. return false;
  304. }
  305. setDirty();
  306. state.fields._isSetMode = 1;
  307. state.fields._mode = mode;
  308. return true;
  309. }
  310. bool GroupState::isSetKelvin() const { return state.fields._isSetKelvin; }
  311. uint8_t GroupState::getKelvin() const { return state.fields._kelvin; }
  312. uint16_t GroupState::getMireds() const {
  313. return Units::whiteValToMireds(getKelvin(), 100);
  314. }
  315. bool GroupState::setKelvin(uint8_t kelvin) {
  316. if (isSetKelvin() && getKelvin() == kelvin) {
  317. return false;
  318. }
  319. setDirty();
  320. state.fields._isSetKelvin = 1;
  321. state.fields._kelvin = kelvin;
  322. return true;
  323. }
  324. bool GroupState::setMireds(uint16_t mireds) {
  325. return setKelvin(Units::miredsToWhiteVal(mireds, 100));
  326. }
  327. bool GroupState::isSetBulbMode() const { return state.fields._isSetBulbMode; }
  328. BulbMode GroupState::getBulbMode() const {
  329. BulbMode mode;
  330. // Night mode is a transient state. When power is toggled, the bulb returns
  331. // to the state it was last in. To handle this case, night mode state is
  332. // stored separately.
  333. if (isSetNightMode() && isNightMode()) {
  334. return BULB_MODE_NIGHT;
  335. } else {
  336. return static_cast<BulbMode>(state.fields._bulbMode);
  337. }
  338. }
  339. bool GroupState::setBulbMode(BulbMode bulbMode) {
  340. if (isSetBulbMode() && getBulbMode() == bulbMode) {
  341. return false;
  342. }
  343. setDirty();
  344. // As mentioned in isSetBulbMode, NIGHT_MODE is stored separately.
  345. if (bulbMode == BULB_MODE_NIGHT) {
  346. setNightMode(true);
  347. } else {
  348. state.fields._isSetBulbMode = 1;
  349. state.fields._bulbMode = bulbMode;
  350. }
  351. return true;
  352. }
  353. bool GroupState::isSetNightMode() const { return state.fields._isSetNightMode; }
  354. bool GroupState::isNightMode() const { return state.fields._isNightMode; }
  355. bool GroupState::setNightMode(bool nightMode) {
  356. if (isSetNightMode() && isNightMode() == nightMode) {
  357. return false;
  358. }
  359. setDirty();
  360. state.fields._isSetNightMode = 1;
  361. state.fields._isNightMode = nightMode;
  362. return true;
  363. }
  364. bool GroupState::isDirty() const { return state.fields._dirty; }
  365. inline bool GroupState::setDirty() {
  366. state.fields._dirty = 1;
  367. state.fields._mqttDirty = 1;
  368. }
  369. bool GroupState::clearDirty() { state.fields._dirty = 0; }
  370. bool GroupState::isMqttDirty() const { return state.fields._mqttDirty; }
  371. bool GroupState::clearMqttDirty() { state.fields._mqttDirty = 0; }
  372. void GroupState::load(Stream& stream) {
  373. for (size_t i = 0; i < DATA_LONGS; i++) {
  374. stream.readBytes(reinterpret_cast<uint8_t*>(&state.rawData[i]), 4);
  375. }
  376. clearDirty();
  377. }
  378. void GroupState::dump(Stream& stream) const {
  379. for (size_t i = 0; i < DATA_LONGS; i++) {
  380. stream.write(reinterpret_cast<const uint8_t*>(&state.rawData[i]), 4);
  381. }
  382. }
  383. bool GroupState::applyIncrementCommand(GroupStateField field, IncrementDirection dir) {
  384. if (field != GroupStateField::KELVIN && field != GroupStateField::BRIGHTNESS) {
  385. Serial.print(F("WARNING: tried to apply increment for unsupported field: "));
  386. Serial.println(static_cast<uint8_t>(field));
  387. return false;
  388. }
  389. int8_t dirValue = static_cast<int8_t>(dir);
  390. // If there's already a known value, update it
  391. if (isSetField(field)) {
  392. int8_t currentValue = static_cast<int8_t>(getFieldValue(field));
  393. int8_t newValue = currentValue + (dirValue * INCREMENT_COMMAND_VALUE);
  394. #ifdef STATE_DEBUG
  395. debugState("Updating field from increment command");
  396. #endif
  397. // For now, assume range for both brightness and kelvin is [0, 100]
  398. setFieldValue(field, constrain(newValue, 0, 100));
  399. return true;
  400. // Otherwise start or update scratch state
  401. } else {
  402. if (isSetScratchField(field)) {
  403. int8_t newValue = static_cast<int8_t>(getScratchFieldValue(field)) + dirValue;
  404. if (newValue == 0 || newValue == 10) {
  405. setFieldValue(field, newValue * INCREMENT_COMMAND_VALUE);
  406. return true;
  407. } else {
  408. setScratchFieldValue(field, newValue);
  409. }
  410. } else if (dir == IncrementDirection::DECREASE) {
  411. setScratchFieldValue(field, 9);
  412. } else {
  413. setScratchFieldValue(field, 1);
  414. }
  415. #ifdef STATE_DEBUG
  416. Serial.print(F("Updated scratch field: "));
  417. Serial.print(static_cast<int8_t>(field));
  418. Serial.print(F(" to: "));
  419. Serial.println(getScratchFieldValue(field));
  420. #endif
  421. }
  422. return false;
  423. }
  424. /*
  425. Update group state to reflect a packet state
  426. Called both when a packet is sent locally, and when an intercepted packet is read
  427. (see main.cpp onPacketSentHandler)
  428. Returns true if the packet changes affects a state change
  429. */
  430. bool GroupState::patch(const JsonObject& state) {
  431. bool changes = false;
  432. #ifdef STATE_DEBUG
  433. Serial.print(F("Patching existing state with: "));
  434. state.printTo(Serial);
  435. Serial.println();
  436. #endif
  437. if (state.containsKey("state")) {
  438. bool stateChange = setState(state["state"] == "ON" ? ON : OFF);
  439. changes |= stateChange;
  440. }
  441. // Devices do not support changing their state while off, so don't apply state
  442. // changes to devices we know are off.
  443. if (isOn() && state.containsKey("brightness")) {
  444. bool stateChange = setBrightness(Units::rescale(state.get<uint8_t>("brightness"), 100, 255));
  445. changes |= stateChange;
  446. }
  447. if (isOn() && state.containsKey("hue")) {
  448. changes |= setHue(state["hue"]);
  449. changes |= setBulbMode(BULB_MODE_COLOR);
  450. }
  451. if (isOn() && state.containsKey("saturation")) {
  452. changes |= setSaturation(state["saturation"]);
  453. }
  454. if (isOn() && state.containsKey("mode")) {
  455. changes |= setMode(state["mode"]);
  456. changes |= setBulbMode(BULB_MODE_SCENE);
  457. }
  458. if (isOn() && state.containsKey("color_temp")) {
  459. changes |= setMireds(state["color_temp"]);
  460. changes |= setBulbMode(BULB_MODE_WHITE);
  461. }
  462. if (state.containsKey("command")) {
  463. const String& command = state["command"];
  464. if (isOn() && command == "set_white") {
  465. changes |= setBulbMode(BULB_MODE_WHITE);
  466. } else if (command == "night_mode") {
  467. changes |= setBulbMode(BULB_MODE_NIGHT);
  468. } else if (isOn() && command == "brightness_up") {
  469. changes |= applyIncrementCommand(GroupStateField::BRIGHTNESS, IncrementDirection::INCREASE);
  470. } else if (isOn() && command == "brightness_down") {
  471. changes |= applyIncrementCommand(GroupStateField::BRIGHTNESS, IncrementDirection::DECREASE);
  472. } else if (isOn() && command == "temperature_up") {
  473. changes |= applyIncrementCommand(GroupStateField::KELVIN, IncrementDirection::INCREASE);
  474. } else if (isOn() && command == "temperature_down") {
  475. changes |= applyIncrementCommand(GroupStateField::KELVIN, IncrementDirection::DECREASE);
  476. }
  477. }
  478. if (changes) {
  479. debugState("GroupState::patch: State changed");
  480. }
  481. else {
  482. debugState("GroupState::patch: State not changed");
  483. }
  484. return changes;
  485. }
  486. void GroupState::applyColor(ArduinoJson::JsonObject& state) {
  487. uint8_t rgb[3];
  488. RGBConverter converter;
  489. converter.hsvToRgb(
  490. getHue()/360.0,
  491. // Default to fully saturated
  492. (isSetSaturation() ? getSaturation() : 100)/100.0,
  493. 1,
  494. rgb
  495. );
  496. applyColor(state, rgb[0], rgb[1], rgb[2]);
  497. }
  498. void GroupState::applyColor(ArduinoJson::JsonObject& state, uint8_t r, uint8_t g, uint8_t b) {
  499. JsonObject& color = state.createNestedObject("color");
  500. color["r"] = r;
  501. color["g"] = g;
  502. color["b"] = b;
  503. }
  504. // gather partial state for a single field; see GroupState::applyState to gather many fields
  505. void GroupState::applyField(JsonObject& partialState, const BulbId& bulbId, GroupStateField field) {
  506. if (isSetField(field)) {
  507. switch (field) {
  508. case GroupStateField::STATE:
  509. case GroupStateField::STATUS:
  510. partialState[GroupStateFieldHelpers::getFieldName(field)] = getState() == ON ? "ON" : "OFF";
  511. break;
  512. case GroupStateField::BRIGHTNESS:
  513. partialState["brightness"] = Units::rescale(getBrightness(), 255, 100);
  514. break;
  515. case GroupStateField::LEVEL:
  516. partialState["level"] = getBrightness();
  517. break;
  518. case GroupStateField::BULB_MODE:
  519. partialState["bulb_mode"] = BULB_MODE_NAMES[getBulbMode()];
  520. break;
  521. case GroupStateField::COLOR:
  522. if (getBulbMode() == BULB_MODE_COLOR) {
  523. applyColor(partialState);
  524. }
  525. break;
  526. case GroupStateField::COMPUTED_COLOR:
  527. if (getBulbMode() == BULB_MODE_COLOR) {
  528. applyColor(partialState);
  529. } else {
  530. applyColor(partialState, 255, 255, 255);
  531. }
  532. break;
  533. case GroupStateField::HUE:
  534. if (getBulbMode() == BULB_MODE_COLOR) {
  535. partialState["hue"] = getHue();
  536. }
  537. break;
  538. case GroupStateField::SATURATION:
  539. if (getBulbMode() == BULB_MODE_COLOR) {
  540. partialState["saturation"] = getSaturation();
  541. }
  542. break;
  543. case GroupStateField::MODE:
  544. if (getBulbMode() == BULB_MODE_SCENE) {
  545. partialState["mode"] = getMode();
  546. }
  547. break;
  548. case GroupStateField::EFFECT:
  549. if (getBulbMode() == BULB_MODE_SCENE) {
  550. partialState["effect"] = String(getMode());
  551. } else if (getBulbMode() == BULB_MODE_WHITE) {
  552. partialState["effect"] = "white_mode";
  553. } else if (getBulbMode() == BULB_MODE_NIGHT) {
  554. partialState["effect"] = "night_mode";
  555. }
  556. break;
  557. case GroupStateField::COLOR_TEMP:
  558. if (getBulbMode() == BULB_MODE_WHITE) {
  559. partialState["color_temp"] = getMireds();
  560. }
  561. break;
  562. case GroupStateField::KELVIN:
  563. if (getBulbMode() == BULB_MODE_WHITE) {
  564. partialState["kelvin"] = getKelvin();
  565. }
  566. break;
  567. case GroupStateField::DEVICE_ID:
  568. partialState["device_id"] = bulbId.deviceId;
  569. break;
  570. case GroupStateField::GROUP_ID:
  571. partialState["group_id"] = bulbId.groupId;
  572. break;
  573. case GroupStateField::DEVICE_TYPE:
  574. const MiLightRemoteConfig* remoteConfig = MiLightRemoteConfig::fromType(bulbId.deviceType);
  575. if (remoteConfig) {
  576. partialState["device_type"] = remoteConfig->name;
  577. }
  578. break;
  579. }
  580. }
  581. }
  582. // helper function to debug the current state (in JSON) to the serial port
  583. void GroupState::debugState(char const *debugMessage) {
  584. #ifdef STATE_DEBUG
  585. // using static to keep large buffers off the call stack
  586. static StaticJsonBuffer<500> jsonBuffer;
  587. // define fields to show (if count changes, make sure to update count to applyState below)
  588. GroupStateField fields[] {
  589. GroupStateField::BRIGHTNESS,
  590. GroupStateField::BULB_MODE,
  591. GroupStateField::COLOR,
  592. GroupStateField::COLOR_TEMP,
  593. GroupStateField::COMPUTED_COLOR,
  594. GroupStateField::EFFECT,
  595. GroupStateField::HUE,
  596. GroupStateField::KELVIN,
  597. GroupStateField::LEVEL,
  598. GroupStateField::MODE,
  599. GroupStateField::SATURATION,
  600. GroupStateField::STATE,
  601. GroupStateField::STATUS };
  602. // since our buffer is reused, make sure to clear it every time
  603. jsonBuffer.clear();
  604. JsonObject& jsonState = jsonBuffer.createObject();
  605. // Fake id
  606. BulbId id;
  607. // use applyState to build JSON of all fields (from above)
  608. applyState(jsonState, id, fields, 13);
  609. // convert to string and print
  610. Serial.printf("%s: ", debugMessage);
  611. jsonState.printTo(Serial);
  612. Serial.println("");
  613. #endif
  614. }
  615. // build up a partial state representation based on the specified GrouipStateField array. Used
  616. // to gather a subset of states (configurable in the UI) for sending to MQTT and web responses.
  617. void GroupState::applyState(JsonObject& partialState, const BulbId& bulbId, GroupStateField* fields, size_t numFields) {
  618. for (size_t i = 0; i < numFields; i++) {
  619. applyField(partialState, bulbId, fields[i]);
  620. }
  621. }