GroupState.cpp 23 KB

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