V6MiLightUdpServer.cpp 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. #include <V6MiLightUdpServer.h>
  2. #include <ESP8266WiFi.h>
  3. #include <Arduino.h>
  4. #define MATCHES_PACKET(packet1, packet2, packet2Len) ( \
  5. matchesPacket(packet1, size(packet1), packet2, packet2Len) \
  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::COMMAND_RESPONSE[] = {
  25. 0x88, 0x00, 0x00, 0x00, 0x03, 0x00, 0xFF, 0x00
  26. };
  27. uint8_t V6MiLightUdpServer::SEARCH_COMMAND[] = {
  28. 0x10, 0x00, 0x00, 0x00, 0x24, 0x02, 0xAE, 0x65, 0x02, 0x39, 0x38, 0x35, 0x62
  29. };
  30. template<typename T, size_t sz>
  31. size_t size(T(&)[sz]) {
  32. return sz;
  33. }
  34. V6MiLightUdpServer::~V6MiLightUdpServer() {
  35. V6Session* cur = firstSession;
  36. while (cur != NULL) {
  37. V6Session* next = cur->next;
  38. delete cur;
  39. cur = next;
  40. }
  41. }
  42. template <typename T>
  43. T V6MiLightUdpServer::readInt(uint8_t* packet) {
  44. size_t numBytes = sizeof(T);
  45. T value = 0;
  46. for (size_t i = 0; i < numBytes; i++) {
  47. value |= packet[i] << (8 * (numBytes - i - 1));
  48. }
  49. return value;
  50. }
  51. template <typename T>
  52. uint8_t* V6MiLightUdpServer::writeInt(const T& value, uint8_t* packet) {
  53. size_t numBytes = sizeof(T);
  54. for (size_t i = 0; i < numBytes; i++) {
  55. packet[i] = (value >> (8 * (numBytes - i - 1))) & 0xFF;
  56. }
  57. return packet + numBytes;
  58. }
  59. uint16_t V6MiLightUdpServer::beginSession() {
  60. const uint16_t id = sessionId++;
  61. V6Session* session = new V6Session(socket.remoteIP(), socket.remotePort(), id);
  62. session->next = firstSession;
  63. firstSession = session;
  64. if (numSessions >= V6_MAX_SESSIONS) {
  65. V6Session* cur = firstSession;
  66. for (size_t i = 1; i < V6_MAX_SESSIONS; i++) {
  67. cur = cur->next;
  68. }
  69. delete cur->next;
  70. cur->next = NULL;
  71. } else {
  72. numSessions++;
  73. }
  74. return id;
  75. }
  76. void V6MiLightUdpServer::handleStartSession() {
  77. size_t len = size(START_SESSION_RESPONSE);
  78. uint8_t response[len];
  79. uint16_t sessionId = beginSession();
  80. memcpy(response, START_SESSION_RESPONSE, len);
  81. WiFi.macAddress(response + 7);
  82. response[19] = sessionId >> 8;
  83. response[20] = sessionId & 0xFF;
  84. sendResponse(sessionId, response, len);
  85. }
  86. void V6MiLightUdpServer::sendResponse(uint16_t sessionId, uint8_t* responseBuffer, size_t responseSize) {
  87. V6Session* session = firstSession;
  88. while (session != NULL) {
  89. if (session->sessionId == sessionId) {
  90. break;
  91. }
  92. session = session->next;
  93. }
  94. if (session == NULL || session->sessionId != sessionId) {
  95. Serial.print("Received request with untracked session ID: ");
  96. Serial.println(sessionId);
  97. return;
  98. }
  99. #ifdef MILIGHT_UDP_DEBUG
  100. printf("Sending response to %s:%d\n", session->ipAddr.toString().c_str(), session->port);
  101. #endif
  102. socket.beginPacket(session->ipAddr, session->port);
  103. socket.write(responseBuffer, responseSize);
  104. socket.endPacket();
  105. }
  106. bool V6MiLightUdpServer::handleRgbBulbCommand(uint8_t group, uint32_t _cmd, uint32_t _arg) {
  107. const uint8_t cmd = _cmd & 0xFF;
  108. const uint8_t arg = _arg >> 24;
  109. client->prepare(MilightRgbConfig, deviceId, 0);
  110. if (cmd == V2_RGB_COMMAND_PREFIX) {
  111. switch (arg) {
  112. case V2_RGB_ON:
  113. client->updateStatus(ON);
  114. break;
  115. case V2_RGB_OFF:
  116. client->updateStatus(OFF);
  117. break;
  118. case V2_RGB_BRIGHTNESS_DOWN:
  119. client->decreaseBrightness();
  120. break;
  121. case V2_RGB_BRIGHTNESS_UP:
  122. client->increaseBrightness();
  123. break;
  124. case V2_RGB_MODE_DOWN:
  125. client->previousMode();
  126. break;
  127. case V2_RGB_MODE_UP:
  128. client->nextMode();
  129. break;
  130. case V2_RGB_SPEED_DOWN:
  131. client->modeSpeedDown();
  132. break;
  133. case V2_RGB_SPEED_UP:
  134. client->modeSpeedUp();
  135. break;
  136. }
  137. } else if (cmd == V2_RGB_COLOR_PREFIX) {
  138. client->updateColorRaw(arg);
  139. }
  140. }
  141. bool V6MiLightUdpServer::handleV2BulbCommand(uint8_t group, uint32_t _cmd, uint32_t _arg) {
  142. const uint8_t cmd = _cmd & 0xFF;
  143. const uint8_t arg = _arg >> 24;
  144. client->prepare(MilightRgbCctConfig, deviceId, group);
  145. switch (cmd) {
  146. case V2_STATUS:
  147. if (arg == 0x01) {
  148. client->updateStatus(ON);
  149. } else if (arg == 0x02) {
  150. client->updateStatus(OFF);
  151. } else if (arg == 0x05) {
  152. client->updateBrightness(0);
  153. }
  154. break;
  155. case V2_COLOR:
  156. client->updateColorRaw(arg);
  157. break;
  158. case V2_KELVIN:
  159. client->updateTemperature(arg);
  160. break;
  161. case V2_BRIGHTNESS:
  162. client->updateBrightness(arg);
  163. break;
  164. case V2_SATURATION:
  165. client->updateSaturation(100 - arg);
  166. break;
  167. default:
  168. return false;
  169. }
  170. return true;
  171. }
  172. void V6MiLightUdpServer::handleCommand(
  173. uint16_t sessionId,
  174. uint8_t sequenceNum,
  175. uint8_t* cmd,
  176. uint8_t group,
  177. uint8_t checksum
  178. ) {
  179. uint8_t cmdType = readInt<uint8_t>(cmd);
  180. uint32_t cmdHeader = readInt<uint32_t>(cmd+1);
  181. uint32_t cmdArg = readInt<uint32_t>(cmd+5);
  182. #ifdef MILIGHT_UDP_DEBUG
  183. printf("Command type: %02X, command: %08X, arg: %08X\n", cmdType, cmdHeader, cmdArg);
  184. #endif
  185. bool handled = false;
  186. if ((cmdHeader & 0x0800) == 0x0800) {
  187. handled = handleV2BulbCommand(group, cmdHeader, cmdArg);
  188. } else if ((cmdHeader & 0x0500) == 0x0500) {
  189. handled = handleRgbBulbCommand(group, cmdHeader, cmdArg);
  190. }
  191. if (handled) {
  192. size_t len = size(COMMAND_RESPONSE);
  193. memcpy(responseBuffer, COMMAND_RESPONSE, len);
  194. responseBuffer[6] = sequenceNum;
  195. sendResponse(sessionId, responseBuffer, len);
  196. return;
  197. }
  198. #ifdef MILIGHT_UDP_DEBUG
  199. printf("V6MiLightUdpServer - Unhandled command: ");
  200. for (size_t i = 0; i < V6_COMMAND_LEN; i++) {
  201. printf("%02X ", cmd[i]);
  202. }
  203. printf("\n");
  204. #endif
  205. }
  206. void V6MiLightUdpServer::handleHeartbeat(uint16_t sessionId) {
  207. char header[] = { 0xD8, 0x00, 0x00, 0x00, 0x07 };
  208. memcpy(responseBuffer, header, size(header));
  209. WiFi.macAddress(responseBuffer+5);
  210. responseBuffer[11] = 0;
  211. sendResponse(sessionId, responseBuffer, 12);
  212. }
  213. bool V6MiLightUdpServer::matchesPacket(uint8_t* packet1, size_t packet1Len, uint8_t* packet2, size_t packet2Len) {
  214. return packet2Len >= packet1Len && memcmp(packet1, packet2, packet1Len);
  215. }
  216. void V6MiLightUdpServer::handlePacket(uint8_t* packet, size_t packetSize) {
  217. #ifdef MILIGHT_UDP_DEBUG
  218. printf("Packet size: %d\n", packetSize);
  219. #endif
  220. if (MATCHES_PACKET(START_SESSION_COMMAND, packet, packetSize)) {
  221. handleStartSession();
  222. } else if (MATCHES_PACKET(HEARTBEAT_HEADER, packet, packetSize)) {
  223. uint16_t sessionId = readInt<uint16_t>(packet+5);
  224. handleHeartbeat(sessionId);
  225. } else if (packetSize == 22 && MATCHES_PACKET(COMMAND_HEADER, packet, packetSize)) {
  226. uint16_t sessionId = readInt<uint16_t>(packet+5);
  227. uint8_t sequenceNum = packet[8];
  228. uint8_t* cmd = packet+10;
  229. uint8_t group = packet[19];
  230. uint8_t checksum = packet[21];
  231. #ifdef MILIGHT_UDP_DEBUG
  232. printf("session: %04X, sequence: %d, group: %d, checksum: %d\n", sessionId, sequenceNum, group, checksum);
  233. #endif
  234. handleCommand(sessionId, sequenceNum, cmd, group, checksum);
  235. } else {
  236. Serial.println("Unhandled V6 packet");
  237. }
  238. }