V6MiLightUdpServer.cpp 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. #include <V6MiLightUdpServer.h>
  2. #include <ESP8266WiFi.h>
  3. #include <Arduino.h>
  4. #define MATCHES_PACKET(packet1) ( \
  5. matchesPacket(packet1, size(packet1), packet, packetSize) \
  6. )
  7. uint8_t V6MiLightUdpServer::START_SESSION_COMMAND[] = {
  8. 0x20, 0x00, 0x00, 0x00, 0x16, 0x02, 0x62, 0x3A, 0xD5, 0xED, 0xA3, 0x01, 0xAE,
  9. 0x08, 0x2D, 0x46, 0x61, 0x41, 0xA7, 0xF6, 0xDC, 0xAF
  10. };
  11. uint8_t V6MiLightUdpServer::START_SESSION_RESPONSE[] = {
  12. 0x28, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02,
  13. 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // should be replaced with hw addr
  14. 0x69, 0xF0, 0x3C, 0x23, 0x00, 0x01,
  15. 0xFF, 0xFF, // should be replaced with a session ID
  16. 0x00
  17. };
  18. uint8_t V6MiLightUdpServer::COMMAND_HEADER[] = {
  19. 0x80, 0x00, 0x00, 0x00
  20. };
  21. uint8_t V6MiLightUdpServer::HEARTBEAT_HEADER[] = {
  22. 0xD0, 0x00, 0x00, 0x00, 0x02
  23. };
  24. uint8_t V6MiLightUdpServer::HEARTBEAT_HEADER2[] = {
  25. 0x30, 0x00, 0x00, 0x00, 0x03
  26. };
  27. uint8_t V6MiLightUdpServer::COMMAND_RESPONSE[] = {
  28. 0x88, 0x00, 0x00, 0x00, 0x03, 0x00, 0xFF, 0x00
  29. };
  30. uint8_t V6MiLightUdpServer::SEARCH_COMMAND[] = {
  31. 0x10, 0x00, 0x00, 0x00
  32. //, 0x24, 0x02
  33. //, 0xAE, 0x65, 0x02, 0x39, 0x38, 0x35, 0x62
  34. };
  35. uint8_t V6MiLightUdpServer::SEARCH_RESPONSE[] = {
  36. 0x18, 0x00, 0x00, 0x00, 0x40, 0x02,
  37. 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // mac address
  38. 0x00, 0x20, 0x39, 0x38, 0x35, 0x62,
  39. 0x31, 0x35, 0x37, 0x62, 0x66, 0x36,
  40. 0x66, 0x63, 0x34, 0x33, 0x33, 0x36,
  41. 0x38, 0x61, 0x36, 0x33, 0x34, 0x36,
  42. 0x37, 0x65, 0x61, 0x33, 0x62, 0x31,
  43. 0x39, 0x64, 0x30, 0x64, 0x01, 0x00,
  44. 0x01,
  45. 0x17, 0x63, // port?
  46. 0x00, 0x00, 0x05, 0x00, 0x09, 0x78,
  47. 0x6C, 0x69, 0x6E, 0x6B, 0x5F, 0x64,
  48. 0x65, 0x76, 0x07, 0x5B, 0xCD, 0x15
  49. };
  50. uint8_t V6MiLightUdpServer::OPEN_COMMAND_RESPONSE[] = {
  51. 0x80, 0x00, 0x00, 0x00, 0x15,
  52. 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // mac address
  53. 0x05, 0x02, 0x00, 0x34, 0x00, 0x00,
  54. 0x00, 0x00 ,0x00 ,0x00, 0x00, 0x00,
  55. 0x00, 0x00, 0x34
  56. };
  57. template<typename T, size_t sz>
  58. size_t size(T(&)[sz]) {
  59. return sz;
  60. }
  61. V6MiLightUdpServer::~V6MiLightUdpServer() {
  62. V6Session* cur = firstSession;
  63. while (cur != NULL) {
  64. V6Session* next = cur->next;
  65. delete cur;
  66. cur = next;
  67. }
  68. }
  69. template <typename T>
  70. T V6MiLightUdpServer::readInt(uint8_t* packet) {
  71. size_t numBytes = sizeof(T);
  72. T value = 0;
  73. for (size_t i = 0; i < numBytes; i++) {
  74. value |= packet[i] << (8 * (numBytes - i - 1));
  75. }
  76. return value;
  77. }
  78. template <typename T>
  79. uint8_t* V6MiLightUdpServer::writeInt(const T& value, uint8_t* packet) {
  80. size_t numBytes = sizeof(T);
  81. for (size_t i = 0; i < numBytes; i++) {
  82. packet[i] = (value >> (8 * (numBytes - i - 1))) & 0xFF;
  83. }
  84. return packet + numBytes;
  85. }
  86. uint16_t V6MiLightUdpServer::beginSession() {
  87. const uint16_t id = sessionId++;
  88. V6Session* session = new V6Session(socket.remoteIP(), socket.remotePort(), id);
  89. session->next = firstSession;
  90. firstSession = session;
  91. if (numSessions >= V6_MAX_SESSIONS) {
  92. V6Session* cur = firstSession;
  93. for (size_t i = 1; i < V6_MAX_SESSIONS; i++) {
  94. cur = cur->next;
  95. }
  96. delete cur->next;
  97. cur->next = NULL;
  98. } else {
  99. numSessions++;
  100. }
  101. return id;
  102. }
  103. void V6MiLightUdpServer::handleSearch() {
  104. const size_t packetLen = size(SEARCH_RESPONSE);
  105. uint8_t response[packetLen];
  106. memcpy(response, SEARCH_RESPONSE, packetLen);
  107. WiFi.macAddress(response + 6);
  108. printf("Sending search response - ");
  109. for (size_t i = 0; i < packetLen; i++) {
  110. printf("%02X ", response[i]);
  111. }
  112. printf("\n");
  113. socket.beginPacket(socket.remoteIP(), socket.remotePort());
  114. socket.write(response, packetLen);
  115. socket.endPacket();
  116. }
  117. void V6MiLightUdpServer::handleStartSession() {
  118. size_t len = size(START_SESSION_RESPONSE);
  119. uint8_t response[len];
  120. uint16_t sessionId = beginSession();
  121. memcpy(response, START_SESSION_RESPONSE, len);
  122. WiFi.macAddress(response + 7);
  123. response[19] = sessionId >> 8;
  124. response[20] = sessionId & 0xFF;
  125. sendResponse(sessionId, response, len);
  126. }
  127. bool V6MiLightUdpServer::sendResponse(uint16_t sessionId, uint8_t* responseBuffer, size_t responseSize) {
  128. V6Session* session = firstSession;
  129. while (session != NULL) {
  130. if (session->sessionId == sessionId) {
  131. break;
  132. }
  133. session = session->next;
  134. }
  135. if (session == NULL || session->sessionId != sessionId) {
  136. Serial.print("Received request with untracked session ID: ");
  137. Serial.println(sessionId);
  138. return false;
  139. }
  140. #ifdef MILIGHT_UDP_DEBUG
  141. printf("Sending response to %s:%d\n", session->ipAddr.toString().c_str(), session->port);
  142. #endif
  143. socket.beginPacket(session->ipAddr, session->port);
  144. socket.write(responseBuffer, responseSize);
  145. socket.endPacket();
  146. return true;
  147. }
  148. bool V6MiLightUdpServer::handleRgbBulbCommand(uint8_t group, uint32_t _cmd, uint32_t _arg) {
  149. const uint8_t cmd = _cmd & 0xFF;
  150. const uint8_t arg = _arg >> 24;
  151. client->prepare(MilightRgbConfig, deviceId, 0);
  152. if (cmd == V2_RGB_COMMAND_PREFIX) {
  153. switch (arg) {
  154. case V2_RGB_ON:
  155. client->updateStatus(ON);
  156. break;
  157. case V2_RGB_OFF:
  158. client->updateStatus(OFF);
  159. break;
  160. case V2_RGB_BRIGHTNESS_DOWN:
  161. client->decreaseBrightness();
  162. break;
  163. case V2_RGB_BRIGHTNESS_UP:
  164. client->increaseBrightness();
  165. break;
  166. case V2_RGB_MODE_DOWN:
  167. client->previousMode();
  168. break;
  169. case V2_RGB_MODE_UP:
  170. client->nextMode();
  171. break;
  172. case V2_RGB_SPEED_DOWN:
  173. client->modeSpeedDown();
  174. break;
  175. case V2_RGB_SPEED_UP:
  176. client->modeSpeedUp();
  177. break;
  178. }
  179. } else if (cmd == V2_RGB_COLOR_PREFIX) {
  180. client->updateColorRaw(arg);
  181. }
  182. }
  183. bool V6MiLightUdpServer::handleV2BulbCommand(uint8_t group, uint32_t _cmd, uint32_t _arg) {
  184. const uint8_t cmd = _cmd & 0xFF;
  185. const uint8_t arg = _arg >> 24;
  186. client->prepare(MilightRgbCctConfig, deviceId, group);
  187. switch (cmd) {
  188. case V2_STATUS:
  189. if (arg == 0x01) {
  190. client->updateStatus(ON);
  191. } else if (arg == 0x02) {
  192. client->updateStatus(OFF);
  193. } else if (arg == 0x05) {
  194. client->updateBrightness(0);
  195. }
  196. break;
  197. case V2_COLOR:
  198. client->updateColorRaw(arg);
  199. break;
  200. case V2_KELVIN:
  201. client->updateTemperature(arg);
  202. break;
  203. case V2_BRIGHTNESS:
  204. client->updateBrightness(arg);
  205. break;
  206. case V2_SATURATION:
  207. client->updateSaturation(100 - arg);
  208. break;
  209. default:
  210. return false;
  211. }
  212. return true;
  213. }
  214. bool V6MiLightUdpServer::handleOpenCommand(uint16_t sessionId) {
  215. size_t len = size(OPEN_COMMAND_RESPONSE);
  216. uint8_t response[len];
  217. memcpy(response, OPEN_COMMAND_RESPONSE, len);
  218. WiFi.macAddress(response + 5);
  219. return sendResponse(sessionId, response, len);
  220. }
  221. void V6MiLightUdpServer::handleCommand(
  222. uint16_t sessionId,
  223. uint8_t sequenceNum,
  224. uint8_t* cmd,
  225. uint8_t group,
  226. uint8_t checksum
  227. ) {
  228. uint8_t cmdType = readInt<uint8_t>(cmd);
  229. uint32_t cmdHeader = readInt<uint32_t>(cmd+1);
  230. uint32_t cmdArg = readInt<uint32_t>(cmd+5);
  231. #ifdef MILIGHT_UDP_DEBUG
  232. printf("Command cmdType: %02X, cmdHeader: %08X, cmdArg: %08X\n", cmdType, cmdHeader, cmdArg);
  233. #endif
  234. bool handled = false;
  235. if (cmdHeader == 0) {
  236. handled = handleOpenCommand(sessionId);
  237. } else if ((cmdHeader & 0x0800) == 0x0800) {
  238. handled = handleV2BulbCommand(group, cmdHeader, cmdArg);
  239. } else if ((cmdHeader & 0x0500) == 0x0500) {
  240. handled = handleRgbBulbCommand(group, cmdHeader, cmdArg);
  241. }
  242. if (handled) {
  243. size_t len = size(COMMAND_RESPONSE);
  244. memcpy(responseBuffer, COMMAND_RESPONSE, len);
  245. responseBuffer[6] = sequenceNum;
  246. sendResponse(sessionId, responseBuffer, len);
  247. return;
  248. }
  249. #ifdef MILIGHT_UDP_DEBUG
  250. printf("V6MiLightUdpServer - Unhandled command: ");
  251. for (size_t i = 0; i < V6_COMMAND_LEN; i++) {
  252. printf("%02X ", cmd[i]);
  253. }
  254. printf("\n");
  255. #endif
  256. }
  257. void V6MiLightUdpServer::handleHeartbeat(uint16_t sessionId) {
  258. char header[] = { 0xD8, 0x00, 0x00, 0x00, 0x07 };
  259. memcpy(responseBuffer, header, size(header));
  260. WiFi.macAddress(responseBuffer+5);
  261. responseBuffer[11] = 0;
  262. sendResponse(sessionId, responseBuffer, 12);
  263. }
  264. bool V6MiLightUdpServer::matchesPacket(uint8_t* packet1, size_t packet1Len, uint8_t* packet2, size_t packet2Len) {
  265. return packet2Len >= packet1Len && memcmp(packet1, packet2, packet1Len) == 0;
  266. }
  267. void V6MiLightUdpServer::handlePacket(uint8_t* packet, size_t packetSize) {
  268. #ifdef MILIGHT_UDP_DEBUG
  269. printf("Packet size: %d\n", packetSize);
  270. #endif
  271. if (MATCHES_PACKET(START_SESSION_COMMAND)) {
  272. handleStartSession();
  273. } else if (MATCHES_PACKET(HEARTBEAT_HEADER) || MATCHES_PACKET(HEARTBEAT_HEADER2)) {
  274. uint16_t sessionId = readInt<uint16_t>(packet+5);
  275. handleHeartbeat(sessionId);
  276. } else if (MATCHES_PACKET(SEARCH_COMMAND)) {
  277. handleSearch();
  278. } else if (packetSize == 22 && MATCHES_PACKET(COMMAND_HEADER)) {
  279. uint16_t sessionId = readInt<uint16_t>(packet+5);
  280. uint8_t sequenceNum = packet[8];
  281. uint8_t* cmd = packet+10;
  282. uint8_t group = packet[19];
  283. uint8_t checksum = packet[21];
  284. #ifdef MILIGHT_UDP_DEBUG
  285. printf("session: %04X, sequence: %d, group: %d, checksum: %d\n", sessionId, sequenceNum, group, checksum);
  286. #endif
  287. handleCommand(sessionId, sequenceNum, cmd, group, checksum);
  288. } else {
  289. Serial.println("Unhandled V6 packet");
  290. }
  291. }