GroupState.cpp 27 KB

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