MiLightClient.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  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. size_t throttleThreshold,
  10. size_t throttleSensitivity,
  11. size_t packetRepeatMinimum
  12. )
  13. : baseResendCount(MILIGHT_DEFAULT_RESEND_COUNT),
  14. currentRadio(NULL),
  15. currentRemote(NULL),
  16. numRadios(MiLightRadioConfig::NUM_CONFIGS),
  17. packetSentHandler(NULL),
  18. updateBeginHandler(NULL),
  19. updateEndHandler(NULL),
  20. stateStore(stateStore),
  21. lastSend(0),
  22. throttleThreshold(throttleThreshold),
  23. throttleSensitivity(throttleSensitivity),
  24. packetRepeatMinimum(packetRepeatMinimum)
  25. {
  26. radios = new MiLightRadio*[numRadios];
  27. for (size_t i = 0; i < numRadios; i++) {
  28. radios[i] = radioFactory->create(MiLightRadioConfig::ALL_CONFIGS[i]);
  29. }
  30. }
  31. void MiLightClient::begin() {
  32. for (size_t i = 0; i < numRadios; i++) {
  33. radios[i]->begin();
  34. }
  35. switchRadio(static_cast<size_t>(0));
  36. }
  37. void MiLightClient::setHeld(bool held) {
  38. currentRemote->packetFormatter->setHeld(held);
  39. }
  40. size_t MiLightClient::getNumRadios() const {
  41. return numRadios;
  42. }
  43. MiLightRadio* MiLightClient::switchRadio(size_t radioIx) {
  44. if (radioIx >= getNumRadios()) {
  45. return NULL;
  46. }
  47. if (this->currentRadio != radios[radioIx]) {
  48. this->currentRadio = radios[radioIx];
  49. this->currentRadio->configure();
  50. }
  51. return this->currentRadio;
  52. }
  53. MiLightRadio* MiLightClient::switchRadio(const MiLightRemoteConfig* remoteConfig) {
  54. MiLightRadio* radio;
  55. for (int i = 0; i < numRadios; i++) {
  56. if (&this->radios[i]->config() == &remoteConfig->radioConfig) {
  57. radio = switchRadio(i);
  58. break;
  59. }
  60. }
  61. return radio;
  62. }
  63. void MiLightClient::prepare(const MiLightRemoteConfig* config,
  64. const uint16_t deviceId,
  65. const uint8_t groupId
  66. ) {
  67. switchRadio(config);
  68. this->currentRemote = config;
  69. if (deviceId >= 0 && groupId >= 0) {
  70. currentRemote->packetFormatter->prepare(deviceId, groupId);
  71. }
  72. }
  73. void MiLightClient::prepare(const MiLightRemoteType type,
  74. const uint16_t deviceId,
  75. const uint8_t groupId
  76. ) {
  77. prepare(MiLightRemoteConfig::fromType(type));
  78. }
  79. void MiLightClient::setResendCount(const unsigned int resendCount) {
  80. this->baseResendCount = resendCount;
  81. this->currentResendCount = resendCount;
  82. this->throttleMultiplier = ceil((throttleSensitivity / 1000.0) * this->baseResendCount);
  83. }
  84. bool MiLightClient::available() {
  85. if (currentRadio == NULL) {
  86. return false;
  87. }
  88. return currentRadio->available();
  89. }
  90. size_t MiLightClient::read(uint8_t packet[]) {
  91. if (currentRadio == NULL) {
  92. return 0;
  93. }
  94. size_t length;
  95. currentRadio->read(packet, length);
  96. return length;
  97. }
  98. void MiLightClient::write(uint8_t packet[]) {
  99. if (currentRadio == NULL) {
  100. return;
  101. }
  102. #ifdef DEBUG_PRINTF
  103. Serial.printf("Sending packet (%d repeats): \n", this->currentResendCount);
  104. for (int i = 0; i < currentRemote->packetFormatter->getPacketLength(); i++) {
  105. Serial.printf("%02X ", packet[i]);
  106. }
  107. Serial.println();
  108. int iStart = millis();
  109. #endif
  110. for (int i = 0; i < this->currentResendCount; i++) {
  111. currentRadio->write(packet, currentRemote->packetFormatter->getPacketLength());
  112. }
  113. if (this->packetSentHandler) {
  114. this->packetSentHandler(packet, *currentRemote);
  115. }
  116. #ifdef DEBUG_PRINTF
  117. int iElapsed = millis() - iStart;
  118. Serial.print("Elapsed: ");
  119. Serial.println(iElapsed);
  120. #endif
  121. }
  122. void MiLightClient::updateColorRaw(const uint8_t color) {
  123. currentRemote->packetFormatter->updateColorRaw(color);
  124. flushPacket();
  125. }
  126. void MiLightClient::updateHue(const uint16_t hue) {
  127. currentRemote->packetFormatter->updateHue(hue);
  128. flushPacket();
  129. }
  130. void MiLightClient::updateBrightness(const uint8_t brightness) {
  131. currentRemote->packetFormatter->updateBrightness(brightness);
  132. flushPacket();
  133. }
  134. void MiLightClient::updateMode(uint8_t mode) {
  135. currentRemote->packetFormatter->updateMode(mode);
  136. flushPacket();
  137. }
  138. void MiLightClient::nextMode() {
  139. currentRemote->packetFormatter->nextMode();
  140. flushPacket();
  141. }
  142. void MiLightClient::previousMode() {
  143. currentRemote->packetFormatter->previousMode();
  144. flushPacket();
  145. }
  146. void MiLightClient::modeSpeedDown() {
  147. currentRemote->packetFormatter->modeSpeedDown();
  148. flushPacket();
  149. }
  150. void MiLightClient::modeSpeedUp() {
  151. currentRemote->packetFormatter->modeSpeedUp();
  152. flushPacket();
  153. }
  154. void MiLightClient::updateStatus(MiLightStatus status, uint8_t groupId) {
  155. currentRemote->packetFormatter->updateStatus(status, groupId);
  156. flushPacket();
  157. }
  158. void MiLightClient::updateStatus(MiLightStatus status) {
  159. currentRemote->packetFormatter->updateStatus(status);
  160. flushPacket();
  161. }
  162. void MiLightClient::updateSaturation(const uint8_t value) {
  163. currentRemote->packetFormatter->updateSaturation(value);
  164. flushPacket();
  165. }
  166. void MiLightClient::updateColorWhite() {
  167. currentRemote->packetFormatter->updateColorWhite();
  168. flushPacket();
  169. }
  170. void MiLightClient::enableNightMode() {
  171. currentRemote->packetFormatter->enableNightMode();
  172. flushPacket();
  173. }
  174. void MiLightClient::pair() {
  175. currentRemote->packetFormatter->pair();
  176. flushPacket();
  177. }
  178. void MiLightClient::unpair() {
  179. currentRemote->packetFormatter->unpair();
  180. flushPacket();
  181. }
  182. void MiLightClient::increaseBrightness() {
  183. currentRemote->packetFormatter->increaseBrightness();
  184. flushPacket();
  185. }
  186. void MiLightClient::decreaseBrightness() {
  187. currentRemote->packetFormatter->decreaseBrightness();
  188. flushPacket();
  189. }
  190. void MiLightClient::increaseTemperature() {
  191. currentRemote->packetFormatter->increaseTemperature();
  192. flushPacket();
  193. }
  194. void MiLightClient::decreaseTemperature() {
  195. currentRemote->packetFormatter->decreaseTemperature();
  196. flushPacket();
  197. }
  198. void MiLightClient::updateTemperature(const uint8_t temperature) {
  199. currentRemote->packetFormatter->updateTemperature(temperature);
  200. flushPacket();
  201. }
  202. void MiLightClient::command(uint8_t command, uint8_t arg) {
  203. currentRemote->packetFormatter->command(command, arg);
  204. flushPacket();
  205. }
  206. void MiLightClient::update(const JsonObject& request) {
  207. if (this->updateBeginHandler) {
  208. this->updateBeginHandler();
  209. }
  210. const uint8_t parsedStatus = this->parseStatus(request);
  211. // Always turn on first
  212. if (parsedStatus == ON) {
  213. this->updateStatus(ON);
  214. }
  215. if (request.containsKey("command")) {
  216. this->handleCommand(request["command"]);
  217. }
  218. if (request.containsKey("commands")) {
  219. JsonArray& commands = request["commands"];
  220. if (commands.success()) {
  221. for (size_t i = 0; i < commands.size(); i++) {
  222. this->handleCommand(commands.get<String>(i));
  223. }
  224. }
  225. }
  226. //Homeassistant - Handle effect
  227. if (request.containsKey("effect")) {
  228. this->handleEffect(request["effect"]);
  229. }
  230. if (request.containsKey("hue")) {
  231. this->updateHue(request["hue"]);
  232. }
  233. if (request.containsKey("saturation")) {
  234. this->updateSaturation(request["saturation"]);
  235. }
  236. // Convert RGB to HSV
  237. if (request.containsKey("color")) {
  238. JsonObject& color = request["color"];
  239. uint8_t r = color["r"];
  240. uint8_t g = color["g"];
  241. uint8_t b = color["b"];
  242. //If close to white
  243. if( r > 256 - RGB_WHITE_BOUNDARY && g > 256 - RGB_WHITE_BOUNDARY && b > 256 - RGB_WHITE_BOUNDARY) {
  244. this->updateColorWhite();
  245. } else {
  246. double hsv[3];
  247. RGBConverter converter;
  248. converter.rgbToHsv(r, g, b, hsv);
  249. uint16_t hue = round(hsv[0]*360);
  250. uint8_t saturation = round(hsv[1]*100);
  251. this->updateHue(hue);
  252. this->updateSaturation(saturation);
  253. }
  254. }
  255. if (request.containsKey("level")) {
  256. this->updateBrightness(request["level"]);
  257. }
  258. // HomeAssistant
  259. if (request.containsKey("brightness")) {
  260. uint8_t scaledBrightness = Units::rescale(request.get<uint8_t>("brightness"), 100, 255);
  261. this->updateBrightness(scaledBrightness);
  262. }
  263. if (request.containsKey("temperature")) {
  264. this->updateTemperature(request["temperature"]);
  265. }
  266. // HomeAssistant
  267. if (request.containsKey("color_temp")) {
  268. this->updateTemperature(
  269. Units::miredsToWhiteVal(request["color_temp"], 100)
  270. );
  271. }
  272. if (request.containsKey("mode")) {
  273. this->updateMode(request["mode"]);
  274. }
  275. // Raw packet command/args
  276. if (request.containsKey("button_id") && request.containsKey("argument")) {
  277. this->command(request["button_id"], request["argument"]);
  278. }
  279. // Always turn off last
  280. if (parsedStatus == OFF) {
  281. this->updateStatus(OFF);
  282. }
  283. if (this->updateEndHandler) {
  284. this->updateEndHandler();
  285. }
  286. }
  287. void MiLightClient::handleCommand(const String& command) {
  288. if (command == "unpair") {
  289. this->unpair();
  290. } else if (command == "pair") {
  291. this->pair();
  292. } else if (command == "set_white") {
  293. this->updateColorWhite();
  294. } else if (command == "night_mode") {
  295. this->enableNightMode();
  296. } else if (command == "level_up") {
  297. this->increaseBrightness();
  298. } else if (command == "level_down") {
  299. this->decreaseBrightness();
  300. } else if (command == "temperature_up") {
  301. this->increaseTemperature();
  302. } else if (command == "temperature_down") {
  303. this->decreaseTemperature();
  304. } else if (command == "next_mode") {
  305. this->nextMode();
  306. } else if (command == "previous_mode") {
  307. this->previousMode();
  308. } else if (command == "mode_speed_down") {
  309. this->modeSpeedDown();
  310. } else if (command == "mode_speed_up") {
  311. this->modeSpeedUp();
  312. }
  313. }
  314. void MiLightClient::handleEffect(const String& effect) {
  315. if (effect == "night_mode") {
  316. this->enableNightMode();
  317. } else if (effect == "white" || effect == "white_mode") {
  318. this->updateColorWhite();
  319. } else { // assume we're trying to set mode
  320. this->updateMode(effect.toInt());
  321. }
  322. }
  323. uint8_t MiLightClient::parseStatus(const JsonObject& object) {
  324. String strStatus;
  325. if (object.containsKey("status")) {
  326. strStatus = object.get<char*>("status");
  327. } else if (object.containsKey("state")) {
  328. strStatus = object.get<char*>("state");
  329. } else {
  330. return 255;
  331. }
  332. return (strStatus.equalsIgnoreCase("on") || strStatus.equalsIgnoreCase("true")) ? ON : OFF;
  333. }
  334. void MiLightClient::updateResendCount() {
  335. unsigned long now = millis();
  336. long millisSinceLastSend = now - lastSend;
  337. long x = (millisSinceLastSend - throttleThreshold);
  338. long delta = x * throttleMultiplier;
  339. this->currentResendCount = constrain(this->currentResendCount + delta, packetRepeatMinimum, this->baseResendCount);
  340. this->lastSend = now;
  341. }
  342. void MiLightClient::flushPacket() {
  343. PacketStream& stream = currentRemote->packetFormatter->buildPackets();
  344. updateResendCount();
  345. while (stream.hasNext()) {
  346. write(stream.next());
  347. if (stream.hasNext()) {
  348. delay(10);
  349. }
  350. }
  351. currentRemote->packetFormatter->reset();
  352. }
  353. void MiLightClient::onPacketSent(PacketSentHandler handler) {
  354. this->packetSentHandler = handler;
  355. }
  356. void MiLightClient::onUpdateBegin(EventHandler handler) {
  357. this->updateBeginHandler = handler;
  358. }
  359. void MiLightClient::onUpdateEnd(EventHandler handler) {
  360. this->updateEndHandler = handler;
  361. }