MiLightClient.cpp 13 KB

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