MiLightClient.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  1. #include <MiLightClient.h>
  2. #include <MiLightRadioConfig.h>
  3. #include <Arduino.h>
  4. #include <RGBConverter.h>
  5. #include <Units.h>
  6. MiLightClient::MiLightClient(
  7. MiLightRadioFactory* radioFactory,
  8. GroupStateStore* stateStore,
  9. Settings* settings
  10. )
  11. : baseResendCount(MILIGHT_DEFAULT_RESEND_COUNT),
  12. currentRadio(NULL),
  13. currentRemote(NULL),
  14. numRadios(MiLightRadioConfig::NUM_CONFIGS),
  15. packetSentHandler(NULL),
  16. updateBeginHandler(NULL),
  17. updateEndHandler(NULL),
  18. stateStore(stateStore),
  19. settings(settings),
  20. lastSend(0)
  21. {
  22. radios = new MiLightRadio*[numRadios];
  23. for (size_t i = 0; i < numRadios; i++) {
  24. radios[i] = radioFactory->create(MiLightRadioConfig::ALL_CONFIGS[i]);
  25. }
  26. }
  27. void MiLightClient::begin() {
  28. for (size_t i = 0; i < numRadios; i++) {
  29. radios[i]->begin();
  30. }
  31. switchRadio(static_cast<size_t>(0));
  32. // Little gross to do this here as it's relying on global state. A better alternative
  33. // would be to statically construct remote config factories which take in a stateStore
  34. // and settings pointer. The objects could then be initialized by calling the factory
  35. // in main.
  36. for (size_t i = 0; i < MiLightRemoteConfig::NUM_REMOTES; i++) {
  37. MiLightRemoteConfig::ALL_REMOTES[i]->packetFormatter->initialize(stateStore, settings);
  38. }
  39. }
  40. void MiLightClient::setHeld(bool held) {
  41. currentRemote->packetFormatter->setHeld(held);
  42. }
  43. size_t MiLightClient::getNumRadios() const {
  44. return numRadios;
  45. }
  46. MiLightRadio* MiLightClient::switchRadio(size_t radioIx) {
  47. if (radioIx >= getNumRadios()) {
  48. return NULL;
  49. }
  50. if (this->currentRadio != radios[radioIx]) {
  51. this->currentRadio = radios[radioIx];
  52. this->currentRadio->configure();
  53. }
  54. return this->currentRadio;
  55. }
  56. MiLightRadio* MiLightClient::switchRadio(const MiLightRemoteConfig* remoteConfig) {
  57. MiLightRadio* radio;
  58. for (int i = 0; i < numRadios; i++) {
  59. if (&this->radios[i]->config() == &remoteConfig->radioConfig) {
  60. radio = switchRadio(i);
  61. break;
  62. }
  63. }
  64. return radio;
  65. }
  66. void MiLightClient::prepare(const MiLightRemoteConfig* config,
  67. const uint16_t deviceId,
  68. const uint8_t groupId
  69. ) {
  70. switchRadio(config);
  71. this->currentRemote = config;
  72. if (deviceId >= 0 && groupId >= 0) {
  73. currentRemote->packetFormatter->prepare(deviceId, groupId);
  74. }
  75. }
  76. void MiLightClient::prepare(const MiLightRemoteType type,
  77. const uint16_t deviceId,
  78. const uint8_t groupId
  79. ) {
  80. prepare(MiLightRemoteConfig::fromType(type), deviceId, groupId);
  81. }
  82. void MiLightClient::setResendCount(const unsigned int resendCount) {
  83. this->baseResendCount = resendCount;
  84. this->currentResendCount = resendCount;
  85. this->throttleMultiplier = ceil((settings->packetRepeatThrottleSensitivity / 1000.0) * this->baseResendCount);
  86. }
  87. bool MiLightClient::available() {
  88. if (currentRadio == NULL) {
  89. return false;
  90. }
  91. return currentRadio->available();
  92. }
  93. size_t MiLightClient::read(uint8_t packet[]) {
  94. if (currentRadio == NULL) {
  95. return 0;
  96. }
  97. size_t length;
  98. currentRadio->read(packet, length);
  99. return length;
  100. }
  101. void MiLightClient::write(uint8_t packet[]) {
  102. if (currentRadio == NULL) {
  103. return;
  104. }
  105. #ifdef DEBUG_PRINTF
  106. Serial.printf_P(PSTR("Sending packet (%d repeats): \n"), this->currentResendCount);
  107. for (int i = 0; i < currentRemote->packetFormatter->getPacketLength(); i++) {
  108. Serial.printf_P(PSTR("%02X "), packet[i]);
  109. }
  110. Serial.println();
  111. int iStart = millis();
  112. #endif
  113. // send the packet out (multiple times for "reliability")
  114. for (int i = 0; i < this->currentResendCount; i++) {
  115. currentRadio->write(packet, currentRemote->packetFormatter->getPacketLength());
  116. }
  117. // if we have a packetSendHandler defined (see MiLightClient::onPacketSent), call it now that
  118. // the packet has been dispatched
  119. if (this->packetSentHandler) {
  120. this->packetSentHandler(packet, *currentRemote);
  121. }
  122. #ifdef DEBUG_PRINTF
  123. int iElapsed = millis() - iStart;
  124. Serial.print("Elapsed: ");
  125. Serial.println(iElapsed);
  126. #endif
  127. }
  128. void MiLightClient::updateColorRaw(const uint8_t color) {
  129. #ifdef DEBUG_CLIENT_COMMANDS
  130. Serial.printf_P(PSTR("MiLightClient::updateColorRaw: Change color to %d\n"), color);
  131. #endif
  132. currentRemote->packetFormatter->updateColorRaw(color);
  133. flushPacket();
  134. }
  135. void MiLightClient::updateHue(const uint16_t hue) {
  136. #ifdef DEBUG_CLIENT_COMMANDS
  137. Serial.printf_P(PSTR("MiLightClient::updateHue: Change hue to %d\n"), hue);
  138. #endif
  139. currentRemote->packetFormatter->updateHue(hue);
  140. flushPacket();
  141. }
  142. void MiLightClient::updateBrightness(const uint8_t brightness) {
  143. #ifdef DEBUG_CLIENT_COMMANDS
  144. Serial.printf_P(PSTR("MiLightClient::updateBrightness: Change brightness to %d\n"), brightness);
  145. #endif
  146. currentRemote->packetFormatter->updateBrightness(brightness);
  147. flushPacket();
  148. }
  149. void MiLightClient::updateMode(uint8_t mode) {
  150. #ifdef DEBUG_CLIENT_COMMANDS
  151. Serial.printf_P(PSTR("MiLightClient::updateMode: Change mode to %d\n"), mode);
  152. #endif
  153. currentRemote->packetFormatter->updateMode(mode);
  154. flushPacket();
  155. }
  156. void MiLightClient::nextMode() {
  157. #ifdef DEBUG_CLIENT_COMMANDS
  158. Serial.println(F("MiLightClient::nextMode: Switch to next mode"));
  159. #endif
  160. currentRemote->packetFormatter->nextMode();
  161. flushPacket();
  162. }
  163. void MiLightClient::previousMode() {
  164. #ifdef DEBUG_CLIENT_COMMANDS
  165. Serial.println(F("MiLightClient::previousMode: Switch to previous mode"));
  166. #endif
  167. currentRemote->packetFormatter->previousMode();
  168. flushPacket();
  169. }
  170. void MiLightClient::modeSpeedDown() {
  171. #ifdef DEBUG_CLIENT_COMMANDS
  172. Serial.println(F("MiLightClient::modeSpeedDown: Speed down\n"));
  173. #endif
  174. currentRemote->packetFormatter->modeSpeedDown();
  175. flushPacket();
  176. }
  177. void MiLightClient::modeSpeedUp() {
  178. #ifdef DEBUG_CLIENT_COMMANDS
  179. Serial.println(F("MiLightClient::modeSpeedUp: Speed up"));
  180. #endif
  181. currentRemote->packetFormatter->modeSpeedUp();
  182. flushPacket();
  183. }
  184. void MiLightClient::updateStatus(MiLightStatus status, uint8_t groupId) {
  185. #ifdef DEBUG_CLIENT_COMMANDS
  186. Serial.printf_P(PSTR("MiLightClient::updateStatus: Status %s, groupId %d\n"), status == MiLightStatus::OFF ? "OFF" : "ON", groupId);
  187. #endif
  188. currentRemote->packetFormatter->updateStatus(status, groupId);
  189. flushPacket();
  190. }
  191. void MiLightClient::updateStatus(MiLightStatus status) {
  192. #ifdef DEBUG_CLIENT_COMMANDS
  193. Serial.printf_P(PSTR("MiLightClient::updateStatus: Status %s\n"), status == MiLightStatus::OFF ? "OFF" : "ON");
  194. #endif
  195. currentRemote->packetFormatter->updateStatus(status);
  196. flushPacket();
  197. }
  198. void MiLightClient::updateSaturation(const uint8_t value) {
  199. #ifdef DEBUG_CLIENT_COMMANDS
  200. Serial.printf_P(PSTR("MiLightClient::updateSaturation: Saturation %d\n"), value);
  201. #endif
  202. currentRemote->packetFormatter->updateSaturation(value);
  203. flushPacket();
  204. }
  205. void MiLightClient::updateColorWhite() {
  206. #ifdef DEBUG_CLIENT_COMMANDS
  207. Serial.println(F("MiLightClient::updateColorWhite: Color white"));
  208. #endif
  209. currentRemote->packetFormatter->updateColorWhite();
  210. flushPacket();
  211. }
  212. void MiLightClient::enableNightMode() {
  213. #ifdef DEBUG_CLIENT_COMMANDS
  214. Serial.println(F("MiLightClient::enableNightMode: Night mode"));
  215. #endif
  216. currentRemote->packetFormatter->enableNightMode();
  217. flushPacket();
  218. }
  219. void MiLightClient::pair() {
  220. #ifdef DEBUG_CLIENT_COMMANDS
  221. Serial.println(F("MiLightClient::pair: Pair"));
  222. #endif
  223. currentRemote->packetFormatter->pair();
  224. flushPacket();
  225. }
  226. void MiLightClient::unpair() {
  227. #ifdef DEBUG_CLIENT_COMMANDS
  228. Serial.println(F("MiLightClient::unpair: Unpair"));
  229. #endif
  230. currentRemote->packetFormatter->unpair();
  231. flushPacket();
  232. }
  233. void MiLightClient::increaseBrightness() {
  234. #ifdef DEBUG_CLIENT_COMMANDS
  235. Serial.println(F("MiLightClient::increaseBrightness: Increase brightness"));
  236. #endif
  237. currentRemote->packetFormatter->increaseBrightness();
  238. flushPacket();
  239. }
  240. void MiLightClient::decreaseBrightness() {
  241. #ifdef DEBUG_CLIENT_COMMANDS
  242. Serial.println(F("MiLightClient::decreaseBrightness: Decrease brightness"));
  243. #endif
  244. currentRemote->packetFormatter->decreaseBrightness();
  245. flushPacket();
  246. }
  247. void MiLightClient::increaseTemperature() {
  248. #ifdef DEBUG_CLIENT_COMMANDS
  249. Serial.println(F("MiLightClient::increaseTemperature: Increase temperature"));
  250. #endif
  251. currentRemote->packetFormatter->increaseTemperature();
  252. flushPacket();
  253. }
  254. void MiLightClient::decreaseTemperature() {
  255. #ifdef DEBUG_CLIENT_COMMANDS
  256. Serial.println(F("MiLightClient::decreaseTemperature: Decrease temperature"));
  257. #endif
  258. currentRemote->packetFormatter->decreaseTemperature();
  259. flushPacket();
  260. }
  261. void MiLightClient::updateTemperature(const uint8_t temperature) {
  262. #ifdef DEBUG_CLIENT_COMMANDS
  263. Serial.printf_P(PSTR("MiLightClient::updateTemperature: Set temperature to %d\n"), temperature);
  264. #endif
  265. currentRemote->packetFormatter->updateTemperature(temperature);
  266. flushPacket();
  267. }
  268. void MiLightClient::command(uint8_t command, uint8_t arg) {
  269. #ifdef DEBUG_CLIENT_COMMANDS
  270. Serial.printf_P(PSTR("MiLightClient::command: Execute command %d, argument %d\n"), command, arg);
  271. #endif
  272. currentRemote->packetFormatter->command(command, arg);
  273. flushPacket();
  274. }
  275. void MiLightClient::update(const JsonObject& request) {
  276. if (this->updateBeginHandler) {
  277. this->updateBeginHandler();
  278. }
  279. const uint8_t parsedStatus = this->parseStatus(request);
  280. // Always turn on first
  281. if (parsedStatus == ON) {
  282. this->updateStatus(ON);
  283. }
  284. if (request.containsKey("command")) {
  285. this->handleCommand(request["command"]);
  286. }
  287. if (request.containsKey("commands")) {
  288. JsonArray& commands = request["commands"];
  289. if (commands.success()) {
  290. for (size_t i = 0; i < commands.size(); i++) {
  291. this->handleCommand(commands.get<String>(i));
  292. }
  293. }
  294. }
  295. //Homeassistant - Handle effect
  296. if (request.containsKey("effect")) {
  297. this->handleEffect(request["effect"]);
  298. }
  299. if (request.containsKey("hue")) {
  300. this->updateHue(request["hue"]);
  301. }
  302. if (request.containsKey("saturation")) {
  303. this->updateSaturation(request["saturation"]);
  304. }
  305. // Convert RGB to HSV
  306. if (request.containsKey("color")) {
  307. JsonObject& color = request["color"];
  308. int16_t r = color["r"];
  309. int16_t g = color["g"];
  310. int16_t b = color["b"];
  311. // We consider an RGB color "white" if all color intensities are roughly the
  312. // same value. An unscientific value of 10 (~4%) is chosen.
  313. if ( abs(r - g) < RGB_WHITE_THRESHOLD
  314. && abs(g - b) < RGB_WHITE_THRESHOLD
  315. && abs(r - b) < RGB_WHITE_THRESHOLD) {
  316. this->updateColorWhite();
  317. } else {
  318. double hsv[3];
  319. RGBConverter converter;
  320. converter.rgbToHsv(r, g, b, hsv);
  321. uint16_t hue = round(hsv[0]*360);
  322. uint8_t saturation = round(hsv[1]*100);
  323. this->updateHue(hue);
  324. this->updateSaturation(saturation);
  325. }
  326. }
  327. if (request.containsKey("level")) {
  328. this->updateBrightness(request["level"]);
  329. }
  330. // HomeAssistant
  331. if (request.containsKey("brightness")) {
  332. uint8_t scaledBrightness = Units::rescale(request.get<uint8_t>("brightness"), 100, 255);
  333. this->updateBrightness(scaledBrightness);
  334. }
  335. if (request.containsKey("temperature")) {
  336. this->updateTemperature(request["temperature"]);
  337. }
  338. // HomeAssistant
  339. if (request.containsKey("color_temp")) {
  340. this->updateTemperature(
  341. Units::miredsToWhiteVal(request["color_temp"], 100)
  342. );
  343. }
  344. if (request.containsKey("mode")) {
  345. this->updateMode(request["mode"]);
  346. }
  347. // Raw packet command/args
  348. if (request.containsKey("button_id") && request.containsKey("argument")) {
  349. this->command(request["button_id"], request["argument"]);
  350. }
  351. // Always turn off last
  352. if (parsedStatus == OFF) {
  353. this->updateStatus(OFF);
  354. }
  355. if (this->updateEndHandler) {
  356. this->updateEndHandler();
  357. }
  358. }
  359. void MiLightClient::handleCommand(const String& command) {
  360. if (command == "unpair") {
  361. this->unpair();
  362. } else if (command == "pair") {
  363. this->pair();
  364. } else if (command == "set_white") {
  365. this->updateColorWhite();
  366. } else if (command == "night_mode") {
  367. this->enableNightMode();
  368. } else if (command == "level_up") {
  369. this->increaseBrightness();
  370. } else if (command == "level_down") {
  371. this->decreaseBrightness();
  372. } else if (command == "temperature_up") {
  373. this->increaseTemperature();
  374. } else if (command == "temperature_down") {
  375. this->decreaseTemperature();
  376. } else if (command == "next_mode") {
  377. this->nextMode();
  378. } else if (command == "previous_mode") {
  379. this->previousMode();
  380. } else if (command == "mode_speed_down") {
  381. this->modeSpeedDown();
  382. } else if (command == "mode_speed_up") {
  383. this->modeSpeedUp();
  384. }
  385. }
  386. void MiLightClient::handleEffect(const String& effect) {
  387. if (effect == "night_mode") {
  388. this->enableNightMode();
  389. } else if (effect == "white" || effect == "white_mode") {
  390. this->updateColorWhite();
  391. } else { // assume we're trying to set mode
  392. this->updateMode(effect.toInt());
  393. }
  394. }
  395. uint8_t MiLightClient::parseStatus(const JsonObject& object) {
  396. String strStatus;
  397. if (object.containsKey("status")) {
  398. strStatus = object.get<char*>("status");
  399. } else if (object.containsKey("state")) {
  400. strStatus = object.get<char*>("state");
  401. } else {
  402. return 255;
  403. }
  404. return (strStatus.equalsIgnoreCase("on") || strStatus.equalsIgnoreCase("true")) ? ON : OFF;
  405. }
  406. void MiLightClient::updateResendCount() {
  407. unsigned long now = millis();
  408. long millisSinceLastSend = now - lastSend;
  409. long x = (millisSinceLastSend - settings->packetRepeatThrottleThreshold);
  410. long delta = x * throttleMultiplier;
  411. this->currentResendCount = constrain(this->currentResendCount + delta, settings->packetRepeatMinimum, this->baseResendCount);
  412. this->lastSend = now;
  413. }
  414. void MiLightClient::flushPacket() {
  415. PacketStream& stream = currentRemote->packetFormatter->buildPackets();
  416. updateResendCount();
  417. while (stream.hasNext()) {
  418. write(stream.next());
  419. if (stream.hasNext()) {
  420. delay(10);
  421. }
  422. }
  423. currentRemote->packetFormatter->reset();
  424. }
  425. /*
  426. Register a callback for when packets are sent
  427. */
  428. void MiLightClient::onPacketSent(PacketSentHandler handler) {
  429. this->packetSentHandler = handler;
  430. }
  431. void MiLightClient::onUpdateBegin(EventHandler handler) {
  432. this->updateBeginHandler = handler;
  433. }
  434. void MiLightClient::onUpdateEnd(EventHandler handler) {
  435. this->updateEndHandler = handler;
  436. }