GroupState.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. #include <GroupState.h>
  2. #include <Units.h>
  3. #include <MiLightRemoteConfig.h>
  4. #include <RGBConverter.h>
  5. const BulbId DEFAULT_BULB_ID;
  6. const GroupState& GroupState::defaultState(MiLightRemoteType remoteType) {
  7. static GroupState instances[MiLightRemoteConfig::NUM_REMOTES];
  8. GroupState& state = instances[remoteType];
  9. switch (remoteType) {
  10. case REMOTE_TYPE_RGB:
  11. state.setBulbMode(BULB_MODE_COLOR);
  12. break;
  13. case REMOTE_TYPE_CCT:
  14. state.setBulbMode(BULB_MODE_WHITE);
  15. break;
  16. }
  17. return state;
  18. }
  19. BulbId::BulbId()
  20. : deviceId(0),
  21. groupId(0),
  22. deviceType(REMOTE_TYPE_UNKNOWN)
  23. { }
  24. BulbId::BulbId(const BulbId &other)
  25. : deviceId(other.deviceId),
  26. groupId(other.groupId),
  27. deviceType(other.deviceType)
  28. { }
  29. BulbId::BulbId(
  30. const uint16_t deviceId, const uint8_t groupId, const MiLightRemoteType deviceType
  31. )
  32. : deviceId(deviceId),
  33. groupId(groupId),
  34. deviceType(deviceType)
  35. { }
  36. void BulbId::operator=(const BulbId &other) {
  37. deviceId = other.deviceId;
  38. groupId = other.groupId;
  39. deviceType = other.deviceType;
  40. }
  41. bool BulbId::operator==(const BulbId &other) {
  42. return deviceId == other.deviceId
  43. && groupId == other.groupId
  44. && deviceType == other.deviceType;
  45. }
  46. GroupState::GroupState() {
  47. state.fields._state = 0;
  48. state.fields._brightness = 0;
  49. state.fields._brightnessColor = 0;
  50. state.fields._brightnessMode = 0;
  51. state.fields._hue = 0;
  52. state.fields._saturation = 0;
  53. state.fields._mode = 0;
  54. state.fields._bulbMode = 0;
  55. state.fields._kelvin = 0;
  56. state.fields._isSetState = 0;
  57. state.fields._isSetHue = 0;
  58. state.fields._isSetBrightness = 0;
  59. state.fields._isSetBrightnessColor = 0;
  60. state.fields._isSetBrightnessMode = 0;
  61. state.fields._isSetSaturation = 0;
  62. state.fields._isSetMode = 0;
  63. state.fields._isSetKelvin = 0;
  64. state.fields._isSetBulbMode = 0;
  65. state.fields._dirty = 1;
  66. state.fields._mqttDirty = 0;
  67. state.fields._isSetNightMode = 0;
  68. state.fields._isNightMode = 0;
  69. }
  70. bool GroupState::isSetField(GroupStateField field) const {
  71. switch (field) {
  72. case GroupStateField::COMPUTED_COLOR:
  73. // Always set -- either send RGB color or white
  74. return true;
  75. case GroupStateField::STATE:
  76. case GroupStateField::STATUS:
  77. return isSetState();
  78. case GroupStateField::BRIGHTNESS:
  79. case GroupStateField::LEVEL:
  80. return isSetBrightness();
  81. case GroupStateField::COLOR:
  82. case GroupStateField::HUE:
  83. return isSetHue();
  84. case GroupStateField::SATURATION:
  85. return isSetSaturation();
  86. case GroupStateField::MODE:
  87. return isSetMode();
  88. case GroupStateField::EFFECT:
  89. return isSetEffect();
  90. case GroupStateField::KELVIN:
  91. case GroupStateField::COLOR_TEMP:
  92. return isSetKelvin();
  93. case GroupStateField::BULB_MODE:
  94. return isSetBulbMode();
  95. }
  96. Serial.print(F("WARNING: tried to check if unknown field was set: "));
  97. Serial.println(static_cast<unsigned int>(field));
  98. return false;
  99. }
  100. bool GroupState::isSetState() const { return state.fields._isSetState; }
  101. MiLightStatus GroupState::getState() const { return state.fields._state ? ON : OFF; }
  102. bool GroupState::setState(const MiLightStatus status) {
  103. if (isSetState() && getState() == status) {
  104. return false;
  105. }
  106. setDirty();
  107. state.fields._isSetState = 1;
  108. state.fields._state = status == ON ? 1 : 0;
  109. return true;
  110. }
  111. bool GroupState::isSetBrightness() const {
  112. if (! state.fields._isSetBulbMode) {
  113. return state.fields._isSetBrightness;
  114. }
  115. switch (state.fields._bulbMode) {
  116. case BULB_MODE_WHITE:
  117. return state.fields._isSetBrightness;
  118. case BULB_MODE_COLOR:
  119. return state.fields._isSetBrightnessColor;
  120. case BULB_MODE_SCENE:
  121. return state.fields._isSetBrightnessMode;
  122. }
  123. return false;
  124. }
  125. uint8_t GroupState::getBrightness() const {
  126. switch (state.fields._bulbMode) {
  127. case BULB_MODE_WHITE:
  128. return state.fields._brightness;
  129. case BULB_MODE_COLOR:
  130. return state.fields._brightnessColor;
  131. case BULB_MODE_SCENE:
  132. return state.fields._brightnessMode;
  133. }
  134. return 0;
  135. }
  136. bool GroupState::setBrightness(uint8_t brightness) {
  137. if (isSetBrightness() && getBrightness() == brightness) {
  138. return false;
  139. }
  140. setDirty();
  141. uint8_t bulbMode = state.fields._bulbMode;
  142. if (! state.fields._isSetBulbMode) {
  143. bulbMode = BULB_MODE_WHITE;
  144. }
  145. switch (bulbMode) {
  146. case BULB_MODE_WHITE:
  147. state.fields._isSetBrightness = 1;
  148. state.fields._brightness = brightness;
  149. break;
  150. case BULB_MODE_COLOR:
  151. state.fields._isSetBrightnessColor = 1;
  152. state.fields._brightnessColor = brightness;
  153. break;
  154. case BULB_MODE_SCENE:
  155. state.fields._isSetBrightnessMode = 1;
  156. state.fields._brightnessMode = brightness;
  157. default:
  158. return false;
  159. }
  160. return true;
  161. }
  162. bool GroupState::isSetHue() const { return state.fields._isSetHue; }
  163. uint16_t GroupState::getHue() const {
  164. return Units::rescale<uint16_t, uint16_t>(state.fields._hue, 360, 255);
  165. }
  166. bool GroupState::setHue(uint16_t hue) {
  167. if (isSetHue() && getHue() == hue) {
  168. return false;
  169. }
  170. setDirty();
  171. state.fields._isSetHue = 1;
  172. state.fields._hue = Units::rescale<uint16_t, uint16_t>(hue, 255, 360);
  173. return true;
  174. }
  175. bool GroupState::isSetSaturation() const { return state.fields._isSetSaturation; }
  176. uint8_t GroupState::getSaturation() const { return state.fields._saturation; }
  177. bool GroupState::setSaturation(uint8_t saturation) {
  178. if (isSetSaturation() && getSaturation() == saturation) {
  179. return false;
  180. }
  181. setDirty();
  182. state.fields._isSetSaturation = 1;
  183. state.fields._saturation = saturation;
  184. return true;
  185. }
  186. bool GroupState::isSetMode() const { return state.fields._isSetMode; }
  187. bool GroupState::isSetEffect() const {
  188. // only BULB_MODE_COLOR does not have an effect.
  189. return isSetBulbMode() && getBulbMode() != BULB_MODE_COLOR;
  190. }
  191. uint8_t GroupState::getMode() const { return state.fields._mode; }
  192. bool GroupState::setMode(uint8_t mode) {
  193. if (isSetMode() && getMode() == mode) {
  194. return false;
  195. }
  196. setDirty();
  197. state.fields._isSetMode = 1;
  198. state.fields._mode = mode;
  199. return true;
  200. }
  201. bool GroupState::isSetKelvin() const { return state.fields._isSetKelvin; }
  202. uint8_t GroupState::getKelvin() const { return state.fields._kelvin; }
  203. uint16_t GroupState::getMireds() const {
  204. return Units::whiteValToMireds(getKelvin(), 100);
  205. }
  206. bool GroupState::setKelvin(uint8_t kelvin) {
  207. if (isSetKelvin() && getKelvin() == kelvin) {
  208. return false;
  209. }
  210. setDirty();
  211. state.fields._isSetKelvin = 1;
  212. state.fields._kelvin = kelvin;
  213. return true;
  214. }
  215. bool GroupState::setMireds(uint16_t mireds) {
  216. return setKelvin(Units::miredsToWhiteVal(mireds, 100));
  217. }
  218. bool GroupState::isSetBulbMode() const { return state.fields._isSetBulbMode; }
  219. BulbMode GroupState::getBulbMode() const {
  220. BulbMode mode;
  221. // Night mode is a transient state. When power is toggled, the bulb returns
  222. // to the state it was last in. To handle this case, night mode state is
  223. // stored separately.
  224. if (isSetNightMode() && isNightMode()) {
  225. return BULB_MODE_NIGHT;
  226. } else {
  227. return static_cast<BulbMode>(state.fields._bulbMode);
  228. }
  229. }
  230. bool GroupState::setBulbMode(BulbMode bulbMode) {
  231. if (isSetBulbMode() && getBulbMode() == bulbMode) {
  232. return false;
  233. }
  234. setDirty();
  235. // As mentioned in isSetBulbMode, NIGHT_MODE is stored separately.
  236. if (bulbMode == BULB_MODE_NIGHT) {
  237. setNightMode(true);
  238. } else {
  239. state.fields._isSetBulbMode = 1;
  240. state.fields._bulbMode = bulbMode;
  241. }
  242. return true;
  243. }
  244. bool GroupState::isSetNightMode() const { return state.fields._isSetNightMode; }
  245. bool GroupState::isNightMode() const { return state.fields._isNightMode; }
  246. bool GroupState::setNightMode(bool nightMode) {
  247. if (isSetNightMode() && isNightMode() == nightMode) {
  248. return false;
  249. }
  250. setDirty();
  251. state.fields._isSetNightMode = 1;
  252. state.fields._isNightMode = nightMode;
  253. return true;
  254. }
  255. bool GroupState::isDirty() const { return state.fields._dirty; }
  256. inline bool GroupState::setDirty() {
  257. state.fields._dirty = 1;
  258. state.fields._mqttDirty = 1;
  259. }
  260. bool GroupState::clearDirty() { state.fields._dirty = 0; }
  261. bool GroupState::isMqttDirty() const { return state.fields._mqttDirty; }
  262. bool GroupState::clearMqttDirty() { state.fields._mqttDirty = 0; }
  263. void GroupState::load(Stream& stream) {
  264. for (size_t i = 0; i < DATA_BYTES; i++) {
  265. stream.readBytes(reinterpret_cast<uint8_t*>(&state.data[i]), 4);
  266. }
  267. clearDirty();
  268. }
  269. void GroupState::dump(Stream& stream) const {
  270. for (size_t i = 0; i < DATA_BYTES; i++) {
  271. stream.write(reinterpret_cast<const uint8_t*>(&state.data[i]), 4);
  272. }
  273. }
  274. bool GroupState::patch(const JsonObject& state) {
  275. bool changes = false;
  276. if (state.containsKey("state")) {
  277. changes |= setState(state["state"] == "ON" ? ON : OFF);
  278. }
  279. if (state.containsKey("brightness")) {
  280. changes |= setBrightness(Units::rescale(state.get<uint8_t>("brightness"), 100, 255));
  281. }
  282. if (state.containsKey("hue")) {
  283. changes |= setHue(state["hue"]);
  284. changes |= setBulbMode(BULB_MODE_COLOR);
  285. }
  286. if (state.containsKey("saturation")) {
  287. changes |= setSaturation(state["saturation"]);
  288. }
  289. if (state.containsKey("mode")) {
  290. changes |= setMode(state["mode"]);
  291. changes |= setBulbMode(BULB_MODE_SCENE);
  292. }
  293. if (state.containsKey("color_temp")) {
  294. changes |= setMireds(state["color_temp"]);
  295. changes |= setBulbMode(BULB_MODE_WHITE);
  296. }
  297. // Any changes other than setting mode to night should take device out of
  298. // night mode.
  299. if (changes && getBulbMode() == BULB_MODE_NIGHT) {
  300. setNightMode(false);
  301. }
  302. if (state.containsKey("command")) {
  303. const String& command = state["command"];
  304. if (command == "white_mode") {
  305. changes |= setBulbMode(BULB_MODE_WHITE);
  306. setNightMode(false);
  307. } else if (command == "night_mode") {
  308. changes |= setBulbMode(BULB_MODE_NIGHT);
  309. }
  310. }
  311. return changes;
  312. }
  313. void GroupState::applyColor(ArduinoJson::JsonObject& state) {
  314. uint8_t rgb[3];
  315. RGBConverter converter;
  316. converter.hsvToRgb(
  317. getHue()/360.0,
  318. // Default to fully saturated
  319. (isSetSaturation() ? getSaturation() : 100)/100.0,
  320. 1,
  321. rgb
  322. );
  323. applyColor(state, rgb[0], rgb[1], rgb[2]);
  324. }
  325. void GroupState::applyColor(ArduinoJson::JsonObject& state, uint8_t r, uint8_t g, uint8_t b) {
  326. JsonObject& color = state.createNestedObject("color");
  327. color["r"] = r;
  328. color["g"] = g;
  329. color["b"] = b;
  330. }
  331. void GroupState::applyField(JsonObject& partialState, GroupStateField field) {
  332. if (isSetField(field)) {
  333. switch (field) {
  334. case GroupStateField::STATE:
  335. case GroupStateField::STATUS:
  336. partialState[GroupStateFieldHelpers::getFieldName(field)] = getState() == ON ? "ON" : "OFF";
  337. break;
  338. case GroupStateField::BRIGHTNESS:
  339. partialState["brightness"] = Units::rescale(getBrightness(), 255, 100);
  340. break;
  341. case GroupStateField::LEVEL:
  342. partialState["level"] = getBrightness();
  343. break;
  344. case GroupStateField::BULB_MODE:
  345. partialState["bulb_mode"] = BULB_MODE_NAMES[getBulbMode()];
  346. break;
  347. case GroupStateField::COLOR:
  348. if (getBulbMode() == BULB_MODE_COLOR) {
  349. applyColor(partialState);
  350. }
  351. break;
  352. case GroupStateField::COMPUTED_COLOR:
  353. if (getBulbMode() == BULB_MODE_COLOR) {
  354. applyColor(partialState);
  355. } else {
  356. applyColor(partialState, 255, 255, 255);
  357. }
  358. break;
  359. case GroupStateField::HUE:
  360. if (getBulbMode() == BULB_MODE_COLOR) {
  361. partialState["hue"] = getHue();
  362. }
  363. break;
  364. case GroupStateField::SATURATION:
  365. if (getBulbMode() == BULB_MODE_COLOR) {
  366. partialState["saturation"] = getSaturation();
  367. }
  368. break;
  369. case GroupStateField::MODE:
  370. if (getBulbMode() == BULB_MODE_SCENE) {
  371. partialState["mode"] = getMode();
  372. }
  373. break;
  374. case GroupStateField::EFFECT:
  375. if (getBulbMode() == BULB_MODE_SCENE) {
  376. partialState["effect"] = String(getMode());
  377. } else if (getBulbMode() == BULB_MODE_WHITE) {
  378. partialState["effect"] = "white_mode";
  379. } else if (getBulbMode() == BULB_MODE_NIGHT) {
  380. partialState["effect"] = "night_mode";
  381. }
  382. break;
  383. case GroupStateField::COLOR_TEMP:
  384. if (getBulbMode() == BULB_MODE_WHITE) {
  385. partialState["color_temp"] = getMireds();
  386. }
  387. break;
  388. case GroupStateField::KELVIN:
  389. if (getBulbMode() == BULB_MODE_WHITE) {
  390. partialState["kelvin"] = getKelvin();
  391. }
  392. break;
  393. }
  394. }
  395. }
  396. void GroupState::applyState(JsonObject& partialState, GroupStateField* fields, size_t numFields) {
  397. for (size_t i = 0; i < numFields; i++) {
  398. applyField(partialState, fields[i]);
  399. }
  400. }