MiLightClient.cpp 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. #include <MiLightClient.h>
  2. #include <MiLightRadioConfig.h>
  3. #include <Arduino.h>
  4. #include <RGBConverter.h>
  5. #define COLOR_TEMP_MAX_MIREDS 370
  6. #define COLOR_TEMP_MIN_MIREDS 153
  7. MiLightClient::MiLightClient(MiLightRadioFactory* radioFactory)
  8. : resendCount(MILIGHT_DEFAULT_RESEND_COUNT),
  9. currentRadio(NULL),
  10. numRadios(MiLightRadioConfig::NUM_CONFIGS),
  11. packetSentHandler(NULL)
  12. {
  13. radios = new MiLightRadio*[numRadios];
  14. for (size_t i = 0; i < numRadios; i++) {
  15. radios[i] = radioFactory->create(*MiLightRadioConfig::ALL_CONFIGS[i]);
  16. }
  17. }
  18. void MiLightClient::begin() {
  19. for (size_t i = 0; i < numRadios; i++) {
  20. radios[i]->begin();
  21. }
  22. this->currentRadio = radios[0];
  23. this->currentRadio->configure();
  24. }
  25. void MiLightClient::setHeld(bool held) {
  26. formatter->setHeld(held);
  27. }
  28. MiLightRadio* MiLightClient::switchRadio(const MiLightRadioType type) {
  29. MiLightRadio* radio = NULL;
  30. for (int i = 0; i < numRadios; i++) {
  31. if (this->radios[i]->config().type == type) {
  32. radio = radios[i];
  33. break;
  34. }
  35. }
  36. if (radio != NULL) {
  37. if (currentRadio != radio) {
  38. radio->configure();
  39. }
  40. this->currentRadio = radio;
  41. this->formatter = radio->config().packetFormatter;
  42. return radio;
  43. } else {
  44. Serial.print(F("MiLightClient - tried to get radio for unknown type: "));
  45. Serial.println(type);
  46. }
  47. return NULL;
  48. }
  49. void MiLightClient::prepare(MiLightRadioConfig& config,
  50. const uint16_t deviceId,
  51. const uint8_t groupId) {
  52. prepare(config.type, deviceId, groupId);
  53. }
  54. void MiLightClient::prepare(MiLightRadioType type,
  55. const uint16_t deviceId,
  56. const uint8_t groupId) {
  57. switchRadio(type);
  58. if (deviceId >= 0 && groupId >= 0) {
  59. formatter->prepare(deviceId, groupId);
  60. }
  61. }
  62. void MiLightClient::setResendCount(const unsigned int resendCount) {
  63. this->resendCount = resendCount;
  64. }
  65. bool MiLightClient::available() {
  66. if (currentRadio == NULL) {
  67. return false;
  68. }
  69. return currentRadio->available();
  70. }
  71. void MiLightClient::read(uint8_t packet[]) {
  72. if (currentRadio == NULL) {
  73. return;
  74. }
  75. size_t length = currentRadio->config().getPacketLength();
  76. currentRadio->read(packet, length);
  77. }
  78. void MiLightClient::write(uint8_t packet[]) {
  79. if (currentRadio == NULL) {
  80. return;
  81. }
  82. #ifdef DEBUG_PRINTF
  83. printf("Sending packet: ");
  84. for (int i = 0; i < currentRadio->config().getPacketLength(); i++) {
  85. printf("%02X", packet[i]);
  86. }
  87. printf("\n");
  88. int iStart = millis();
  89. #endif
  90. for (int i = 0; i < this->resendCount; i++) {
  91. currentRadio->write(packet, currentRadio->config().getPacketLength());
  92. }
  93. if (this->packetSentHandler) {
  94. this->packetSentHandler(packet, currentRadio->config());
  95. }
  96. #ifdef DEBUG_PRINTF
  97. int iElapsed = millis() - iStart;
  98. Serial.print("Elapsed: ");
  99. Serial.println(iElapsed);
  100. #endif
  101. }
  102. void MiLightClient::updateColorRaw(const uint8_t color) {
  103. formatter->updateColorRaw(color);
  104. flushPacket();
  105. }
  106. void MiLightClient::updateHue(const uint16_t hue) {
  107. formatter->updateHue(hue);
  108. flushPacket();
  109. }
  110. void MiLightClient::updateBrightness(const uint8_t brightness) {
  111. formatter->updateBrightness(brightness);
  112. flushPacket();
  113. }
  114. void MiLightClient::updateMode(uint8_t mode) {
  115. formatter->updateMode(mode);
  116. flushPacket();
  117. }
  118. void MiLightClient::nextMode() {
  119. formatter->nextMode();
  120. flushPacket();
  121. }
  122. void MiLightClient::previousMode() {
  123. formatter->previousMode();
  124. flushPacket();
  125. }
  126. void MiLightClient::modeSpeedDown() {
  127. formatter->modeSpeedDown();
  128. flushPacket();
  129. }
  130. void MiLightClient::modeSpeedUp() {
  131. formatter->modeSpeedUp();
  132. flushPacket();
  133. }
  134. void MiLightClient::updateStatus(MiLightStatus status, uint8_t groupId) {
  135. formatter->updateStatus(status, groupId);
  136. flushPacket();
  137. }
  138. void MiLightClient::updateStatus(MiLightStatus status) {
  139. formatter->updateStatus(status);
  140. flushPacket();
  141. }
  142. void MiLightClient::updateSaturation(const uint8_t value) {
  143. formatter->updateSaturation(value);
  144. flushPacket();
  145. }
  146. void MiLightClient::updateColorWhite() {
  147. formatter->updateColorWhite();
  148. flushPacket();
  149. }
  150. void MiLightClient::enableNightMode() {
  151. formatter->enableNightMode();
  152. flushPacket();
  153. }
  154. void MiLightClient::pair() {
  155. formatter->pair();
  156. flushPacket();
  157. }
  158. void MiLightClient::unpair() {
  159. formatter->unpair();
  160. flushPacket();
  161. }
  162. void MiLightClient::increaseBrightness() {
  163. formatter->increaseBrightness();
  164. flushPacket();
  165. }
  166. void MiLightClient::decreaseBrightness() {
  167. formatter->decreaseBrightness();
  168. flushPacket();
  169. }
  170. void MiLightClient::increaseTemperature() {
  171. formatter->increaseTemperature();
  172. flushPacket();
  173. }
  174. void MiLightClient::decreaseTemperature() {
  175. formatter->decreaseTemperature();
  176. flushPacket();
  177. }
  178. void MiLightClient::updateTemperature(const uint8_t temperature) {
  179. formatter->updateTemperature(temperature);
  180. flushPacket();
  181. }
  182. void MiLightClient::command(uint8_t command, uint8_t arg) {
  183. formatter->command(command, arg);
  184. flushPacket();
  185. }
  186. void MiLightClient::update(const JsonObject& request) {
  187. const uint8_t parsedStatus = this->parseStatus(request);
  188. // Always turn on first
  189. if (parsedStatus == ON) {
  190. this->updateStatus(ON);
  191. }
  192. if (request.containsKey("command")) {
  193. this->handleCommand(request["command"]);
  194. }
  195. if (request.containsKey("commands")) {
  196. JsonArray& commands = request["commands"];
  197. if (commands.success()) {
  198. for (size_t i = 0; i < commands.size(); i++) {
  199. this->handleCommand(commands.get<String>(i));
  200. }
  201. }
  202. }
  203. if (request.containsKey("hue")) {
  204. this->updateHue(request["hue"]);
  205. }
  206. if (request.containsKey("saturation")) {
  207. this->updateSaturation(request["saturation"]);
  208. }
  209. // Convert RGB to HSV
  210. if (request.containsKey("color")) {
  211. JsonObject& color = request["color"];
  212. uint8_t r = color["r"];
  213. uint8_t g = color["g"];
  214. uint8_t b = color["b"];
  215. double hsv[3];
  216. RGBConverter converter;
  217. converter.rgbToHsv(r, g, b, hsv);
  218. uint16_t hue = round(hsv[0]*360);
  219. uint8_t saturation = round(hsv[1]*100);
  220. this->updateHue(hue);
  221. this->updateSaturation(saturation);
  222. }
  223. if (request.containsKey("level")) {
  224. this->updateBrightness(request["level"]);
  225. }
  226. // HomeAssistant
  227. if (request.containsKey("brightness")) {
  228. uint8_t scaledBrightness = round(request.get<uint8_t>("brightness") * (100/255.0));
  229. this->updateBrightness(scaledBrightness);
  230. }
  231. if (request.containsKey("temperature")) {
  232. this->updateTemperature(request["temperature"]);
  233. }
  234. // HomeAssistant
  235. if (request.containsKey("color_temp")) {
  236. // MiLight CCT bulbs range from 2700K-6500K, or ~370.3-153.8 mireds. Note
  237. // that mireds are inversely correlated with color temperature.
  238. uint32_t tempMireds = request["color_temp"];
  239. tempMireds = tempMireds > COLOR_TEMP_MAX_MIREDS ? COLOR_TEMP_MAX_MIREDS : tempMireds;
  240. tempMireds = tempMireds < COLOR_TEMP_MIN_MIREDS ? COLOR_TEMP_MIN_MIREDS : tempMireds;
  241. uint8_t scaledTemp = round(
  242. 100*
  243. (tempMireds - COLOR_TEMP_MIN_MIREDS)
  244. /
  245. static_cast<double>(COLOR_TEMP_MAX_MIREDS - COLOR_TEMP_MIN_MIREDS)
  246. );
  247. this->updateTemperature(100 - scaledTemp);
  248. }
  249. if (request.containsKey("mode")) {
  250. this->updateMode(request["mode"]);
  251. }
  252. // Always turn off last
  253. if (parsedStatus == OFF) {
  254. this->updateStatus(OFF);
  255. }
  256. }
  257. void MiLightClient::handleCommand(const String& command) {
  258. if (command == "unpair") {
  259. this->unpair();
  260. } else if (command == "pair") {
  261. this->pair();
  262. } else if (command == "set_white") {
  263. this->updateColorWhite();
  264. } else if (command == "night_mode") {
  265. this->enableNightMode();
  266. } else if (command == "level_up") {
  267. this->increaseBrightness();
  268. } else if (command == "level_down") {
  269. this->decreaseBrightness();
  270. } else if (command == "temperature_up") {
  271. this->increaseTemperature();
  272. } else if (command == "temperature_down") {
  273. this->decreaseTemperature();
  274. } else if (command == "next_mode") {
  275. this->nextMode();
  276. } else if (command == "previous_mode") {
  277. this->previousMode();
  278. } else if (command == "mode_speed_down") {
  279. this->modeSpeedDown();
  280. } else if (command == "mode_speed_up") {
  281. this->modeSpeedUp();
  282. }
  283. }
  284. uint8_t MiLightClient::parseStatus(const JsonObject& object) {
  285. String strStatus;
  286. if (object.containsKey("status")) {
  287. strStatus = object.get<char*>("status");
  288. } else if (object.containsKey("state")) {
  289. strStatus = object.get<char*>("state");
  290. } else {
  291. return 255;
  292. }
  293. return (strStatus.equalsIgnoreCase("on") || strStatus.equalsIgnoreCase("true")) ? ON : OFF;
  294. }
  295. void MiLightClient::formatPacket(uint8_t* packet, char* buffer) {
  296. formatter->format(packet, buffer);
  297. }
  298. void MiLightClient::flushPacket() {
  299. PacketStream& stream = formatter->buildPackets();
  300. const size_t prevNumRepeats = this->resendCount;
  301. // When sending multiple packets, normalize the number of repeats
  302. if (stream.numPackets > 1) {
  303. setResendCount(MILIGHT_DEFAULT_RESEND_COUNT);
  304. }
  305. while (stream.hasNext()) {
  306. write(stream.next());
  307. if (stream.hasNext()) {
  308. delay(10);
  309. }
  310. }
  311. setResendCount(prevNumRepeats);
  312. formatter->reset();
  313. }
  314. void MiLightClient::onPacketSent(PacketSentHandler handler) {
  315. this->packetSentHandler = handler;
  316. }