MiLightClient.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. #include <MiLightClient.h>
  2. #include <MiLightRadioConfig.h>
  3. #include <Arduino.h>
  4. #define V2_OFFSET(byte, key) ( V2_OFFSETS[byte-1][key%4] )
  5. uint8_t const MiLightClient::V2_OFFSETS[][4] = {
  6. { 0x45, 0x1F, 0x14, 0x5C },
  7. { 0xAB, 0x49, 0x63, 0x91 },
  8. { 0x2D, 0x1F, 0x4A, 0xEB },
  9. { 0xAF, 0x03, 0x1D, 0xF3 },
  10. { 0x5A, 0x22, 0x30, 0x11 },
  11. { 0x04, 0xD8, 0x71, 0x42 },
  12. { 0xAF, 0x04, 0xDD, 0x07 },
  13. { 0xE1, 0x93, 0xB8, 0xE4 }
  14. };
  15. MiLightRadio* MiLightClient::getRadio(const MiLightRadioType type) {
  16. MiLightRadioStack* stack = NULL;
  17. if (type == RGBW) {
  18. stack = rgbwRadio;
  19. } else if (type == CCT) {
  20. stack = cctRadio;
  21. } else if (type == RGB_CCT) {
  22. stack = rgbCctRadio;
  23. }
  24. if (stack != NULL) {
  25. MiLightRadio *radio = stack->getRadio();
  26. if (currentRadio != stack->type) {
  27. radio->configure();
  28. }
  29. currentRadio = stack->type;
  30. return radio;
  31. }
  32. return NULL;
  33. }
  34. void MiLightClient::setResendCount(const unsigned int resendCount) {
  35. this->resendCount = resendCount;
  36. }
  37. uint8_t MiLightClient::nextSequenceNum() {
  38. return sequenceNum++;
  39. }
  40. bool MiLightClient::available(const MiLightRadioType radioType) {
  41. MiLightRadio* radio = getRadio(radioType);
  42. if (radio == NULL) {
  43. return false;
  44. }
  45. return radio->available();
  46. }
  47. void MiLightClient::read(const MiLightRadioType radioType, uint8_t packet[]) {
  48. MiLightRadio* radio = getRadio(radioType);
  49. if (radio == NULL) {
  50. return;
  51. }
  52. size_t length;
  53. radio->read(packet, length);
  54. }
  55. void MiLightClient::write(const MiLightRadioConfig& radioConfig,
  56. uint8_t packet[]) {
  57. MiLightRadio* radio = getRadio(radioConfig.type);
  58. if (radio == NULL) {
  59. return;
  60. }
  61. for (int i = 0; i < this->resendCount; i++) {
  62. radio->write(packet, radioConfig.packetLength);
  63. }
  64. }
  65. void MiLightClient::writeRgbw(
  66. const uint16_t deviceId,
  67. const uint8_t color,
  68. const uint8_t brightness,
  69. const uint8_t groupId,
  70. const uint8_t button) {
  71. uint8_t packet[MilightRgbwConfig.packetLength];
  72. size_t packetPtr = 0;
  73. packet[packetPtr++] = RGBW;
  74. packet[packetPtr++] = deviceId >> 8;
  75. packet[packetPtr++] = deviceId & 0xFF;
  76. packet[packetPtr++] = color;
  77. packet[packetPtr++] = (brightness << 3) | (groupId & 0x07);
  78. packet[packetPtr++] = button;
  79. packet[packetPtr++] = nextSequenceNum();
  80. write(MilightRgbwConfig, packet);
  81. }
  82. void MiLightClient::writeCct(const uint16_t deviceId,
  83. const uint8_t groupId,
  84. const uint8_t button) {
  85. uint8_t packet[MilightRgbwConfig.packetLength];
  86. uint8_t sequenceNum = nextSequenceNum();
  87. size_t packetPtr = 0;
  88. packet[packetPtr++] = CCT;
  89. packet[packetPtr++] = deviceId >> 8;
  90. packet[packetPtr++] = deviceId & 0xFF;
  91. packet[packetPtr++] = groupId;
  92. packet[packetPtr++] = button;
  93. packet[packetPtr++] = sequenceNum;
  94. packet[packetPtr++] = sequenceNum;
  95. write(MilightCctConfig, packet);
  96. }
  97. void MiLightClient::writeRgbCct(const uint16_t deviceId,
  98. const uint8_t command,
  99. const uint8_t arg,
  100. const uint8_t group) {
  101. uint8_t packet[MilightRgbCctConfig.packetLength];
  102. uint8_t sequenceNum = nextSequenceNum();
  103. size_t packetPtr = 0;
  104. packet[packetPtr++] = 0x00;
  105. packet[packetPtr++] = RGB_CCT;
  106. packet[packetPtr++] = deviceId >> 8;
  107. packet[packetPtr++] = deviceId & 0xFF;
  108. packet[packetPtr++] = command;
  109. packet[packetPtr++] = arg;
  110. packet[packetPtr++] = sequenceNum;
  111. packet[packetPtr++] = group;
  112. packet[packetPtr++] = 0;
  113. printf("Constructed raw packet: ");
  114. for (int i = 0; i < MilightRgbCctConfig.packetLength; i++) {
  115. printf("%02X ", packet[i]);
  116. }
  117. printf("\n");
  118. encodeV2Packet(packet);
  119. write(MilightRgbCctConfig, packet);
  120. }
  121. void MiLightClient::updateColorRaw(const uint16_t deviceId, const uint8_t groupId, const uint16_t color) {
  122. writeRgbw(deviceId, color, 0, groupId, RGBW_COLOR);
  123. }
  124. void MiLightClient::updateHue(const uint16_t deviceId, const uint8_t groupId, const uint16_t hue) {
  125. // Map color as a Hue value in [0, 359] to [0, 255]. The protocol also has
  126. // 0 being roughly magenta (#FF00FF)
  127. const int16_t remappedColor = (hue + 40) % 360;
  128. const uint8_t adjustedColor = round(remappedColor * (255 / 360.0));
  129. writeRgbw(deviceId, adjustedColor, 0, groupId, RGBW_COLOR);
  130. }
  131. void MiLightClient::updateBrightness(const uint16_t deviceId, const uint8_t groupId, const uint8_t brightness) {
  132. // Expect an input value in [0, 100]. Map it down to [0, 25].
  133. const uint8_t adjustedBrightness = round(brightness * (25 / 100.0));
  134. // The actual protocol uses a bizarre range where min is 16, max is 23:
  135. // [16, 15, ..., 0, 31, ..., 23]
  136. const uint8_t packetBrightnessValue = (
  137. ((31 - adjustedBrightness) + 17) % 32
  138. );
  139. writeRgbw(deviceId, 0, packetBrightnessValue, groupId, RGBW_BRIGHTNESS);
  140. }
  141. void MiLightClient::updateStatus(const MiLightRadioType type, const uint16_t deviceId, const uint8_t groupId, MiLightStatus status) {
  142. if (type == RGBW) {
  143. uint8_t button = RGBW_GROUP_1_ON + ((groupId - 1)*2) + status;
  144. writeRgbw(deviceId, 0, 0, groupId, button);
  145. } else if (type == RGB_CCT) {
  146. writeRgbCct(
  147. deviceId,
  148. RGB_CCT_ON,
  149. 0xC0 + groupId + (status == OFF ? 0x05 : 0x00),
  150. groupId
  151. );
  152. } else {
  153. writeCct(deviceId, groupId, getCctStatusButton(groupId, status));
  154. }
  155. }
  156. void MiLightClient::updateColorWhite(const uint16_t deviceId, const uint8_t groupId) {
  157. uint8_t button = RGBW_GROUP_1_MAX_LEVEL + ((groupId - 1)*2);
  158. pressButton(RGBW, deviceId, groupId, button);
  159. }
  160. void MiLightClient::pair(const MiLightRadioType type, const uint16_t deviceId, const uint8_t groupId) {
  161. if (type == RGBW || type == CCT) {
  162. updateStatus(type, deviceId, groupId, ON);
  163. } else if (type == RGB_CCT) {
  164. updateStatus(type, deviceId, groupId, ON);
  165. delay(1);
  166. updateStatus(type, deviceId, groupId, ON);
  167. }
  168. }
  169. void MiLightClient::unpair(const MiLightRadioType type, const uint16_t deviceId, const uint8_t groupId) {
  170. if (type == RGBW) {
  171. updateStatus(type, deviceId, groupId, ON);
  172. delay(1);
  173. updateColorWhite(deviceId, groupId);
  174. } else if (type == CCT) {
  175. for (int i = 0; i < 5; i++) {
  176. updateStatus(type, deviceId, groupId, ON);
  177. delay(1);
  178. }
  179. } else if (type == RGB_CCT) {
  180. for (int i = 0; i < 5; i++) {
  181. updateStatus(type, deviceId, 0, ON);
  182. delay(1);
  183. }
  184. }
  185. }
  186. void MiLightClient::pressButton(const MiLightRadioType type, const uint16_t deviceId, const uint8_t groupId, const uint8_t button) {
  187. if (type == RGBW) {
  188. writeRgbw(deviceId, 0, 0, groupId, button);
  189. } else if (type == CCT) {
  190. writeCct(deviceId, groupId, button);
  191. }
  192. }
  193. void MiLightClient::allOn(const MiLightRadioType type, const uint16_t deviceId) {
  194. if (type == RGBW) {
  195. writeRgbw(deviceId, 0, 0, 0, RGBW_ALL_ON);
  196. } else if (type == CCT) {
  197. writeCct(deviceId, 0, CCT_ALL_ON);
  198. } else if (type == RGB_CCT) {
  199. updateStatus(RGB_CCT, deviceId, 0, ON);
  200. }
  201. }
  202. void MiLightClient::allOff(const MiLightRadioType type, const uint16_t deviceId) {
  203. if (type == RGBW) {
  204. writeRgbw(deviceId, 0, 0, 0, RGBW_ALL_OFF);
  205. } else if (type == CCT) {
  206. writeCct(deviceId, 0, CCT_ALL_OFF);
  207. } else if (type == RGB_CCT) {
  208. updateStatus(RGB_CCT, deviceId, 0, OFF);
  209. }
  210. }
  211. void MiLightClient::increaseCctBrightness(const uint16_t deviceId, const uint8_t groupId) {
  212. writeCct(deviceId, groupId, CCT_BRIGHTNESS_UP);
  213. }
  214. void MiLightClient::decreaseCctBrightness(const uint16_t deviceId, const uint8_t groupId) {
  215. writeCct(deviceId, groupId, CCT_BRIGHTNESS_DOWN);
  216. }
  217. void MiLightClient::updateCctBrightness(const uint16_t deviceId, const uint8_t groupId, const uint8_t brightness) {
  218. for (int i = 0; i < MILIGHT_CCT_INTERVALS; i++) {
  219. decreaseCctBrightness(deviceId, groupId);
  220. }
  221. for (int i = 0; i < brightness/10; i++) {
  222. increaseCctBrightness(deviceId, groupId);
  223. }
  224. }
  225. void MiLightClient::increaseTemperature(const uint16_t deviceId, const uint8_t groupId) {
  226. writeCct(deviceId, groupId, CCT_TEMPERATURE_UP);
  227. }
  228. void MiLightClient::decreaseTemperature(const uint16_t deviceId, const uint8_t groupId) {
  229. writeCct(deviceId, groupId, CCT_TEMPERATURE_DOWN);
  230. }
  231. void MiLightClient::updateTemperature(const uint16_t deviceId, const uint8_t groupId, const uint8_t temperature) {
  232. for (int i = 0; i < MILIGHT_CCT_INTERVALS; i++) {
  233. decreaseTemperature(deviceId, groupId);
  234. }
  235. for (int i = 0; i < temperature; i++) {
  236. increaseTemperature(deviceId, groupId);
  237. }
  238. }
  239. uint8_t MiLightClient::getCctStatusButton(uint8_t groupId, MiLightStatus status) {
  240. uint8_t button = 0;
  241. if (status == ON) {
  242. switch(groupId) {
  243. case 1:
  244. button = CCT_GROUP_1_ON;
  245. break;
  246. case 2:
  247. button = CCT_GROUP_2_ON;
  248. break;
  249. case 3:
  250. button = CCT_GROUP_3_ON;
  251. break;
  252. case 4:
  253. button = CCT_GROUP_4_ON;
  254. break;
  255. }
  256. } else {
  257. switch(groupId) {
  258. case 1:
  259. button = CCT_GROUP_1_OFF;
  260. break;
  261. case 2:
  262. button = CCT_GROUP_2_OFF;
  263. break;
  264. case 3:
  265. button = CCT_GROUP_3_OFF;
  266. break;
  267. case 4:
  268. button = CCT_GROUP_4_OFF;
  269. break;
  270. }
  271. }
  272. return button;
  273. }
  274. MiLightRadioType MiLightClient::getRadioType(const String& typeName) {
  275. if (typeName.equalsIgnoreCase("rgbw")) {
  276. return RGBW;
  277. } else if (typeName.equalsIgnoreCase("cct")) {
  278. return CCT;
  279. } else if (typeName.equalsIgnoreCase("rgb_cct")) {
  280. return RGB_CCT;
  281. } else {
  282. return UNKNOWN;
  283. }
  284. }
  285. const MiLightRadioConfig& MiLightClient::getRadioConfig(const String& typeName) {
  286. switch (getRadioType(typeName)) {
  287. case RGBW:
  288. return MilightRgbwConfig;
  289. case CCT:
  290. return MilightCctConfig;
  291. case RGB_CCT:
  292. return MilightRgbCctConfig;
  293. default:
  294. Serial.print("Unknown radio type: ");
  295. Serial.println(typeName);
  296. return MilightRgbwConfig;
  297. }
  298. }
  299. void MiLightClient::formatPacket(MiLightRadioConfig& config, uint8_t* packet, char* buffer) {
  300. if (config.type == RGBW || config.type == CCT) {
  301. String format = String("Request type : %02X\n")
  302. + "Device ID : %02X%02X\n"
  303. + "b1 : %02X\n"
  304. + "b2 : %02X\n"
  305. + "b3 : %02X\n"
  306. + "Sequence Num. : %02X";
  307. sprintf(
  308. buffer,
  309. format.c_str(),
  310. packet[0],
  311. packet[1], packet[2],
  312. packet[3],
  313. packet[4],
  314. packet[5],
  315. packet[6]
  316. );
  317. } else {
  318. for (int i = 0; i < config.packetLength; i++) {
  319. sprintf(buffer, "%02X ", packet[i]);
  320. buffer += 3;
  321. }
  322. sprintf(buffer, "\n\n");
  323. }
  324. }
  325. uint8_t MiLightClient::xorKey(uint8_t key) {
  326. // Generate most significant nibble
  327. const uint8_t shift = (key & 0x0F) < 0x04 ? 0 : 1;
  328. const uint8_t x = (((key & 0xF0) >> 4) + shift + 6) % 8;
  329. const uint8_t msn = (((4 + x) ^ 1) & 0x0F) << 4;
  330. // Generate least significant nibble
  331. const uint8_t lsn = ((((key & 0xF) + 4)^2) & 0x0F);
  332. return ( msn | lsn );
  333. }
  334. uint8_t MiLightClient::encodeByte(uint8_t byte, uint8_t s1, uint8_t xorKey, uint8_t s2) {
  335. uint8_t value = (byte + s1) % 0x100;
  336. value = value ^ xorKey;
  337. value = (value + s2) % 0x100;
  338. return value;
  339. }
  340. void MiLightClient::encodeV2Packet(uint8_t *packet) {
  341. uint8_t key = xorKey(packet[0]);
  342. uint8_t sum = key;
  343. for (size_t i = 1; i <= 7; i++) {
  344. sum += packet[i];
  345. packet[i] = encodeByte(packet[i], 0, key, V2_OFFSET(i, packet[0]));
  346. }
  347. packet[8] = encodeByte(sum, 2, key, V2_OFFSET(8, packet[0]));
  348. printf("encoded packet: ");
  349. for (int i = 0; i < MilightRgbCctConfig.packetLength; i++) {
  350. printf("%02X ", packet[i]);
  351. }
  352. printf("\n");
  353. }