MiLightClient.cpp 10 KB

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