GroupState.cpp 28 KB

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