GroupState.cpp 29 KB

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