GroupState.cpp 26 KB

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