Преглед на файлове

Major refactor of radio/remote config. Add support for FUT089 remote

Chris Mullins преди 8 години
родител
ревизия
02ce659ef5
променени са 37 файла, в които са добавени 528 реда и са изтрити 352 реда
  1. 6 1
      lib/Helpers/Size.h
  2. 6 5
      lib/MQTT/MqttClient.cpp
  3. 2 1
      lib/MQTT/MqttClient.h
  4. 1 1
      lib/MiLight/CctPacketFormatter.cpp
  5. 59 0
      lib/MiLight/FUT089PacketFormatter.cpp
  6. 45 0
      lib/MiLight/FUT089PacketFormatter.h
  7. 46 106
      lib/MiLight/LT8900MiLightRadio.cpp
  8. 1 1
      lib/MiLight/LT8900MiLightRadio.h
  9. 12 11
      lib/MiLight/MiLightButtons.h
  10. 68 68
      lib/MiLight/MiLightClient.cpp
  11. 12 9
      lib/MiLight/MiLightClient.h
  12. 5 29
      lib/MiLight/MiLightRadioConfig.cpp
  13. 14 40
      lib/MiLight/MiLightRadioConfig.h
  14. 1 1
      lib/MiLight/MiLightRadioFactory.h
  15. 60 0
      lib/MiLight/MiLightRemoteConfig.cpp
  16. 75 0
      lib/MiLight/MiLightRemoteConfig.h
  17. 1 1
      lib/MiLight/NRF24MiLightRadio.cpp
  18. 4 0
      lib/MiLight/PacketFormatter.cpp
  19. 4 2
      lib/MiLight/PacketFormatter.h
  20. 3 3
      lib/MiLight/RgbCctPacketFormatter.cpp
  21. 3 4
      lib/MiLight/RgbCctPacketFormatter.h
  22. 1 1
      lib/MiLight/RgbPacketFormatter.cpp
  23. 3 3
      lib/MiLight/RgbwPacketFormatter.cpp
  24. 2 0
      lib/MiLight/RgbwPacketFormatter.h
  25. 7 0
      lib/MiLight/V2PacketFormatter.cpp
  26. 5 1
      lib/MiLight/V2PacketFormatter.h
  27. 5 5
      lib/Udp/V5MiLightUdpServer.cpp
  28. 1 1
      lib/Udp/V6CctCommandHandler.h
  29. 1 1
      lib/Udp/V6ComamndHandler.cpp
  30. 4 4
      lib/Udp/V6CommandHandler.h
  31. 1 1
      lib/Udp/V6RgbCctCommandHandler.h
  32. 1 1
      lib/Udp/V6RgbCommandHandler.h
  33. 1 1
      lib/Udp/V6RgbwCommandHandler.h
  34. 39 36
      lib/WebServer/MiLightHttpServer.cpp
  35. 1 1
      lib/WebServer/MiLightHttpServer.h
  36. 3 3
      platformio.ini
  37. 25 10
      src/main.cpp

+ 6 - 1
lib/Helpers/Size.h

@@ -1,6 +1,11 @@
 #include <Arduino.h>
 
+#ifndef _SIZE_H
+#define _SIZE_H
+
 template<typename T, size_t sz>
 size_t size(T(&)[sz]) {
     return sz;
-}
+}
+
+#endif

+ 6 - 5
lib/MQTT/MqttClient.cpp

@@ -4,6 +4,7 @@
 #include <IntParsing.h>
 #include <ArduinoJson.h>
 #include <WiFiClient.h>
+#include <MiLightRadioConfig.h>
 
 MqttClient::MqttClient(Settings& settings, MiLightClient*& milightClient)
   : milightClient(milightClient),
@@ -85,7 +86,7 @@ void MqttClient::handleClient() {
   mqttClient->loop();
 }
 
-void MqttClient::sendUpdate(MiLightRadioType type, uint16_t deviceId, uint16_t groupId, const char* update) {
+void MqttClient::sendUpdate(const MiLightRemoteConfig& remoteConfig, uint16_t deviceId, uint16_t groupId, const char* update) {
   String topic = settings.mqttUpdateTopicPattern;
 
   if (topic.length() == 0) {
@@ -97,7 +98,7 @@ void MqttClient::sendUpdate(MiLightRadioType type, uint16_t deviceId, uint16_t g
 
   topic.replace(":device_id", String("0x") + deviceIdStr);
   topic.replace(":group_id", String(groupId));
-  topic.replace(":device_type", MiLightRadioConfig::fromType(type)->name);
+  topic.replace(":device_type", remoteConfig.name);
 
 #ifdef MQTT_DEBUG
   printf_P(PSTR("MqttClient - publishing update to %s: %s\n"), topic.c_str(), update);
@@ -123,7 +124,7 @@ void MqttClient::subscribe() {
 void MqttClient::publishCallback(char* topic, byte* payload, int length) {
   uint16_t deviceId = 0;
   uint8_t groupId = 0;
-  MiLightRadioConfig* config = &MilightRgbCctConfig;
+  const MiLightRemoteConfig* config = &FUT092Config;
   char cstrPayload[length + 1];
   cstrPayload[length] = 0;
   memcpy(cstrPayload, payload, sizeof(byte)*length);
@@ -148,7 +149,7 @@ void MqttClient::publishCallback(char* topic, byte* payload, int length) {
   }
 
   if (tokenBindings.hasBinding("device_type")) {
-    config = MiLightRadioConfig::fromString(tokenBindings.get("device_type"));
+    config = MiLightRemoteConfig::fromType(tokenBindings.get("device_type"));
   }
 
   StaticJsonBuffer<400> buffer;
@@ -158,6 +159,6 @@ void MqttClient::publishCallback(char* topic, byte* payload, int length) {
   printf_P(PSTR("MqttClient - device %04X, group %u\n"), deviceId, groupId);
 #endif
 
-  milightClient->prepare(*config, deviceId, groupId);
+  milightClient->prepare(config, deviceId, groupId);
   milightClient->update(obj);
 }

+ 2 - 1
lib/MQTT/MqttClient.h

@@ -2,6 +2,7 @@
 #include <Settings.h>
 #include <PubSubClient.h>
 #include <WiFiClient.h>
+#include <MiLightRadioConfig.h>
 
 #ifndef MQTT_CONNECTION_ATTEMPT_FREQUENCY
 #define MQTT_CONNECTION_ATTEMPT_FREQUENCY 5000
@@ -18,7 +19,7 @@ public:
   void begin();
   void handleClient();
   void reconnect();
-  void sendUpdate(MiLightRadioType type, uint16_t deviceId, uint16_t groupId, const char* update);
+  void sendUpdate(const MiLightRemoteConfig& remoteConfig, uint16_t deviceId, uint16_t groupId, const char* update);
 
 private:
   WiFiClient tcpClient;

+ 1 - 1
lib/MiLight/CctPacketFormatter.cpp

@@ -4,7 +4,7 @@
 void CctPacketFormatter::initializePacket(uint8_t* packet) {
   size_t packetPtr = 0;
 
-  packet[packetPtr++] = CCT;
+  packet[packetPtr++] = 0x5A;
   packet[packetPtr++] = deviceId >> 8;
   packet[packetPtr++] = deviceId & 0xFF;
   packet[packetPtr++] = groupId;

+ 59 - 0
lib/MiLight/FUT089PacketFormatter.cpp

@@ -0,0 +1,59 @@
+#include <FUT089PacketFormatter.h>
+#include <V2RFEncoding.h>
+#include <Units.h>
+
+void FUT089PacketFormatter::modeSpeedDown() {
+  command(FUT089_ON, FUT089_MODE_SPEED_DOWN);
+}
+
+void FUT089PacketFormatter::modeSpeedUp() {
+  command(FUT089_ON, FUT089_MODE_SPEED_UP);
+}
+
+void FUT089PacketFormatter::updateMode(uint8_t mode) {
+  command(FUT089_MODE, mode);
+}
+
+void FUT089PacketFormatter::updateBrightness(uint8_t brightness) {
+  command(FUT089_BRIGHTNESS, brightness);
+}
+
+void FUT089PacketFormatter::updateHue(uint16_t value) {
+  uint8_t remapped = Units::rescale(value, 255, 360);
+  updateColorRaw(remapped);
+}
+
+void FUT089PacketFormatter::updateColorRaw(uint8_t value) {
+  command(FUT089_COLOR, FUT089_COLOR_OFFSET + value);
+}
+
+void FUT089PacketFormatter::updateTemperature(uint8_t value) {
+  command(FUT089_KELVIN, value);
+}
+
+void FUT089PacketFormatter::updateSaturation(uint8_t value) {
+  command(FUT089_SATURATION, value);
+}
+
+void FUT089PacketFormatter::updateColorWhite() {
+  command(FUT089_ON, FUT089_WHITE_MODE);
+}
+
+void FUT089PacketFormatter::enableNightMode() {
+  uint8_t arg = groupCommandArg(OFF, groupId);
+  command(FUT089_ON | 0x80, arg);
+}
+
+void FUT089PacketFormatter::parsePacket(const uint8_t *packet, JsonObject& result) {
+  uint8_t packetCopy[V2_PACKET_LEN];
+  memcpy(packetCopy, packet, V2_PACKET_LEN);
+  V2RFEncoding::decodeV2Packet(packetCopy);
+
+  result["device_id"] = (packetCopy[2] << 8) | packetCopy[3];
+  result["group_id"] = packetCopy[7];
+  result["device_type"] = "rgb_cct";
+
+  if (! result.containsKey("state")) {
+    result["state"] = "ON";
+  }
+}

+ 45 - 0
lib/MiLight/FUT089PacketFormatter.h

@@ -0,0 +1,45 @@
+#include <V2PacketFormatter.h>
+
+#ifndef _FUT089_PACKET_FORMATTER_H
+#define _FUT089_PACKET_FORMATTER_H
+
+#define FUT089_COLOR_OFFSET 0
+
+enum MiLightFUT089Command {
+  FUT089_ON = 0x01,
+  FUT089_OFF = 0x01,
+  FUT089_COLOR = 0x02,
+  FUT089_BRIGHTNESS = 0x05,
+  FUT089_MODE = 0x06,
+  FUT089_KELVIN = 0x07,
+  FUT089_SATURATION = 0x07
+};
+
+enum MiLightFUT089Arguments {
+  FUT089_MODE_SPEED_UP   = 0x12,
+  FUT089_MODE_SPEED_DOWN = 0x13,
+  FUT089_WHITE_MODE = 0x14
+};
+
+class FUT089PacketFormatter : public V2PacketFormatter {
+public:
+  FUT089PacketFormatter()
+    : V2PacketFormatter(0x25, 8)
+  { }
+
+  virtual void updateBrightness(uint8_t value);
+  virtual void updateHue(uint16_t value);
+  virtual void updateColorRaw(uint8_t value);
+  virtual void updateColorWhite();
+  virtual void updateTemperature(uint8_t value);
+  virtual void updateSaturation(uint8_t value);
+  virtual void enableNightMode();
+
+  virtual void modeSpeedDown();
+  virtual void modeSpeedUp();
+  virtual void updateMode(uint8_t mode);
+
+  virtual void parsePacket(const uint8_t* packet, JsonObject& result);
+};
+
+#endif

+ 46 - 106
lib/MiLight/LT8900MiLightRadio.cpp

@@ -47,7 +47,7 @@ LT8900MiLightRadio::LT8900MiLightRadio(byte byCSPin, byte byResetPin, byte byPkt
   SPI.setBitOrder(MSBFIRST);
 
   //Initialize transceiver with correct settings
-  vInitRadioModule(config.type);
+  vInitRadioModule();
   delay(50);
 
   // Check if HW is connected
@@ -90,108 +90,48 @@ bool LT8900MiLightRadio::bCheckRadioConnection(void)
 /**************************************************************************/
 // Initialize radio module
 /**************************************************************************/
-void LT8900MiLightRadio::vInitRadioModule(MiLightRadioType type) {
-	if (type == RGB_CCT) {
-		bool bWriteDefaultDefault = true;  // Is it okay to use the default power up values, without setting them
-
-		regWrite16(0x00, 0x6F, 0xE0, 7);  // Recommended value by PMmicro
-		regWrite16(0x02, 0x66, 0x17, 7);  // Recommended value by PMmicro
-		regWrite16(0x04, 0x9C, 0xC9, 7);  // Recommended value by PMmicro
-
-		regWrite16(0x05, 0x66, 0x37, 7);  // Recommended value by PMmicro
-		regWrite16(0x07, 0x00, 0x4C, 7);  // PL1167's TX/RX Enable and Channel Register, Default channel 76
-		regWrite16(0x08, 0x6C, 0x90, 7);  // Recommended value by PMmicro
-		regWrite16(0x09, 0x48, 0x00, 7);  // PA Control register
-
-		regWrite16(0x0B, 0x00, 0x08, 7);  // Recommended value by PMmicro
-		regWrite16(0x0D, 0x48, 0xBD, 7);  // Recommended value by PMmicro
-		regWrite16(0x16, 0x00, 0xFF, 7);  // Recommended value by PMmicro
-		regWrite16(0x18, 0x00, 0x67, 7);  // Recommended value by PMmicro
-
-		regWrite16(0x1A, 0x19, 0xE0, 7);  // Recommended value by PMmicro
-		regWrite16(0x1B, 0x13, 0x00, 7);  // Recommended value by PMmicro
-
-		regWrite16(0x20, 0x48, 0x00, 7);  // Recommended value by PMmicro
-		regWrite16(0x21, 0x3F, 0xC7, 7);  // Recommended value by PMmicro
-		regWrite16(0x22, 0x20, 0x00, 7);  // Recommended value by PMmicro
-		regWrite16(0x23, 0x03, 0x00, 7);  // Recommended value by PMmicro
-
-		regWrite16(0x24, 0x72, 0x36, 7);  // Sync R0
-		regWrite16(0x27, 0x18, 0x09, 7);  // Sync R3
-		regWrite16(0x28, 0x44, 0x02, 7);  // Recommended value by PMmicro
-		regWrite16(0x29, 0xB0, 0x00, 7);  // Recommended value by PMmicro
-		regWrite16(0x2A, 0xFD, 0xB0, 7);  // Recommended value by PMmicro
-
-		if (bWriteDefaultDefault == true) {
-			regWrite16(0x01, 0x56, 0x81, 7);  // Recommended value by PMmicro
-			regWrite16(0x0A, 0x7F, 0xFD, 7);  // Recommended value by PMmicro
-			regWrite16(0x0C, 0x00, 0x00, 7);  // Recommended value by PMmicro
-			regWrite16(0x17, 0x80, 0x05, 7);  // Recommended value by PMmicro
-			regWrite16(0x19, 0x16, 0x59, 7);  // Recommended value by PMmicro
-			regWrite16(0x1C, 0x18, 0x00, 7);  // Recommended value by PMmicro
-
-			regWrite16(0x25, 0x00, 0x00, 7);  // Recommended value by PMmicro
-			regWrite16(0x26, 0x00, 0x00, 7);  // Recommended value by PMmicro
-			regWrite16(0x2B, 0x00, 0x0F, 7);  // Recommended value by PMmicro
-		}
-	} else if( (type == RGBW) || (type == CCT) || (type == RGB) ) {
-		regWrite16(0x00, 0x6F, 0xE0, 7);  // Recommended value by PMmicro
-		regWrite16(0x01, 0x56, 0x81, 7);   // Recommended value by PMmicro
-		regWrite16(0x02, 0x66, 0x17, 7);   // Recommended value by PMmicro
-		regWrite16(0x04, 0x9C, 0xC9, 7);  // Recommended value by PMmicro
-		regWrite16(0x05, 0x66, 0x37, 7);   // Recommended value by PMmicro
-		regWrite16(0x07, 0x00, 0x4C, 7);     // PL1167's TX/RX Enable and Channel Register
-		regWrite16(0x08, 0x6C, 0x90, 7);  // Recommended value by PMmicro
-		regWrite16(0x09, 0x48, 0x00, 7);     // PL1167's PA Control Register
-		regWrite16(0x0A, 0x7F, 0xFD, 7); // Recommended value by PMmicro
-		regWrite16(0x0B, 0x00, 0x08, 7);     // PL1167's RSSI OFF Control Register -- ???
-		regWrite16(0x0C, 0x00, 0x00, 7);     // Recommended value by PMmicro
-		regWrite16(0x0D, 0x48, 0xBD, 7);  // Recommended value by PMmicro
-		regWrite16(0x16, 0x00, 0xFF, 7);   // Recommended value by PMmicro
-		regWrite16(0x17, 0x80, 0x05, 7);   // PL1167's VCO Calibration Enable Register
-		regWrite16(0x18, 0x00, 0x67, 7);   // Recommended value by PMmicro
-		regWrite16(0x19, 0x16, 0x59, 7);   // Recommended value by PMmicro
-		regWrite16(0x1A, 0x19, 0xE0, 7);  // Recommended value by PMmicro
-		regWrite16(0x1B, 0x13, 0x00, 7);    // Recommended value by PMmicro
-		regWrite16(0x1C, 0x18, 0x00, 7);    // Recommended value by PMmicro
-		regWrite16(0x20, 0x48, 0x00, 7);    // PL1167's Data Configure Register: LEN_PREAMBLE = 010 -> (0xAAAAAA) 3 bytes, LEN_SYNCWORD = 01 -> 32 bits, LEN_TRAILER = 000 -> (0x05) 4 bits, TYPE_PKT_DAT = 00 -> NRZ law data, TYPE_FEC = 00 -> No FEC
-		regWrite16(0x21, 0x3F, 0xC7, 7);  // PL1167's Delay Time Control Register 0
-		regWrite16(0x22, 0x20, 0x00, 7);    // PL1167's Delay Time Control Register 1
-		regWrite16(0x23, 0x03, 0x00, 7);     // PL1167's Power Management and Miscellaneous Register
-
-		regWrite16(0x28, 0x44, 0x02, 7);    // PL1167's FIFO and SYNCWORD Threshold Register
-		regWrite16(0x29, 0xB0, 0x00, 7);   // PL1167's Miscellaneous Register: CRC_ON = 1 -> ON, SCR_ON = 0 -> OFF, EN_PACK_LEN = 1 -> ON, FW_TERM_TX = 1 -> ON, AUTO_ACK = 0 -> OFF, PKT_LEVEL = 0 -> PKT active high, CRC_INIT_DAT = 0
-		regWrite16(0x2A, 0xFD, 0xB0, 7); // PL1167's SCAN RSSI Register 0
-		regWrite16(0x2B, 0x00, 0x0F, 7);    // PL1167's SCAN RSSI Register 1
-		delay(200);
-		regWrite16(0x80, 0x00, 0x00, 7);
-		regWrite16(0x81, 0xFF, 0xFF, 7);
-		regWrite16(0x82, 0x00, 0x00, 7);
-		regWrite16(0x84, 0x00, 0x00, 7);
-		regWrite16(0x85, 0xFF, 0xFF, 7);
-		regWrite16(0x87, 0xFF, 0xFF, 7);
-		regWrite16(0x88, 0x00, 0x00, 7);
-		regWrite16(0x89, 0xFF, 0xFF, 7);
-		regWrite16(0x8A, 0x00, 0x00, 7);
-		regWrite16(0x8B, 0xFF, 0xFF, 7);
-		regWrite16(0x8C, 0x00, 0x00, 7);
-		regWrite16(0x8D, 0xFF, 0xFF, 7);
-		regWrite16(0x96, 0x00, 0x00, 7);
-		regWrite16(0x97, 0xFF, 0xFF, 7);
-		regWrite16(0x98, 0x00, 0x00, 7);
-		regWrite16(0x99, 0xFF, 0xFF, 7);
-		regWrite16(0x9A, 0x00, 0x00, 7);
-		regWrite16(0x9B, 0xFF, 0xFF, 7);
-		regWrite16(0x9C, 0x00, 0x00, 7);
-		regWrite16(0xA0, 0x00, 0x00, 7);
-		regWrite16(0xA1, 0xFF, 0xFF, 7);
-		regWrite16(0xA2, 0x00, 0x00, 7);
-		regWrite16(0xA3, 0xFF, 0xFF, 7);
-		regWrite16(0xA8, 0x00, 0x00, 7);
-		regWrite16(0xA9, 0xFF, 0xFF, 7);
-		regWrite16(0xAA, 0x00, 0x00, 7);
-		regWrite16(0xAB, 0xFF, 0xFF, 7);
-		regWrite16(0x07, 0x00, 0x00, 7);       // Disable TX/RX and set radio channel to 0
+void LT8900MiLightRadio::vInitRadioModule() {
+	bool bWriteDefaultDefault = true;  // Is it okay to use the default power up values, without setting them
+
+	regWrite16(0x00, 0x6F, 0xE0, 7);  // Recommended value by PMmicro
+	regWrite16(0x02, 0x66, 0x17, 7);  // Recommended value by PMmicro
+	regWrite16(0x04, 0x9C, 0xC9, 7);  // Recommended value by PMmicro
+
+	regWrite16(0x05, 0x66, 0x37, 7);  // Recommended value by PMmicro
+	regWrite16(0x07, 0x00, 0x4C, 7);  // PL1167's TX/RX Enable and Channel Register, Default channel 76
+	regWrite16(0x08, 0x6C, 0x90, 7);  // Recommended value by PMmicro
+	regWrite16(0x09, 0x48, 0x00, 7);  // PA Control register
+
+	regWrite16(0x0B, 0x00, 0x08, 7);  // Recommended value by PMmicro
+	regWrite16(0x0D, 0x48, 0xBD, 7);  // Recommended value by PMmicro
+	regWrite16(0x16, 0x00, 0xFF, 7);  // Recommended value by PMmicro
+	regWrite16(0x18, 0x00, 0x67, 7);  // Recommended value by PMmicro
+
+	regWrite16(0x1A, 0x19, 0xE0, 7);  // Recommended value by PMmicro
+	regWrite16(0x1B, 0x13, 0x00, 7);  // Recommended value by PMmicro
+
+	regWrite16(0x20, 0x48, 0x00, 7);  // Recommended value by PMmicro
+	regWrite16(0x21, 0x3F, 0xC7, 7);  // Recommended value by PMmicro
+	regWrite16(0x22, 0x20, 0x00, 7);  // Recommended value by PMmicro
+	regWrite16(0x23, 0x03, 0x00, 7);  // Recommended value by PMmicro
+
+	regWrite16(0x24, 0x72, 0x36, 7);  // Sync R0
+	regWrite16(0x27, 0x18, 0x09, 7);  // Sync R3
+	regWrite16(0x28, 0x44, 0x02, 7);  // Recommended value by PMmicro
+	regWrite16(0x29, 0xB0, 0x00, 7);  // Recommended value by PMmicro
+	regWrite16(0x2A, 0xFD, 0xB0, 7);  // Recommended value by PMmicro
+
+	if (bWriteDefaultDefault == true) {
+		regWrite16(0x01, 0x56, 0x81, 7);  // Recommended value by PMmicro
+		regWrite16(0x0A, 0x7F, 0xFD, 7);  // Recommended value by PMmicro
+		regWrite16(0x0C, 0x00, 0x00, 7);  // Recommended value by PMmicro
+		regWrite16(0x17, 0x80, 0x05, 7);  // Recommended value by PMmicro
+		regWrite16(0x19, 0x16, 0x59, 7);  // Recommended value by PMmicro
+		regWrite16(0x1C, 0x18, 0x00, 7);  // Recommended value by PMmicro
+
+		regWrite16(0x25, 0x00, 0x00, 7);  // Recommended value by PMmicro
+		regWrite16(0x26, 0x00, 0x00, 7);  // Recommended value by PMmicro
+		regWrite16(0x2B, 0x00, 0x0F, 7);  // Recommended value by PMmicro
 	}
 }
 
@@ -380,7 +320,7 @@ int LT8900MiLightRadio::begin()
 /**************************************************************************/
 int LT8900MiLightRadio::configure()
 {
-  vInitRadioModule(_config.type);
+  vInitRadioModule();
   vSetSyncWord(_config.syncword3, 0,0,_config.syncword0);
   vStartListening(_config.channels[0]);
   return 0;
@@ -412,8 +352,8 @@ int LT8900MiLightRadio::read(uint8_t frame[], size_t &frame_length)
   Serial.println(F("LT8900: Radio was available, reading packet..."));
   #endif
 
-  uint8_t buf[_config.getPacketLength()];
-  int packetSize = iReadRXBuffer(buf, _config.getPacketLength());
+  uint8_t buf[MILIGHT_MAX_PACKET_LENGTH];
+  int packetSize = iReadRXBuffer(buf, MILIGHT_MAX_PACKET_LENGTH);
 
   if (packetSize > 0) {
     frame_length = packetSize;

+ 1 - 1
lib/MiLight/LT8900MiLightRadio.h

@@ -55,7 +55,7 @@ class LT8900MiLightRadio : public MiLightRadio {
 
   private:
 
-    void vInitRadioModule(MiLightRadioType type);
+    void vInitRadioModule();
     void vSetSyncWord(uint16_t syncWord3, uint16_t syncWord2, uint16_t syncWord1, uint16_t syncWord0);
     uint16_t uiReadRegister(uint8_t reg);
     void regWrite16(byte ADDR, byte V1, byte V2, byte WAIT);

+ 12 - 11
lib/MiLight/MiLightButtons.h

@@ -1,17 +1,18 @@
 #ifndef _MILIGHT_BUTTONS
-#define _MILIGHT_BUTTONS 
+#define _MILIGHT_BUTTONS
 
-enum MiLightRadioType {
-  UNKNOWN = 0,
-  RGBW  = 0xB0,
-  CCT   = 0x5A,
-  RGB_CCT = 0x20,
-  RGB = 0xA4
+enum MiLightRemoteType {
+  REMOTE_TYPE_UNKNOWN,
+  REMOTE_TYPE_RGBW,
+  REMOTE_TYPE_CCT,
+  REMOTE_TYPE_RGB_CCT,
+  REMOTE_TYPE_RGB,
+  REMOTE_TYPE_FUT089
 };
 
-enum MiLightStatus { 
-  ON = 0, 
-  OFF = 1 
+enum MiLightStatus {
+  ON = 0,
+  OFF = 1
 };
 
-#endif
+#endif

+ 68 - 68
lib/MiLight/MiLightClient.cpp

@@ -7,13 +7,14 @@
 MiLightClient::MiLightClient(MiLightRadioFactory* radioFactory)
   : resendCount(MILIGHT_DEFAULT_RESEND_COUNT),
     currentRadio(NULL),
+    currentRemote(NULL),
     numRadios(MiLightRadioConfig::NUM_CONFIGS),
     packetSentHandler(NULL)
 {
   radios = new MiLightRadio*[numRadios];
 
   for (size_t i = 0; i < numRadios; i++) {
-    radios[i] = radioFactory->create(*MiLightRadioConfig::ALL_CONFIGS[i]);
+    radios[i] = radioFactory->create(MiLightRadioConfig::ALL_CONFIGS[i]);
   }
 }
 
@@ -22,65 +23,66 @@ void MiLightClient::begin() {
     radios[i]->begin();
   }
 
-  this->currentRadio = radios[0];
-  this->currentRadio->configure();
+  switchRadio(static_cast<size_t>(0));
 }
 
 void MiLightClient::setHeld(bool held) {
-  formatter->setHeld(held);
+  currentRemote->packetFormatter->setHeld(held);
 }
 
-MiLightRadio* MiLightClient::switchRadio(const MiLightRadioType type) {
-  MiLightRadio* radio = NULL;
+size_t MiLightClient::getNumRadios() const {
+  return numRadios;
+}
 
-  for (int i = 0; i < numRadios; i++) {
-    if (this->radios[i]->config().type == type) {
-      radio = radios[i];
-      break;
-    }
+MiLightRadio* MiLightClient::switchRadio(size_t radioIx) {
+  if (radioIx >= getNumRadios()) {
+    return NULL;
   }
 
-  if (radio != NULL) {
-    if (currentRadio != radio) {
-      radio->configure();
-    }
-
-    this->currentRadio = radio;
-    this->formatter = radio->config().packetFormatter;
-
-    return radio;
-  } else {
-    Serial.print(F("MiLightClient - tried to get radio for unknown type: "));
-    Serial.println(type);
+  if (this->currentRadio != radios[radioIx]) {
+    this->currentRadio = radios[radioIx];
+    this->currentRadio->configure();
   }
 
-  return NULL;
+  return this->currentRadio;
 }
 
+MiLightRadio* MiLightClient::switchRadio(const MiLightRemoteConfig* remoteConfig) {
+  MiLightRadio* radio;
 
-void MiLightClient::prepare(MiLightRadioConfig& config,
-  const uint16_t deviceId,
-  const uint8_t groupId) {
+  for (int i = 0; i < numRadios; i++) {
+    if (&this->radios[i]->config() == &remoteConfig->radioConfig) {
+      radio = switchRadio(i);
+      break;
+    }
+  }
 
-  prepare(config.type, deviceId, groupId);
+  return radio;
 }
 
-void MiLightClient::prepare(MiLightRadioType type,
+void MiLightClient::prepare(const MiLightRemoteConfig* config,
   const uint16_t deviceId,
-  const uint8_t groupId) {
-
-  switchRadio(type);
+  const uint8_t groupId
+) {
+  switchRadio(config);
+  this->currentRemote = config;
 
   if (deviceId >= 0 && groupId >= 0) {
-    formatter->prepare(deviceId, groupId);
+    currentRemote->packetFormatter->prepare(deviceId, groupId);
   }
 }
 
+void MiLightClient::prepare(const MiLightRemoteType type,
+  const uint16_t deviceId,
+  const uint8_t groupId
+) {
+  prepare(MiLightRemoteConfig::fromType(type));
+}
+
 void MiLightClient::setResendCount(const unsigned int resendCount) {
   this->resendCount = resendCount;
 }
 
-
 bool MiLightClient::available() {
   if (currentRadio == NULL) {
     return false;
@@ -88,14 +90,16 @@ bool MiLightClient::available() {
 
   return currentRadio->available();
 }
-void MiLightClient::read(uint8_t packet[]) {
+
+size_t MiLightClient::read(uint8_t packet[]) {
   if (currentRadio == NULL) {
-    return;
+    return 0;
   }
 
-  size_t length = currentRadio->config().getPacketLength();
-
+  size_t length;
   currentRadio->read(packet, length);
+
+  return length;
 }
 
 void MiLightClient::write(uint8_t packet[]) {
@@ -105,7 +109,7 @@ void MiLightClient::write(uint8_t packet[]) {
 
 #ifdef DEBUG_PRINTF
   printf("Sending packet: ");
-  for (int i = 0; i < currentRadio->config().getPacketLength(); i++) {
+  for (int i = 0; i < currentRemote->packetFormatter->getPacketLength(); i++) {
     printf("%02X", packet[i]);
   }
   printf("\n");
@@ -113,11 +117,11 @@ void MiLightClient::write(uint8_t packet[]) {
 #endif
 
   for (int i = 0; i < this->resendCount; i++) {
-    currentRadio->write(packet, currentRadio->config().getPacketLength());
+    currentRadio->write(packet, currentRemote->packetFormatter->getPacketLength());
   }
 
   if (this->packetSentHandler) {
-    this->packetSentHandler(packet, currentRadio->config());
+    this->packetSentHandler(packet, *currentRemote);
   }
 
 #ifdef DEBUG_PRINTF
@@ -128,106 +132,106 @@ void MiLightClient::write(uint8_t packet[]) {
 }
 
 void MiLightClient::updateColorRaw(const uint8_t color) {
-  formatter->updateColorRaw(color);
+  currentRemote->packetFormatter->updateColorRaw(color);
   flushPacket();
 }
 
 void MiLightClient::updateHue(const uint16_t hue) {
-  formatter->updateHue(hue);
+  currentRemote->packetFormatter->updateHue(hue);
   flushPacket();
 }
 
 void MiLightClient::updateBrightness(const uint8_t brightness) {
-  formatter->updateBrightness(brightness);
+  currentRemote->packetFormatter->updateBrightness(brightness);
   flushPacket();
 }
 
 void MiLightClient::updateMode(uint8_t mode) {
-  formatter->updateMode(mode);
+  currentRemote->packetFormatter->updateMode(mode);
   flushPacket();
 }
 
 void MiLightClient::nextMode() {
-  formatter->nextMode();
+  currentRemote->packetFormatter->nextMode();
   flushPacket();
 }
 
 void MiLightClient::previousMode() {
-  formatter->previousMode();
+  currentRemote->packetFormatter->previousMode();
   flushPacket();
 }
 
 void MiLightClient::modeSpeedDown() {
-  formatter->modeSpeedDown();
+  currentRemote->packetFormatter->modeSpeedDown();
   flushPacket();
 }
 void MiLightClient::modeSpeedUp() {
-  formatter->modeSpeedUp();
+  currentRemote->packetFormatter->modeSpeedUp();
   flushPacket();
 }
 
 void MiLightClient::updateStatus(MiLightStatus status, uint8_t groupId) {
-  formatter->updateStatus(status, groupId);
+  currentRemote->packetFormatter->updateStatus(status, groupId);
   flushPacket();
 }
 
 void MiLightClient::updateStatus(MiLightStatus status) {
-  formatter->updateStatus(status);
+  currentRemote->packetFormatter->updateStatus(status);
   flushPacket();
 }
 
 void MiLightClient::updateSaturation(const uint8_t value) {
-  formatter->updateSaturation(value);
+  currentRemote->packetFormatter->updateSaturation(value);
   flushPacket();
 }
 
 void MiLightClient::updateColorWhite() {
-  formatter->updateColorWhite();
+  currentRemote->packetFormatter->updateColorWhite();
   flushPacket();
 }
 
 void MiLightClient::enableNightMode() {
-  formatter->enableNightMode();
+  currentRemote->packetFormatter->enableNightMode();
   flushPacket();
 }
 
 void MiLightClient::pair() {
-  formatter->pair();
+  currentRemote->packetFormatter->pair();
   flushPacket();
 }
 
 void MiLightClient::unpair() {
-  formatter->unpair();
+  currentRemote->packetFormatter->unpair();
   flushPacket();
 }
 
 void MiLightClient::increaseBrightness() {
-  formatter->increaseBrightness();
+  currentRemote->packetFormatter->increaseBrightness();
   flushPacket();
 }
 
 void MiLightClient::decreaseBrightness() {
-  formatter->decreaseBrightness();
+  currentRemote->packetFormatter->decreaseBrightness();
   flushPacket();
 }
 
 void MiLightClient::increaseTemperature() {
-  formatter->increaseTemperature();
+  currentRemote->packetFormatter->increaseTemperature();
   flushPacket();
 }
 
 void MiLightClient::decreaseTemperature() {
-  formatter->decreaseTemperature();
+  currentRemote->packetFormatter->decreaseTemperature();
   flushPacket();
 }
 
 void MiLightClient::updateTemperature(const uint8_t temperature) {
-  formatter->updateTemperature(temperature);
+  currentRemote->packetFormatter->updateTemperature(temperature);
   flushPacket();
 }
 
 void MiLightClient::command(uint8_t command, uint8_t arg) {
-  formatter->command(command, arg);
+  currentRemote->packetFormatter->command(command, arg);
   flushPacket();
 }
 
@@ -367,12 +371,8 @@ uint8_t MiLightClient::parseStatus(const JsonObject& object) {
   return (strStatus.equalsIgnoreCase("on") || strStatus.equalsIgnoreCase("true")) ? ON : OFF;
 }
 
-void MiLightClient::formatPacket(uint8_t* packet, char* buffer) {
-  formatter->format(packet, buffer);
-}
-
 void MiLightClient::flushPacket() {
-  PacketStream& stream = formatter->buildPackets();
+  PacketStream& stream = currentRemote->packetFormatter->buildPackets();
   const size_t prevNumRepeats = this->resendCount;
 
   // When sending multiple packets, normalize the number of repeats
@@ -389,7 +389,7 @@ void MiLightClient::flushPacket() {
   }
 
   setResendCount(prevNumRepeats);
-  formatter->reset();
+  currentRemote->packetFormatter->reset();
 }
 
 void MiLightClient::onPacketSent(PacketSentHandler handler) {

+ 12 - 9
lib/MiLight/MiLightClient.h

@@ -3,6 +3,7 @@
 #include <MiLightRadio.h>
 #include <MiLightRadioFactory.h>
 #include <MiLightButtons.h>
+#include <MiLightRemoteConfig.h>
 #include <Settings.h>
 
 #ifndef _MILIGHTCLIENT_H
@@ -22,15 +23,15 @@ public:
     delete[] radios;
   }
 
-  typedef std::function<void(uint8_t* packet, const MiLightRadioConfig& config)> PacketSentHandler;
+  typedef std::function<void(uint8_t* packet, const MiLightRemoteConfig& config)> PacketSentHandler;
 
   void begin();
-  void prepare(MiLightRadioConfig& config, const uint16_t deviceId = -1, const uint8_t groupId = -1);
-  void prepare(MiLightRadioType config, const uint16_t deviceId = -1, const uint8_t groupId = -1);
+  void prepare(const MiLightRemoteConfig* remoteConfig, const uint16_t deviceId = -1, const uint8_t groupId = -1);
+  void prepare(const MiLightRemoteType type, const uint16_t deviceId = -1, const uint8_t groupId = -1);
 
   void setResendCount(const unsigned int resendCount);
   bool available();
-  void read(uint8_t packet[]);
+  size_t read(uint8_t packet[]);
   void write(uint8_t packet[]);
 
   void setHeld(bool held);
@@ -63,24 +64,26 @@ public:
 
   void updateSaturation(const uint8_t saturation);
 
-  void formatPacket(uint8_t* packet, char* buffer);
-
   void update(const JsonObject& object);
   void handleCommand(const String& command);
   void handleEffect(const String& effect);
-  
+
   void onPacketSent(PacketSentHandler handler);
 
+  size_t getNumRadios() const;
+  MiLightRadio* switchRadio(size_t radioIx);
+  MiLightRemoteConfig& currentRemoteConfig() const;
+
 protected:
 
   MiLightRadio** radios;
   MiLightRadio* currentRadio;
-  PacketFormatter* formatter;
+  const MiLightRemoteConfig* currentRemote;
   const size_t numRadios;
   unsigned int resendCount;
   PacketSentHandler packetSentHandler;
 
-  MiLightRadio* switchRadio(const MiLightRadioType type);
+  MiLightRadio* switchRadio(const MiLightRemoteConfig* remoteConfig);
   uint8_t parseStatus(const JsonObject& object);
 
   void flushPacket();

+ 5 - 29
lib/MiLight/MiLightRadioConfig.cpp

@@ -1,32 +1,8 @@
 #include <MiLightRadioConfig.h>
 
-MiLightRadioConfig* MiLightRadioConfig::ALL_CONFIGS[] = {
-  &MilightRgbwConfig,
-  &MilightCctConfig,
-  &MilightRgbCctConfig,
-  &MilightRgbConfig
+MiLightRadioConfig MiLightRadioConfig::ALL_CONFIGS[] = {
+  MiLightRadioConfig(0x147A, 0x258B, 7, 9, 40, 71), // rgbw
+  MiLightRadioConfig(0x050A, 0x55AA, 7, 4, 39, 74), // cct
+  MiLightRadioConfig(0x7236, 0x1809, 9, 8, 39, 70), // rgb+cct, fut089
+  MiLightRadioConfig(0x9AAB, 0xBCCD, 6, 3, 38, 73)  // rgb
 };
-
-MiLightRadioConfig* MiLightRadioConfig::fromString(const String& s) {
-  for (size_t i = 0; i < MiLightRadioConfig::NUM_CONFIGS; i++) {
-    MiLightRadioConfig* config = MiLightRadioConfig::ALL_CONFIGS[i];
-    if (s.equalsIgnoreCase(config->name)) {
-      return config;
-    }
-  }
-  return NULL;
-}
-
-MiLightRadioConfig* MiLightRadioConfig::fromType(MiLightRadioType type) {
-  for (size_t i = 0; i < MiLightRadioConfig::NUM_CONFIGS; i++) {
-    MiLightRadioConfig* config = MiLightRadioConfig::ALL_CONFIGS[i];
-    if (config->type == type) {
-      return config;
-    }
-  }
-  return NULL;
-}
-
-size_t MiLightRadioConfig::getPacketLength() const {
-  return packetFormatter->getPacketLength();
-}

+ 14 - 40
lib/MiLight/MiLightRadioConfig.h

@@ -1,31 +1,27 @@
 #include <Arduino.h>
-#include <PacketFormatter.h>
-#include <RgbCctPacketFormatter.h>
-#include <RgbwPacketFormatter.h>
-#include <CctPacketFormatter.h>
-#include <RgbPacketFormatter.h>
 #include <MiLightButtons.h>
+#include <Size.h>
 
 #ifndef _MILIGHT_RADIO_CONFIG
 #define _MILIGHT_RADIO_CONFIG
 
+#define MILIGHT_MAX_PACKET_LENGTH 9
+
 class MiLightRadioConfig {
 public:
   static const size_t NUM_CHANNELS = 3;
 
-  MiLightRadioConfig(const uint16_t syncword0,
-  const uint16_t syncword3,
-  PacketFormatter* packetFormatter,
-  const MiLightRadioType type,
-  const char* name,
-  const uint8_t channel0,
-  const uint8_t channel1,
-  const uint8_t channel2)
+  MiLightRadioConfig(
+    const uint16_t syncword0,
+    const uint16_t syncword3,
+    const size_t packetLength,
+    const uint8_t channel0,
+    const uint8_t channel1,
+    const uint8_t channel2
+  )
     : syncword0(syncword0),
       syncword3(syncword3),
-      packetFormatter(packetFormatter),
-      type(type),
-      name(name)
+      packetLength(packetLength)
   {
     channels[0] = channel0;
     channels[1] = channel1;
@@ -35,32 +31,10 @@ public:
   const uint16_t syncword0;
   const uint16_t syncword3;
   uint8_t channels[3];
-  PacketFormatter* packetFormatter;
-  const MiLightRadioType type;
-  const char* name;
+  const size_t packetLength;
 
   static const size_t NUM_CONFIGS = 4;
-  static MiLightRadioConfig* ALL_CONFIGS[NUM_CONFIGS];
-
-  static MiLightRadioConfig* fromString(const String& s);
-  static MiLightRadioConfig* fromType(MiLightRadioType type);
-  size_t getPacketLength() const;
+  static MiLightRadioConfig ALL_CONFIGS[NUM_CONFIGS];
 };
 
-static MiLightRadioConfig MilightRgbwConfig(
-  0x147A, 0x258B, new RgbwPacketFormatter(), RGBW, "rgbw", 9, 40, 71
-);
-
-static MiLightRadioConfig MilightCctConfig(
-  0x050A, 0x55AA, new CctPacketFormatter(), CCT, "cct", 4, 39, 74
-);
-
-static MiLightRadioConfig MilightRgbCctConfig(
-  0x7236, 0x1809, new RgbCctPacketFormatter(), RGB_CCT, "rgb_cct", 8, 39, 70
-);
-
-static MiLightRadioConfig MilightRgbConfig(
-  0x9AAB, 0xBCCD, new RgbPacketFormatter(), RGB, "rgb", 3, 38, 73
-);
-
 #endif

+ 1 - 1
lib/MiLight/MiLightRadioFactory.h

@@ -15,7 +15,7 @@ public:
   virtual MiLightRadio* create(const MiLightRadioConfig& config) = 0;
 
   static MiLightRadioFactory* fromSettings(const Settings& settings);
-  
+
 };
 
 class NRF24Factory : public MiLightRadioFactory {

+ 60 - 0
lib/MiLight/MiLightRemoteConfig.cpp

@@ -0,0 +1,60 @@
+#include <MiLightRemoteConfig.h>
+
+const MiLightRemoteConfig* MiLightRemoteConfig::ALL_REMOTES[] = {
+  &FUT096Config,
+  &FUT091Config,
+  &FUT092Config,
+  &FUT089Config,
+  &FUT098Config
+};
+
+const MiLightRemoteConfig* MiLightRemoteConfig::fromType(const String& type) {
+  if (type.equalsIgnoreCase("rgb") || type.equalsIgnoreCase("fut096")) {
+    return &FUT096Config;
+  }
+
+  if (type.equalsIgnoreCase("cct") || type.equalsIgnoreCase("fut091")) {
+    return &FUT091Config;
+  }
+
+  if (type.equalsIgnoreCase("rgb_cct") || type.equalsIgnoreCase("fut092")) {
+    return &FUT092Config;
+  }
+
+  if (type.equalsIgnoreCase("fut089")) {
+    return &FUT089Config;
+  }
+
+  return NULL;
+}
+
+const MiLightRemoteConfig* MiLightRemoteConfig::fromType(MiLightRemoteType type) {
+  switch (type) {
+    case REMOTE_TYPE_RGB:
+      return &FUT096Config;
+    case REMOTE_TYPE_CCT:
+      return &FUT091Config;
+    case REMOTE_TYPE_RGB_CCT:
+      return &FUT092Config;
+    case REMOTE_TYPE_FUT089:
+      return &FUT089Config;
+    default:
+      return NULL;
+  }
+}
+
+const MiLightRemoteConfig* MiLightRemoteConfig::fromReceivedPacket(
+  const MiLightRadioConfig& radioConfig,
+  const uint8_t* packet,
+  const size_t len
+) {
+  for (size_t i = 0; i < MiLightRemoteConfig::NUM_REMOTES; i++) {
+    const MiLightRemoteConfig* config = MiLightRemoteConfig::ALL_REMOTES[i];
+    if (&config->radioConfig == &radioConfig
+      && config->packetFormatter->canHandle(packet, len)) {
+      return config;
+    }
+  }
+
+  return NULL;
+}

+ 75 - 0
lib/MiLight/MiLightRemoteConfig.h

@@ -0,0 +1,75 @@
+#include <MiLightRadioConfig.h>
+#include <PacketFormatter.h>
+
+#include <RgbwPacketFormatter.h>
+#include <RgbPacketFormatter.h>
+#include <RgbCctPacketFormatter.h>
+#include <CctPacketFormatter.h>
+#include <FUT089PacketFormatter.h>
+#include <PacketFormatter.h>
+
+#ifndef _MILIGHT_REMOTE_CONFIG_H
+#define _MILIGHT_REMOTE_CONFIG_H
+
+class MiLightRemoteConfig {
+public:
+  MiLightRemoteConfig(
+    PacketFormatter* packetFormatter,
+    MiLightRadioConfig& radioConfig,
+    const MiLightRemoteType type,
+    const String name
+  ) : packetFormatter(packetFormatter),
+      radioConfig(radioConfig),
+      type(type),
+      name(name)
+  { }
+
+  PacketFormatter* const packetFormatter;
+  const MiLightRadioConfig& radioConfig;
+  const MiLightRemoteType type;
+  const String name;
+
+  static const MiLightRemoteConfig* fromType(MiLightRemoteType type);
+  static const MiLightRemoteConfig* fromType(const String& type);
+  static const MiLightRemoteConfig* fromReceivedPacket(const MiLightRadioConfig& radioConfig, const uint8_t* packet, const size_t len);
+
+  static const size_t NUM_REMOTES = 5;
+  static const MiLightRemoteConfig* ALL_REMOTES[NUM_REMOTES];
+};
+
+static const MiLightRemoteConfig FUT096Config( //rgbw
+  new RgbwPacketFormatter(),
+  MiLightRadioConfig::ALL_CONFIGS[0],
+  REMOTE_TYPE_RGBW,
+  "rgbw"
+);
+
+static const MiLightRemoteConfig FUT091Config( //cct
+  new CctPacketFormatter(),
+  MiLightRadioConfig::ALL_CONFIGS[1],
+  REMOTE_TYPE_CCT,
+  "cct"
+);
+
+static const MiLightRemoteConfig FUT092Config( //rgb+cct
+  new RgbCctPacketFormatter(),
+  MiLightRadioConfig::ALL_CONFIGS[2],
+  REMOTE_TYPE_RGB_CCT,
+  "rgb_cct"
+);
+
+static const MiLightRemoteConfig FUT089Config( //rgb+cct B8 / FUT089
+  new FUT089PacketFormatter(),
+  MiLightRadioConfig::ALL_CONFIGS[2],
+  REMOTE_TYPE_FUT089,
+  "fut089"
+);
+
+static const MiLightRemoteConfig FUT098Config( //rgb
+  new RgbPacketFormatter(),
+  MiLightRadioConfig::ALL_CONFIGS[3],
+  REMOTE_TYPE_RGB,
+  "rgb"
+);
+
+#endif

+ 1 - 1
lib/MiLight/NRF24MiLightRadio.cpp

@@ -49,7 +49,7 @@ int NRF24MiLightRadio::configure() {
   }
 
   // +1 to be able to buffer the length
-  retval = _pl1167.setMaxPacketLength(_config.getPacketLength() + 1);
+  retval = _pl1167.setMaxPacketLength(_config.packetLength + 1);
   if (retval < 0) {
     return retval;
   }

+ 4 - 0
lib/MiLight/PacketFormatter.cpp

@@ -29,6 +29,10 @@ PacketFormatter::PacketFormatter(const size_t packetLength, const size_t maxPack
   packetStream.packetStream = PACKET_BUFFER;
 }
 
+bool PacketFormatter::canHandle(const uint8_t *packet, const size_t len) {
+  return len == packetLength;
+}
+
 void PacketFormatter::finalizePacket(uint8_t* packet) { }
 
 void PacketFormatter::updateStatus(MiLightStatus status) {

+ 4 - 2
lib/MiLight/PacketFormatter.h

@@ -4,11 +4,11 @@
 #include <MiLightButtons.h>
 #include <ArduinoJson.h>
 
-#define PACKET_FORMATTER_BUFFER_SIZE 48
-
 #ifndef _PACKET_FORMATTER_H
 #define _PACKET_FORMATTER_H
 
+#define PACKET_FORMATTER_BUFFER_SIZE 48
+
 struct PacketStream {
   PacketStream();
 
@@ -27,6 +27,8 @@ public:
 
   typedef void (PacketFormatter::*StepFunction)();
 
+  virtual bool canHandle(const uint8_t* packet, const size_t len);
+
   void updateStatus(MiLightStatus status);
   virtual void updateStatus(MiLightStatus status, uint8_t groupId);
   virtual void command(uint8_t command, uint8_t arg);

+ 3 - 3
lib/MiLight/RgbCctPacketFormatter.cpp

@@ -1,6 +1,6 @@
 #include <RgbCctPacketFormatter.h>
-#include <Units.h>
 #include <V2RFEncoding.h>
+#include <Units.h>
 
 void RgbCctPacketFormatter::modeSpeedDown() {
   command(RGB_CCT_ON, RGB_CCT_MODE_SPEED_DOWN);
@@ -55,8 +55,8 @@ void RgbCctPacketFormatter::enableNightMode() {
 }
 
 void RgbCctPacketFormatter::parsePacket(const uint8_t *packet, JsonObject& result) {
-  uint8_t packetCopy[RGB_CCT_PACKET_LEN];
-  memcpy(packetCopy, packet, RGB_CCT_PACKET_LEN);
+  uint8_t packetCopy[V2_PACKET_LEN];
+  memcpy(packetCopy, packet, V2_PACKET_LEN);
   V2RFEncoding::decodeV2Packet(packetCopy);
 
   result["device_id"] = (packetCopy[2] << 8) | packetCopy[3];

+ 3 - 4
lib/MiLight/RgbCctPacketFormatter.h

@@ -1,7 +1,9 @@
 #include <V2PacketFormatter.h>
 
+#ifndef _RGB_CCT_PACKET_FORMATTER_H
+#define _RGB_CCT_PACKET_FORMATTER_H
+
 #define RGB_CCT_NUM_MODES 9
-#define RGB_CCT_PACKET_LEN 9
 
 #define RGB_CCT_COLOR_OFFSET 0x5F
 #define RGB_CCT_BRIGHTNESS_OFFSET 0x8F
@@ -12,9 +14,6 @@
 #define RGB_CCT_KELVIN_REMOTE_OFFSET 0x4C
 #define RGB_CCT_KELVIN_REMOTE_START  0xE8
 
-#ifndef _RGB_CCT_PACKET_FORMATTER_H
-#define _RGB_CCT_PACKET_FORMATTER_H
-
 enum MiLightRgbCctCommand {
   RGB_CCT_ON = 0x01,
   RGB_CCT_OFF = 0x01,

+ 1 - 1
lib/MiLight/RgbPacketFormatter.cpp

@@ -4,7 +4,7 @@
 void RgbPacketFormatter::initializePacket(uint8_t *packet) {
   size_t packetPtr = 0;
 
-  packet[packetPtr++] = RGB;
+  packet[packetPtr++] = 0xA4;
   packet[packetPtr++] = deviceId >> 8;
   packet[packetPtr++] = deviceId & 0xFF;
   packet[packetPtr++] = 0;

+ 3 - 3
lib/MiLight/RgbwPacketFormatter.cpp

@@ -8,7 +8,7 @@
 void RgbwPacketFormatter::initializePacket(uint8_t* packet) {
   size_t packetPtr = 0;
 
-  packet[packetPtr++] = RGBW;
+  packet[packetPtr++] = RGBW_PROTOCOL_ID_BYTE;
   packet[packetPtr++] = deviceId >> 8;
   packet[packetPtr++] = deviceId & 0xFF;
   packet[packetPtr++] = 0;
@@ -42,7 +42,7 @@ void RgbwPacketFormatter::previousMode() {
 
 void RgbwPacketFormatter::updateMode(uint8_t mode) {
   command(RGBW_DISCO_MODE, 0);
-  currentPacket[0] = RGBW | mode;
+  currentPacket[0] = RGBW_PROTOCOL_ID_BYTE | mode;
 }
 
 void RgbwPacketFormatter::updateStatus(MiLightStatus status, uint8_t groupId) {
@@ -121,7 +121,7 @@ void RgbwPacketFormatter::parsePacket(const uint8_t* packet, JsonObject& result)
   } else if (command == RGBW_SPEED_UP) {
     result["command"] = "mode_speed_up";
   } else if (command == RGBW_DISCO_MODE) {
-    result["mode"] = packet[0] & ~RGBW;
+    result["mode"] = packet[0] & ~RGBW_PROTOCOL_ID_BYTE;
   } else {
     result["button_id"] = command;
   }

+ 2 - 0
lib/MiLight/RgbwPacketFormatter.h

@@ -3,6 +3,8 @@
 #ifndef _RGBW_PACKET_FORMATTER_H
 #define _RGBW_PACKET_FORMATTER_H
 
+#define RGBW_PROTOCOL_ID_BYTE 0xB0
+
 enum MiLightRgbwButton {
   RGBW_ALL_ON            = 0x01,
   RGBW_ALL_OFF           = 0x02,

+ 7 - 0
lib/MiLight/V2PacketFormatter.cpp

@@ -9,6 +9,13 @@ V2PacketFormatter::V2PacketFormatter(uint8_t protocolId, uint8_t numGroups)
     numGroups(numGroups)
 { }
 
+bool V2PacketFormatter::canHandle(const uint8_t *packet, const size_t packetLen) {
+  uint8_t packetCopy[V2_PACKET_LEN];
+  memcpy(packetCopy, packet, V2_PACKET_LEN);
+  V2RFEncoding::decodeV2Packet(packetCopy);
+  return packetCopy[V2_PROTOCOL_ID_INDEX] == protocolId;
+}
+
 void V2PacketFormatter::initializePacket(uint8_t* packet) {
   size_t packetPtr = 0;
 

+ 5 - 1
lib/MiLight/V2PacketFormatter.h

@@ -4,6 +4,9 @@
 #ifndef _V2_PACKET_FORMATTER
 #define _V2_PACKET_FORMATTER
 
+#define V2_PACKET_LEN 9
+
+#define V2_PROTOCOL_ID_INDEX 1
 #define V2_COMMAND_INDEX 4
 #define V2_ARGUMENT_INDEX 5
 
@@ -11,6 +14,7 @@ class V2PacketFormatter : public PacketFormatter {
 public:
   V2PacketFormatter(uint8_t protocolId, uint8_t numGroups);
 
+  virtual bool canHandle(const uint8_t* packet, const size_t packetLen);
   virtual void initializePacket(uint8_t* packet);
 
   virtual void updateStatus(MiLightStatus status, uint8_t group);
@@ -22,7 +26,7 @@ public:
 
   uint8_t groupCommandArg(MiLightStatus status, uint8_t groupId);
 
-private:
+protected:
   const uint8_t protocolId;
   const uint8_t numGroups;
 };

+ 5 - 5
lib/Udp/V5MiLightUdpServer.cpp

@@ -16,20 +16,20 @@ void V5MiLightUdpServer::handleCommand(uint8_t command, uint8_t commandArg) {
     const MiLightStatus status = (command % 2) == 1 ? ON : OFF;
     const uint8_t groupId = (command - UDP_RGBW_GROUP_1_ON + 2)/2;
 
-    client->prepare(MilightRgbwConfig, deviceId, groupId);
+    client->prepare(&FUT098Config, deviceId, groupId);
     client->updateStatus(status);
 
     this->lastGroup = groupId;
   // Command set_white for RGBW
   } else if (command >= UDP_RGBW_GROUP_ALL_WHITE && command <= UDP_RGBW_GROUP_4_WHITE) {
     const uint8_t groupId = (command - UDP_RGBW_GROUP_ALL_WHITE)/2;
-    client->prepare(MilightRgbwConfig, deviceId, groupId);
+    client->prepare(&FUT096Config, deviceId, groupId);
     client->updateColorWhite();
     this->lastGroup = groupId;
     // On/off for CCT
   } else if (CctPacketFormatter::cctCommandIdToGroup(command) != 255) {
     uint8_t cctGroup = CctPacketFormatter::cctCommandIdToGroup(command);
-    client->prepare(MilightCctConfig, deviceId, cctGroup);
+    client->prepare(&FUT091Config, deviceId, cctGroup);
     this->lastGroup = cctGroup;
 
     // Night mode commands are same as off commands with MSB set
@@ -39,7 +39,7 @@ void V5MiLightUdpServer::handleCommand(uint8_t command, uint8_t commandArg) {
       client->updateStatus(CctPacketFormatter::cctCommandToStatus(command));
     }
   } else {
-    client->prepare(MilightRgbwConfig, deviceId, lastGroup);
+    client->prepare(&FUT096Config, deviceId, lastGroup);
     bool handled = true;
 
     switch (command) {
@@ -84,7 +84,7 @@ void V5MiLightUdpServer::handleCommand(uint8_t command, uint8_t commandArg) {
       return;
     }
 
-    client->prepare(MilightCctConfig, deviceId, lastGroup);
+    client->prepare(&FUT091Config, deviceId, lastGroup);
 
     switch(command) {
       case UDP_CCT_BRIGHTNESS_DOWN:

+ 1 - 1
lib/Udp/V6CctCommandHandler.h

@@ -18,7 +18,7 @@ enum CctCommandIds {
 class V6CctCommandHandler : public V6CommandHandler {
 public:
   V6CctCommandHandler()
-    : V6CommandHandler(0x0100, MilightCctConfig)
+    : V6CommandHandler(0x0100, FUT091Config)
   { }
 
   virtual bool handleCommand(

+ 1 - 1
lib/Udp/V6ComamndHandler.cpp

@@ -21,7 +21,7 @@ bool V6CommandHandler::handleCommand(MiLightClient* client,
   uint32_t command,
   uint32_t commandArg)
 {
-  client->prepare(radioConfig, deviceId, group);
+  client->prepare(&remoteConfig, deviceId, group);
 
   if (commandType == V6_PAIR) {
     client->pair();

+ 4 - 4
lib/Udp/V6CommandHandler.h

@@ -16,9 +16,9 @@ public:
   static V6CommandHandler* ALL_HANDLERS[];
   static const size_t NUM_HANDLERS;
 
-  V6CommandHandler(uint16_t commandId, MiLightRadioConfig& radioConfig)
+  V6CommandHandler(uint16_t commandId, const MiLightRemoteConfig& remoteConfig)
     : commandId(commandId),
-      radioConfig(radioConfig)
+      remoteConfig(remoteConfig)
   { }
 
   virtual bool handleCommand(
@@ -31,7 +31,7 @@ public:
   );
 
   const uint16_t commandId;
-  MiLightRadioConfig& radioConfig;
+  const MiLightRemoteConfig& remoteConfig;
 
 protected:
 
@@ -51,7 +51,7 @@ protected:
 class V6CommandDemuxer : public V6CommandHandler {
 public:
   V6CommandDemuxer(V6CommandHandler* handlers[], size_t numHandlers)
-    : V6CommandHandler(0, MilightRgbwConfig),
+    : V6CommandHandler(0, FUT096Config),
       handlers(handlers),
       numHandlers(numHandlers)
   { }

+ 1 - 1
lib/Udp/V6RgbCctCommandHandler.h

@@ -23,7 +23,7 @@ enum V2CommandArgIds {
 class V6RgbCctCommandHandler : public V6CommandHandler {
 public:
   V6RgbCctCommandHandler()
-    : V6CommandHandler(0x0800, MilightRgbCctConfig)
+    : V6CommandHandler(0x0800, FUT092Config)
   { }
 
   virtual bool handleCommand(

+ 1 - 1
lib/Udp/V6RgbCommandHandler.h

@@ -19,7 +19,7 @@ enum RgbCommandIds {
 class V6RgbCommandHandler : public V6CommandHandler {
 public:
   V6RgbCommandHandler()
-    : V6CommandHandler(0x0500, MilightRgbConfig)
+    : V6CommandHandler(0x0500, FUT098Config)
   { }
 
   virtual bool handleCommand(

+ 1 - 1
lib/Udp/V6RgbwCommandHandler.h

@@ -20,7 +20,7 @@ enum RgbwCommandIds {
 class V6RgbwCommandHandler : public V6CommandHandler {
 public:
   V6RgbwCommandHandler()
-    : V6CommandHandler(0x0700, MilightRgbwConfig)
+    : V6CommandHandler(0x0700, FUT096Config)
   { }
 
   virtual bool handleCommand(

+ 39 - 36
lib/WebServer/MiLightHttpServer.cpp

@@ -161,7 +161,7 @@ void MiLightHttpServer::handleGetRadioConfigs() {
   JsonArray& arr = buffer.createArray();
 
   for (size_t i = 0; i < MiLightRadioConfig::NUM_CONFIGS; i++) {
-    const MiLightRadioConfig* config = MiLightRadioConfig::ALL_CONFIGS[i];
+    const MiLightRemoteConfig* config = MiLightRemoteConfig::ALL_REMOTES[i];
     arr.add(config->name);
   }
 
@@ -235,17 +235,18 @@ void MiLightHttpServer::handleUpdateSettings() {
 void MiLightHttpServer::handleListenGateway(const UrlTokenBindings* bindings) {
   bool available = false;
   bool listenAll = bindings == NULL;
-  uint8_t configIx = 0;
-  MiLightRadioConfig* currentConfig =
-    listenAll
-      ? MiLightRadioConfig::ALL_CONFIGS[0]
-      : MiLightRadioConfig::fromString(bindings->get("type"));
-
-  if (currentConfig == NULL && bindings != NULL) {
-    String body = "Unknown device type: ";
-    body += bindings->get("type");
+  size_t configIx = 0;
+  const MiLightRadioConfig* radioConfig = NULL;
+
+  if (bindings != NULL) {
+    String strType(bindings->get("type"));
+    const MiLightRemoteConfig* remoteConfig = MiLightRemoteConfig::fromType(strType);
+    milightClient->prepare(remoteConfig, 0, 0);
+    radioConfig = &remoteConfig->radioConfig;
+  }
 
-    server.send(400, "text/plain", body);
+  if (radioConfig == NULL && !listenAll) {
+    server.send_P(400, TEXT_PLAIN, PSTR("Unknown device type supplied."));
     return;
   }
 
@@ -255,11 +256,8 @@ void MiLightHttpServer::handleListenGateway(const UrlTokenBindings* bindings) {
     }
 
     if (listenAll) {
-      currentConfig = MiLightRadioConfig::ALL_CONFIGS[
-        configIx++ % MiLightRadioConfig::NUM_CONFIGS
-      ];
+      radioConfig = &milightClient->switchRadio(configIx++ % milightClient->getNumRadios())->config();
     }
-    milightClient->prepare(*currentConfig, 0, 0);
 
     if (milightClient->available()) {
       available = true;
@@ -268,8 +266,13 @@ void MiLightHttpServer::handleListenGateway(const UrlTokenBindings* bindings) {
     yield();
   }
 
-  uint8_t packet[currentConfig->getPacketLength()];
-  milightClient->read(packet);
+  uint8_t packet[MILIGHT_MAX_PACKET_LENGTH];
+  size_t packetLen = milightClient->read(packet);
+  const MiLightRemoteConfig* remoteConfig = MiLightRemoteConfig::fromReceivedPacket(
+    *radioConfig,
+    packet,
+    packetLen
+  );
 
   char response[200];
   char* responseBuffer = response;
@@ -277,10 +280,10 @@ void MiLightHttpServer::handleListenGateway(const UrlTokenBindings* bindings) {
   responseBuffer += sprintf_P(
     responseBuffer,
     PSTR("\n%s packet received (%d bytes):\n"),
-    currentConfig->name,
-    sizeof(packet)
+    remoteConfig->name.c_str(),
+    packetLen
   );
-  milightClient->formatPacket(packet, responseBuffer);
+  remoteConfig->packetFormatter->format(packet, responseBuffer);
 
   server.send(200, "text/plain", response);
 }
@@ -300,25 +303,25 @@ void MiLightHttpServer::handleUpdateGroup(const UrlTokenBindings* urlBindings) {
 
   String _deviceIds = urlBindings->get("device_id");
   String _groupIds = urlBindings->get("group_id");
-  String _radioTypes = urlBindings->get("type");
+  String _remoteTypes = urlBindings->get("type");
   char deviceIds[_deviceIds.length()];
   char groupIds[_groupIds.length()];
-  char radioTypes[_radioTypes.length()];
-  strcpy(radioTypes, _radioTypes.c_str());
+  char remoteTypes[_remoteTypes.length()];
+  strcpy(remoteTypes, _remoteTypes.c_str());
   strcpy(groupIds, _groupIds.c_str());
   strcpy(deviceIds, _deviceIds.c_str());
 
   TokenIterator deviceIdItr(deviceIds, _deviceIds.length());
   TokenIterator groupIdItr(groupIds, _groupIds.length());
-  TokenIterator radioTypesItr(radioTypes, _radioTypes.length());
+  TokenIterator remoteTypesItr(remoteTypes, _remoteTypes.length());
 
-  while (radioTypesItr.hasNext()) {
-    const char* _radioType = radioTypesItr.nextToken();
-    MiLightRadioConfig* config = MiLightRadioConfig::fromString(_radioType);
+  while (remoteTypesItr.hasNext()) {
+    const char* _remoteType = remoteTypesItr.nextToken();
+    const MiLightRemoteConfig* config = MiLightRemoteConfig::fromType(_remoteType);
 
     if (config == NULL) {
       char buffer[40];
-      sprintf_P(buffer, PSTR("Unknown device type: %s"), _radioType);
+      sprintf_P(buffer, PSTR("Unknown device type: %s"), _remoteType);
       server.send(400, "text/plain", buffer);
       return;
     }
@@ -331,7 +334,7 @@ void MiLightHttpServer::handleUpdateGroup(const UrlTokenBindings* urlBindings) {
       while (groupIdItr.hasNext()) {
         const uint8_t groupId = atoi(groupIdItr.nextToken());
 
-        milightClient->prepare(*config, deviceId, groupId);
+        milightClient->prepare(config, deviceId, groupId);
         handleRequest(request);
       }
     }
@@ -347,7 +350,7 @@ void MiLightHttpServer::handleRequest(const JsonObject& request) {
 void MiLightHttpServer::handleSendRaw(const UrlTokenBindings* bindings) {
   DynamicJsonBuffer buffer;
   JsonObject& request = buffer.parse(server.arg("plain"));
-  MiLightRadioConfig* config = MiLightRadioConfig::fromString(bindings->get("type"));
+  const MiLightRemoteConfig* config = MiLightRemoteConfig::fromType(bindings->get("type"));
 
   if (config == NULL) {
     char buffer[50];
@@ -356,16 +359,16 @@ void MiLightHttpServer::handleSendRaw(const UrlTokenBindings* bindings) {
     return;
   }
 
-  uint8_t packet[config->getPacketLength()];
+  uint8_t packet[MILIGHT_MAX_PACKET_LENGTH];
   const String& hexPacket = request["packet"];
-  hexStrToBytes<uint8_t>(hexPacket.c_str(), hexPacket.length(), packet, config->getPacketLength());
+  hexStrToBytes<uint8_t>(hexPacket.c_str(), hexPacket.length(), packet, MILIGHT_MAX_PACKET_LENGTH);
 
   size_t numRepeats = MILIGHT_DEFAULT_RESEND_COUNT;
   if (request.containsKey("num_repeats")) {
     numRepeats = request["num_repeats"];
   }
 
-  milightClient->prepare(*config, 0, 0);
+  milightClient->prepare(config, 0, 0);
 
   for (size_t i = 0; i < numRepeats; i++) {
     milightClient->write(packet);
@@ -388,7 +391,7 @@ void MiLightHttpServer::handleWsEvent(uint8_t num, WStype_t type, uint8_t *paylo
   }
 }
 
-void MiLightHttpServer::handlePacketSent(uint8_t *packet, const MiLightRadioConfig& config) {
+void MiLightHttpServer::handlePacketSent(uint8_t *packet, const MiLightRemoteConfig& config) {
   if (numWsClients > 0) {
     size_t packetLen = config.packetFormatter->getPacketLength();
     char buffer[packetLen*3];
@@ -401,8 +404,8 @@ void MiLightHttpServer::handlePacketSent(uint8_t *packet, const MiLightRadioConf
     sprintf_P(
       responseBuffer,
       PSTR("\n%s packet received (%d bytes):\n%s"),
-      config.name,
-      sizeof(packet),
+      config.name.c_str(),
+      packetLen,
       formattedPacket
     );
 

+ 1 - 1
lib/WebServer/MiLightHttpServer.h

@@ -29,7 +29,7 @@ public:
   void handleClient();
   void onSettingsSaved(SettingsSavedHandler handler);
   void on(const char* path, HTTPMethod method, ESP8266WebServer::THandlerFunction handler);
-  void handlePacketSent(uint8_t* packet, const MiLightRadioConfig& config);
+  void handlePacketSent(uint8_t* packet, const MiLightRemoteConfig& config);
   WiFiClient client();
 
 protected:

+ 3 - 3
platformio.ini

@@ -20,18 +20,18 @@ lib_deps_external =
   https://github.com/ratkins/RGBConverter
   Hash
   WebSockets
-build_flags = !python .get_version.py -DMQTT_MAX_PACKET_SIZE=200 -Idist
 extra_script =
   .build_web.py
+build_flags = !python .get_version.py -DMQTT_MAX_PACKET_SIZE=200 -Idist
+# -D DEBUG_PRINTF
 # -D MQTT_DEBUG
 # -D MILIGHT_UDP_DEBUG
-# -D DEBUG_PRINTF
 
 [env:nodemcuv2]
 platform = espressif8266
 framework = arduino
 board = nodemcuv2
-; upload_speed = 115200
+upload_speed = 115200
 build_flags = ${common.build_flags} -Wl,-Tesp8266.flash.4m1m.ld -D FIRMWARE_VARIANT=nodemcuv2
 extra_script = ${common.extra_script}
 lib_deps =

+ 25 - 10
src/main.cpp

@@ -6,6 +6,7 @@
 #include <IntParsing.h>
 #include <Size.h>
 #include <MiLightRadioConfig.h>
+#include <MiLightRemoteConfig.h>
 #include <MiLightHttpServer.h>
 #include <Settings.h>
 #include <MiLightUdpServer.h>
@@ -64,20 +65,26 @@ void initMilightUdpServers() {
   }
 }
 
-void onPacketSentHandler(uint8_t* packet, const MiLightRadioConfig& config) {
+void onPacketSentHandler(uint8_t* packet, const MiLightRemoteConfig& config) {
   StaticJsonBuffer<200> buffer;
   JsonObject& result = buffer.createObject();
   config.packetFormatter->parsePacket(packet, result);
 
+  if (!result.containsKey("device_id")
+    ||!result.containsKey("group_id")
+    ||!result.containsKey("device_type")) {
+    Serial.println(F("Skipping update because packet formatter didn't supply necessary information."));
+    return;
+  }
+
   uint16_t deviceId = result["device_id"];
   uint16_t groupId = result["group_id"];
-  MiLightRadioType type = MiLightRadioConfig::fromString(result["device_type"])->type;
 
   char output[200];
   result.printTo(output);
 
   if (mqttClient) {
-    mqttClient->sendUpdate(type, deviceId, groupId, output);
+    mqttClient->sendUpdate(config, deviceId, groupId, output);
   }
   httpServer->handlePacketSent(packet, config);
 }
@@ -87,17 +94,25 @@ void handleListen() {
     return;
   }
 
-  MiLightRadioConfig* config = MiLightRadioConfig::ALL_CONFIGS[
-    currentRadioType++ % MiLightRadioConfig::NUM_CONFIGS
-  ];
-  milightClient->prepare(*config);
+  MiLightRadio* radio = milightClient->switchRadio(currentRadioType++ % milightClient->getNumRadios());
 
   for (size_t i = 0; i < settings.listenRepeats; i++) {
     if (milightClient->available()) {
-      uint8_t readPacket[9];
-      milightClient->read(readPacket);
+      uint8_t readPacket[MILIGHT_MAX_PACKET_LENGTH];
+      size_t packetLen = milightClient->read(readPacket);
+
+      const MiLightRemoteConfig* remoteConfig = MiLightRemoteConfig::fromReceivedPacket(
+        radio->config(),
+        readPacket,
+        packetLen
+      );
+
+      if (remoteConfig == NULL) {
+        Serial.println(F("ERROR: Couldn't find remote for received packet!"));
+        return;
+      }
 
-      onPacketSentHandler(readPacket, *config);
+      onPacketSentHandler(readPacket, *remoteConfig);
     }
   }
 }