MiLightClient.cpp 8.7 KB

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