GroupState.cpp 29 KB

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