MiLightClient.cpp 15 KB

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