GroupState.cpp 27 KB

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