Chris Mullins před 8 roky
rodič
revize
f3b7cd5a95

+ 17 - 0
lib/MiLight/MiLightButtons.h

@@ -1,6 +1,23 @@
 #ifndef _MILIGHT_BUTTONS
 #define _MILIGHT_BUTTONS 
 
+enum MiLightRgbCctCommand {
+  RGB_CCT_ON = 0x01,
+  RGB_CCT_OFF = 0x01,
+  RGB_CCT_MODE_SPEED_UP = 0x01,
+  RGB_CCT_MODE_SPEED_DOWN = 0x01,
+  RGB_CCT_COLOR = 0x02,
+  RGB_CCT_KELVIN = 0x03,
+  RGB_CCT_BRIGHTNESS = 0x04,
+  RGB_CCT_SATURATION = 0x04,
+  RGB_CCT_MODE = 0x05,
+};
+
+enum MiLightStatus { 
+  ON = 0, 
+  OFF = 1 
+};
+
 enum MiLightRgbwButton {
   RGBW_ALL_ON            = 0x01,
   RGBW_ALL_OFF           = 0x02,

+ 112 - 8
lib/MiLight/MiLightClient.cpp

@@ -1,5 +1,19 @@
 #include <MiLightClient.h>
 #include <MiLightRadioConfig.h>
+#include <Arduino.h>
+
+#define V2_OFFSET(byte, key) ( V2_OFFSETS[byte-1][key%4] )
+
+uint8_t const MiLightClient::V2_OFFSETS[][4] = {
+  { 0x45, 0x1F, 0x14, 0x5C },
+  { 0xAB, 0x49, 0x63, 0x91 },
+  { 0x2D, 0x1F, 0x4A, 0xEB },
+  { 0xAF, 0x03, 0x1D, 0xF3 },
+  { 0x5A, 0x22, 0x30, 0x11 },
+  { 0x04, 0xD8, 0x71, 0x42 },
+  { 0xAF, 0x04, 0xDD, 0x07 },
+  { 0xE1, 0x93, 0xB8, 0xE4 }
+};
 
 MiLightRadio* MiLightClient::getRadio(const MiLightRadioType type) {
   MiLightRadioStack* stack = NULL;
@@ -55,17 +69,17 @@ void MiLightClient::read(const MiLightRadioType radioType, uint8_t packet[]) {
   radio->read(packet, length);
 }
 
-void MiLightClient::write(const MiLightRadioType radioType, 
+void MiLightClient::write(const MiLightRadioConfig& radioConfig, 
   uint8_t packet[]) {
     
-  MiLightRadio* radio = getRadio(radioType);
+  MiLightRadio* radio = getRadio(radioConfig.type);
   
   if (radio == NULL) {
     return;
   }
   
   for (int i = 0; i < this->resendCount; i++) {
-    radio->write(packet, MILIGHT_PACKET_LENGTH);
+    radio->write(packet, radioConfig.packetLength);
   }
 }
 
@@ -87,7 +101,7 @@ void MiLightClient::writeRgbw(
   packet[packetPtr++] = button;
   packet[packetPtr++] = nextSequenceNum();
   
-  write(RGBW, packet);
+  write(MilightRgbwConfig, packet);
 }
 
 void MiLightClient::writeCct(const uint16_t deviceId,
@@ -106,7 +120,37 @@ void MiLightClient::writeCct(const uint16_t deviceId,
   packet[packetPtr++] = sequenceNum;
   packet[packetPtr++] = sequenceNum;
   
-  write(CCT, packet);
+  write(MilightCctConfig, packet);
+}
+
+void MiLightClient::writeRgbCct(const uint16_t deviceId,
+  const uint8_t command,
+  const uint8_t arg,
+  const uint8_t group) {
+    
+  uint8_t packet[MilightRgbCctConfig.packetLength];
+  uint8_t sequenceNum = nextSequenceNum();
+  size_t packetPtr = 0;
+  
+  packet[packetPtr++] = 0x00;
+  packet[packetPtr++] = RGB_CCT;
+  packet[packetPtr++] = deviceId >> 8;
+  packet[packetPtr++] = deviceId & 0xFF;
+  packet[packetPtr++] = command;
+  packet[packetPtr++] = arg;
+  packet[packetPtr++] = sequenceNum;
+  packet[packetPtr++] = group;
+  packet[packetPtr++] = 0;
+  
+  printf("Constructed raw packet: ");
+  for (int i = 0; i < MilightRgbCctConfig.packetLength; i++) {
+    printf("%02X ", packet[i]);
+  }
+  printf("\n");
+  
+  encodeV2Packet(packet);
+  
+  write(MilightRgbCctConfig, packet);
 }
     
 void MiLightClient::updateColorRaw(const uint16_t deviceId, const uint8_t groupId, const uint16_t color) {
@@ -139,6 +183,13 @@ void MiLightClient::updateStatus(const MiLightRadioType type, const uint16_t dev
   if (type == RGBW) {
     uint8_t button = RGBW_GROUP_1_ON + ((groupId - 1)*2) + status;
     writeRgbw(deviceId, 0, 0, groupId, button);
+  } else if (type == RGB_CCT) {
+    writeRgbCct(
+      deviceId,
+      RGB_CCT_ON,
+      0xC0 + groupId + (status == OFF ? 0x05 : 0x00),
+      groupId
+    );
   } else {
     writeCct(deviceId, groupId, getCctStatusButton(groupId, status));
   }
@@ -150,17 +201,28 @@ void MiLightClient::updateColorWhite(const uint16_t deviceId, const uint8_t grou
 }
 
 void MiLightClient::pair(const MiLightRadioType type, const uint16_t deviceId, const uint8_t groupId) {
-  updateStatus(type, deviceId, groupId, ON);
+  if (type == RGBW || type == CCT) {
+    updateStatus(type, deviceId, groupId, ON);
+  } else if (type == RGB_CCT) {
+    updateStatus(type, deviceId, groupId, ON);
+    delay(1);
+    updateStatus(type, deviceId, groupId, ON);
+  }
 }
 
 void MiLightClient::unpair(const MiLightRadioType type, const uint16_t deviceId, const uint8_t groupId) {
   if (type == RGBW) {
-    updateStatus(RGBW, deviceId, groupId, ON);
+    updateStatus(type, deviceId, groupId, ON);
     delay(1);
     updateColorWhite(deviceId, groupId);
   } else if (type == CCT) {
     for (int i = 0; i < 5; i++) {
-      updateStatus(CCT, deviceId, groupId, ON);
+      updateStatus(type, deviceId, groupId, ON);
+      delay(1);
+    }
+  } else if (type == RGB_CCT) {
+    for (int i = 0; i < 5; i++) {
+      updateStatus(type, deviceId, 0, ON);
       delay(1);
     }
   }
@@ -179,6 +241,8 @@ void MiLightClient::allOn(const MiLightRadioType type, const uint16_t deviceId)
     writeRgbw(deviceId, 0, 0, 0, RGBW_ALL_ON);
   } else if (type == CCT) {
     writeCct(deviceId, 0, CCT_ALL_ON);
+  } else if (type == RGB_CCT) {
+    updateStatus(RGB_CCT, deviceId, 0, ON);
   }
 }
 
@@ -187,6 +251,8 @@ void MiLightClient::allOff(const MiLightRadioType type, const uint16_t deviceId)
     writeRgbw(deviceId, 0, 0, 0, RGBW_ALL_OFF);
   } else if (type == CCT) {
     writeCct(deviceId, 0, CCT_ALL_OFF);
+  } else if (type == RGB_CCT) {
+    updateStatus(RGB_CCT, deviceId, 0, OFF);
   }
 }
 
@@ -315,4 +381,42 @@ void MiLightClient::formatPacket(MiLightRadioConfig& config, uint8_t* packet, ch
     }
     sprintf(buffer, "\n\n");
   }
+}
+
+uint8_t MiLightClient::xorKey(uint8_t key) {
+  // Generate most significant nibble
+  const uint8_t shift = (key & 0x0F) < 0x04 ? 0 : 1;
+  const uint8_t x = (((key & 0xF0) >> 4) + shift + 6) % 8;
+  const uint8_t msn = (((4 + x) ^ 1) & 0x0F) << 4;
+
+  // Generate least significant nibble
+  const uint8_t lsn = ((((key & 0xF) + 4)^2) & 0x0F);
+
+  return ( msn | lsn );
+}
+
+uint8_t MiLightClient::encodeByte(uint8_t byte, uint8_t s1, uint8_t xorKey, uint8_t s2) {
+  uint8_t value = (byte + s1) % 0x100;
+  value = value ^ xorKey;
+  value = (value + s2) % 0x100;
+  
+  return value;
+}
+
+void MiLightClient::encodeV2Packet(uint8_t *packet) {
+  uint8_t key = xorKey(packet[0]);
+  uint8_t sum = key;
+  
+  for (size_t i = 1; i <= 7; i++) {
+    sum += packet[i];
+    packet[i] = encodeByte(packet[i], 0, key, V2_OFFSET(i, packet[0]));
+  }
+  
+  packet[8] = encodeByte(sum, 2, key, V2_OFFSET(8, packet[0]));
+  
+  printf("encoded packet: ");
+  for (int i = 0; i < MilightRgbCctConfig.packetLength; i++) {
+    printf("%02X ", packet[i]);
+  }
+  printf("\n");
 }

+ 18 - 8
lib/MiLight/MiLightClient.h

@@ -11,8 +11,6 @@
 #define MILIGHT_CCT_INTERVALS 10
 #define MILIGHT_DEFAULT_RESEND_COUNT 10
 
-enum MiLightStatus { ON = 0, OFF = 1 };
-
 class MiLightRadioStack {
 public:
   MiLightRadioStack(RF24& rf, const MiLightRadioConfig& config) 
@@ -40,6 +38,8 @@ private:
 
 class MiLightClient {
   public:
+    static uint8_t const V2_OFFSETS[][4];
+    
     MiLightClient(uint8_t cePin, uint8_t csnPin)
     : sequenceNum(0),
       rf(RF24(cePin, csnPin)),
@@ -66,7 +66,7 @@ class MiLightClient {
     
     bool available(const MiLightRadioType radioType);
     void read(const MiLightRadioType radioType, uint8_t packet[]);
-    void write(const MiLightRadioType radioType, uint8_t packet[]);
+    void write(const MiLightRadioConfig& radioConfig, uint8_t packet[]);
     
     void writeRgbw(
       const uint16_t deviceId,
@@ -82,12 +82,18 @@ class MiLightClient {
       const uint8_t button
     );
     
+    void writeRgbCct(const uint16_t deviceId,
+      const uint8_t command,
+      const uint8_t arg,
+      const uint8_t group
+    );
+    
     // Common methods
-    void updateStatus(const MiLightRadioType type,const uint16_t deviceId, const uint8_t groupId, MiLightStatus status);
-    void pair(const MiLightRadioType type,const uint16_t deviceId, const uint8_t groupId);
-    void unpair(const MiLightRadioType type,const uint16_t deviceId, const uint8_t groupId);
-    void allOn(const MiLightRadioType type,const uint16_t deviceId);
-    void allOff(const MiLightRadioType type,const uint16_t deviceId);
+    void updateStatus(const MiLightRadioType type, const uint16_t deviceId, const uint8_t groupId, MiLightStatus status);
+    void pair(const MiLightRadioType type, const uint16_t deviceId, const uint8_t groupId);
+    void unpair(const MiLightRadioType type, const uint16_t deviceId, const uint8_t groupId);
+    void allOn(const MiLightRadioType type, const uint16_t deviceId);
+    void allOff(const MiLightRadioType type, const uint16_t deviceId);
     void pressButton(const MiLightRadioType type, const uint16_t deviceId, const uint8_t groupId, uint8_t button);
     
     // RGBW methods
@@ -122,6 +128,10 @@ class MiLightClient {
     uint8_t sequenceNum;
     uint8_t nextSequenceNum();
     unsigned int resendCount;
+    
+    static void encodeV2Packet(uint8_t* packet);
+    static uint8_t xorKey(uint8_t key);
+    static uint8_t encodeByte(uint8_t byte, uint8_t s1, uint8_t xorKey, uint8_t s2);
 };
 
 #endif

+ 1 - 1
lib/MiLight/MiLightRadioConfig.h

@@ -7,7 +7,7 @@ enum MiLightRadioType {
   UNKNOWN = 0,
   RGBW  = 0xB8,
   CCT   = 0x5A,
-  RGB_CCT = 0x99
+  RGB_CCT = 0x20
 };
 
 class MiLightRadioConfig {

+ 60 - 0
lib/MiLight/PacketFormatter.h

@@ -0,0 +1,60 @@
+#include <Arduino.h>
+#include <inttypes.h>
+#include <MiLightButtons.h>
+
+#ifndef _PACKET_FORMATTER_H
+#define _PACKET_FORMATTER_H 
+
+class PacketFormatter {
+public:
+  PacketFormatter(const size_t packetLength) {
+    this->packet = new uint8_t[packetLength];
+  }
+  
+  ~PacketFormatter() {
+    delete this->packet;
+  }
+  
+  // all
+  virtual void updateStatus(MiLightStatus status);
+  virtual void updateBrightness(uint8_t value);
+  virtual void updateMode(uint8_t value);
+  virtual void modeSpeedDown();
+  virtual void modeSpeedUp();
+  
+  // rgbw, rgb+cct
+  virtual void updateHue(uint8_t value);
+  virtual void updateColorRaw(uint8_t value);
+  
+  // cct 
+  virtual void increaseTemperature(uint8_t value);
+  virtual void decreaseTemperature(uint8_t value);
+  
+  // rgb+cct
+  virtual void updateTemperature(uint8_t value);
+  virtual void updateSaturation(uint8_t value);
+  
+  virtual void reset() = 0;
+  
+  uint8_t const* buildPacket() {
+    return this->packet;
+  }
+  
+  void prepare(uint16_t deviceId, uint8_t groupId) {
+    this->deviceId = deviceId;
+    this->groupId = groupId;
+    reset();
+  }
+  
+  static uint8_t rescale(uint8_t value, uint8_t max) {
+    return round(value * (max / 255.0));
+  }
+  
+protected:
+  uint8_t* packet;
+  uint16_t deviceId;
+  uint8_t groupId;
+  uint8_t sequenceNum;
+};
+
+#endif

+ 25 - 0
lib/MiLight/RgbCctPacketFormatter.cpp

@@ -0,0 +1,25 @@
+#include <RgbCctPacketFormatter.h>
+
+void RgbCctPacketFormatter::reset() {
+  size_t packetPtr = 0;
+  
+  packet[packetPtr++] = 0x00;
+  packet[packetPtr++] = RGB_CCT;
+  packet[packetPtr++] = deviceId >> 8;
+  packet[packetPtr++] = deviceId & 0xFF;
+  packet[packetPtr++] = 0;
+  packet[packetPtr++] = 0;
+  packet[packetPtr++] = sequenceNum++;
+  packet[packetPtr++] = groupId;
+  packet[packetPtr++] = 0;
+}
+
+void RgbCctPacketFormatter::updateStatus(MiLightStatus status) {
+  packet[RGB_CCT_COMMAND_INDEX] = RGB_CCT_ON;
+  packet[RGB_CCT_ARGUMENT_INDEX] = 0xC0 + groupId + (status == OFF ? 5 : 0);
+}
+
+void RgbCctPacketFormatter::updateBrightness(uint8_t brightness) {
+  packet[RGB_CCT_COMMAND_INDEX] = RGB_CCT_BRIGHTNESS;
+  packet[RGB_CCT_ARGUMENT_INDEX] = 0x4F + rescale(brightness, 0x60);
+}

+ 22 - 0
lib/MiLight/RgbCctPacketFormatter.h

@@ -0,0 +1,22 @@
+#include <PacketFormatter.h>
+#include <MiLightRadioConfig.h>
+
+#define RGB_CCT_COMMAND_INDEX 4
+#define RGB_CCT_ARGUMENT_INDEX 5
+
+#ifndef _RGB_CCT_PACKET_FORMATTER_H
+#define _RGB_CCT_PACKET_FORMATTER_H 
+
+class RgbCctPacketFormatter : public PacketFormatter {
+public:
+  RgbCctPacketFormatter()
+    : PacketFormatter(MilightRgbCctConfig.packetLength)
+  { }
+  
+  virtual void reset();
+  
+  virtual void updateStatus(MiLightStatus status);
+  virtual void updateBrightness(uint8_t value);
+};
+
+#endif

+ 36 - 0
lib/readme.txt

@@ -0,0 +1,36 @@
+
+This directory is intended for the project specific (private) libraries.
+PlatformIO will compile them to static libraries and link to executable file.
+
+The source code of each library should be placed in separate directory, like
+"lib/private_lib/[here are source files]".
+
+For example, see how can be organized `Foo` and `Bar` libraries:
+
+|--lib
+|  |--Bar
+|  |  |--docs
+|  |  |--examples
+|  |  |--src
+|  |     |- Bar.c
+|  |     |- Bar.h
+|  |--Foo
+|  |  |- Foo.c
+|  |  |- Foo.h
+|  |- readme.txt --> THIS FILE
+|- platformio.ini
+|--src
+   |- main.c
+
+Then in `src/main.c` you should use:
+
+#include <Foo.h>
+#include <Bar.h>
+
+// rest H/C/CPP code
+
+PlatformIO will find your libraries automatically, configure preprocessor's
+include paths and build them.
+
+More information about PlatformIO Library Dependency Finder
+- http://docs.platformio.org/page/librarymanager/ldf.html