GroupState.cpp 29 KB

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