ソースを参照

First crack at passive listen, pushing updates to MQTT

Chris Mullins 8 年 前
コミット
530b23c405

+ 10 - 4
data/web/index.html

@@ -124,8 +124,9 @@
   <script lang="text/javascript">
     var FORM_SETTINGS = [
       "admin_username", "admin_password", "ce_pin", "csn_pin", "reset_pin","packet_repeats",
-      "http_repeat_factor", "auto_restart_period", "discovery_port", "mqtt_server", 
-      "mqtt_topic_pattern", "mqtt_username", "mqtt_password", "radio_interface_type"
+      "http_repeat_factor", "auto_restart_period", "discovery_port", "mqtt_server",
+      "mqtt_topic_pattern", "mqtt_update_topic_pattern", "mqtt_username", "mqtt_password",
+      "radio_interface_type", "listen_repeats"
     ];
 
     var FORM_SETTINGS_HELP = {
@@ -141,9 +142,14 @@
       mqtt_server : "Domain or IP address of MQTT broker. Optionally specify a port " +
         "with (example) mymqqtbroker.com:1884.",
       mqtt_topic_pattern : "Pattern for MQTT topics to listen on. Example: " +
-        "lights/:device_id/:type/:group. See README for further details.",
+        "lights/:device_id/:device_type/:group_id. See README for further details.",
+      mqtt_update_topic_pattern : "Pattern to publish MQTT updates. Packets that " +
+        "are received from other devices, and packets that are sent from this device will " +
+        "result in updates being sent.",
       discovery_port : "UDP port to listen for discovery packets on. Defaults to " +
-        "the same port used by MiLight devices, 48899. Use 0 to disable."
+        "the same port used by MiLight devices, 48899. Use 0 to disable.",
+      listen_repeats : "Increasing this increases the amount of time spent listening for " +
+        "packets. Set to 0 to disable listening. Default is 3."
     }
 
     var UDP_PROTOCOL_VERSIONS = [ 5, 6 ];

+ 21 - 0
lib/MQTT/MqttClient.cpp

@@ -85,6 +85,27 @@ void MqttClient::handleClient() {
   mqttClient->loop();
 }
 
+void MqttClient::sendUpdate(MiLightRadioType type, uint16_t deviceId, uint16_t groupId, const char* update) {
+  String topic = settings.mqttUpdateTopicPattern;
+
+  if (topic.length() == 0) {
+    return;
+  }
+
+  String deviceIdStr = String(deviceId, 16);
+  deviceIdStr.toUpperCase();
+
+  topic.replace(":device_id", String("0x") + deviceIdStr);
+  topic.replace(":group_id", String(groupId));
+  topic.replace(":device_type", MiLightRadioConfig::fromType(type)->name);
+
+#ifdef MQTT_DEBUG
+  printf_P(PSTR("MqttClient - publishing update to %s: %s\n"), topic.c_str(), update);
+#endif
+
+  mqttClient->publish(topic.c_str(), update);
+}
+
 void MqttClient::subscribe() {
   String topic = settings.mqttTopicPattern;
 

+ 1 - 0
lib/MQTT/MqttClient.h

@@ -18,6 +18,7 @@ public:
   void begin();
   void handleClient();
   void reconnect();
+  void sendUpdate(MiLightRadioType type, uint16_t deviceId, uint16_t groupId, const char* update);
 
 private:
   WiFiClient tcpClient;

+ 18 - 2
lib/MiLight/MiLightClient.cpp

@@ -9,7 +9,8 @@
 MiLightClient::MiLightClient(MiLightRadioFactory* radioFactory)
   : resendCount(MILIGHT_DEFAULT_RESEND_COUNT),
     currentRadio(NULL),
-    numRadios(MiLightRadioConfig::NUM_CONFIGS)
+    numRadios(MiLightRadioConfig::NUM_CONFIGS),
+    packetSentHandler(NULL)
 {
   radios = new MiLightRadio*[numRadios];
 
@@ -63,7 +64,14 @@ void MiLightClient::prepare(MiLightRadioConfig& config,
   const uint16_t deviceId,
   const uint8_t groupId) {
 
-  switchRadio(config.type);
+  prepare(config.type, deviceId, groupId);
+}
+
+void MiLightClient::prepare(MiLightRadioType type,
+  const uint16_t deviceId,
+  const uint8_t groupId) {
+
+  switchRadio(type);
 
   if (deviceId >= 0 && groupId >= 0) {
     formatter->prepare(deviceId, groupId);
@@ -110,6 +118,10 @@ void MiLightClient::write(uint8_t packet[]) {
     currentRadio->write(packet, currentRadio->config().getPacketLength());
   }
 
+  if (this->packetSentHandler) {
+    this->packetSentHandler(packet, currentRadio->config());
+  }
+
 #ifdef DEBUG_PRINTF
   int iElapsed = millis() - iStart;
   Serial.print("Elapsed: ");
@@ -375,3 +387,7 @@ void MiLightClient::flushPacket() {
   setResendCount(prevNumRepeats);
   formatter->reset();
 }
+
+void MiLightClient::onPacketSent(PacketSentHandler handler) {
+  this->packetSentHandler = handler;
+}

+ 8 - 1
lib/MiLight/MiLightClient.h

@@ -1,3 +1,4 @@
+#include <functional>
 #include <Arduino.h>
 #include <MiLightRadio.h>
 #include <MiLightRadioFactory.h>
@@ -11,6 +12,7 @@
 
 #define MILIGHT_DEFAULT_RESEND_COUNT 10
 
+
 class MiLightClient {
 public:
   MiLightClient(MiLightRadioFactory* radioFactory);
@@ -19,8 +21,11 @@ public:
     delete[] radios;
   }
 
+  typedef std::function<void(uint8_t* packet, const MiLightRadioConfig& 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 setResendCount(const unsigned int resendCount);
   bool available();
@@ -62,14 +67,16 @@ public:
   void update(const JsonObject& object);
   void handleCommand(const String& command);
 
+  void onPacketSent(PacketSentHandler handler);
+
 protected:
 
   MiLightRadio** radios;
   MiLightRadio* currentRadio;
   PacketFormatter* formatter;
   const size_t numRadios;
-
   unsigned int resendCount;
+  PacketSentHandler packetSentHandler;
 
   MiLightRadio* switchRadio(const MiLightRadioType type);
   uint8_t parseStatus(const JsonObject& object);

+ 18 - 12
lib/MiLight/MiLightRadioConfig.cpp

@@ -1,6 +1,6 @@
 #include <MiLightRadioConfig.h>
-  
-const MiLightRadioConfig* MiLightRadioConfig::ALL_CONFIGS[] = {
+
+MiLightRadioConfig* MiLightRadioConfig::ALL_CONFIGS[] = {
   &MilightRgbwConfig,
   &MilightCctConfig,
   &MilightRgbCctConfig,
@@ -8,19 +8,25 @@ const MiLightRadioConfig* MiLightRadioConfig::ALL_CONFIGS[] = {
 };
 
 MiLightRadioConfig* MiLightRadioConfig::fromString(const String& s) {
-  if (s.equalsIgnoreCase("rgbw")) {
-    return &MilightRgbwConfig;
-  } else if (s.equalsIgnoreCase("cct")) {
-    return &MilightCctConfig;
-  } else if (s.equalsIgnoreCase("rgb_cct")) {
-    return &MilightRgbCctConfig;
-  } else if (s.equalsIgnoreCase("rgb")) {
-    return &MilightRgbConfig;
+  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();
-}
+}

+ 8 - 7
lib/MiLight/MiLightRadioConfig.h

@@ -7,12 +7,12 @@
 #include <MiLightButtons.h>
 
 #ifndef _MILIGHT_RADIO_CONFIG
-#define _MILIGHT_RADIO_CONFIG 
+#define _MILIGHT_RADIO_CONFIG
 
 class MiLightRadioConfig {
 public:
   static const size_t NUM_CHANNELS = 3;
-  
+
   MiLightRadioConfig(const uint16_t syncword0,
   const uint16_t syncword3,
   PacketFormatter* packetFormatter,
@@ -20,7 +20,7 @@ public:
   const char* name,
   const uint8_t channel0,
   const uint8_t channel1,
-  const uint8_t channel2) 
+  const uint8_t channel2)
     : syncword0(syncword0),
       syncword3(syncword3),
       packetFormatter(packetFormatter),
@@ -31,18 +31,19 @@ public:
     channels[1] = channel1;
     channels[2] = channel2;
   }
-    
+
   const uint16_t syncword0;
   const uint16_t syncword3;
   uint8_t channels[3];
   PacketFormatter* packetFormatter;
   const MiLightRadioType type;
   const char* name;
-  
+
   static const size_t NUM_CONFIGS = 4;
-  static const MiLightRadioConfig* ALL_CONFIGS[NUM_CONFIGS];
-  
+  static MiLightRadioConfig* ALL_CONFIGS[NUM_CONFIGS];
+
   static MiLightRadioConfig* fromString(const String& s);
+  static MiLightRadioConfig* fromType(MiLightRadioType type);
   size_t getPacketLength() const;
 };
 

+ 2 - 0
lib/MiLight/PacketFormatter.cpp

@@ -61,6 +61,8 @@ void PacketFormatter::enableNightMode() { }
 void PacketFormatter::updateTemperature(uint8_t value) { }
 void PacketFormatter::updateSaturation(uint8_t value) { }
 
+void PacketFormatter::parsePacket(const uint8_t *packet, JsonObject &result) { }
+
 void PacketFormatter::pair() {
   for (size_t i = 0; i < 5; i++) {
     updateStatus(ON);

+ 3 - 0
lib/MiLight/PacketFormatter.h

@@ -2,6 +2,7 @@
 #include <inttypes.h>
 #include <functional>
 #include <MiLightButtons.h>
+#include <ArduinoJson.h>
 
 #define PACKET_FORMATTER_BUFFER_SIZE 48
 
@@ -66,6 +67,8 @@ public:
   virtual void prepare(uint16_t deviceId, uint8_t groupId);
   virtual void format(uint8_t const* packet, char* buffer);
 
+  virtual void parsePacket(const uint8_t* packet, JsonObject& result);
+
   static void formatV1Packet(uint8_t const* packet, char* buffer);
 
   template <typename T>

+ 21 - 0
lib/MiLight/RgbCctPacketFormatter.cpp

@@ -110,6 +110,27 @@ void RgbCctPacketFormatter::finalizePacket(uint8_t* packet) {
   encodeV2Packet(packet);
 }
 
+void RgbCctPacketFormatter::parsePacket(const uint8_t *packet, JsonObject& result) {
+  uint8_t packetCopy[RGB_CCT_PACKET_LEN];
+  memcpy(packetCopy, packet, RGB_CCT_PACKET_LEN);
+  decodeV2Packet(packetCopy);
+
+  result["device_id"] = (packetCopy[2] << 8) | packetCopy[3];
+  result["group_id"] = packetCopy[7];
+  result["device_type"] = "rgb_cct";
+
+  uint8_t command = packetCopy[RGB_CCT_COMMAND_INDEX];
+  uint8_t arg = packetCopy[RGB_CCT_ARGUMENT_INDEX];
+
+  if (command == RGB_CCT_ON) {
+    if (arg < 5) {
+      result["status"] = "on";
+    } else {
+      result["status"] = "off";
+    }
+  }
+}
+
 uint8_t RgbCctPacketFormatter::xorKey(uint8_t key) {
   // Generate most significant nibble
   const uint8_t shift = (key & 0x0F) < 0x04 ? 0 : 1;

+ 3 - 1
lib/MiLight/RgbCctPacketFormatter.h

@@ -4,6 +4,7 @@
 #define RGB_CCT_ARGUMENT_INDEX 5
 #define RGB_CCT_NUM_MODES 9
 #define V2_OFFSET_JUMP_START 0x54
+#define RGB_CCT_PACKET_LEN 9
 
 #ifndef _RGB_CCT_PACKET_FORMATTER_H
 #define _RGB_CCT_PACKET_FORMATTER_H
@@ -28,7 +29,7 @@ public:
   static uint8_t const V2_OFFSETS[][4] PROGMEM;
 
   RgbCctPacketFormatter()
-    : PacketFormatter(9),
+    : PacketFormatter(RGB_CCT_PACKET_LEN),
       lastMode(0)
   { }
 
@@ -53,6 +54,7 @@ public:
   virtual void previousMode();
 
   virtual void finalizePacket(uint8_t* packet);
+  virtual void parsePacket(const uint8_t* packet, JsonObject& result);
 
   static void encodeV2Packet(uint8_t* packet);
   static void decodeV2Packet(uint8_t* packet);

+ 4 - 0
lib/Settings/Settings.cpp

@@ -76,7 +76,9 @@ void Settings::patch(JsonObject& parsedSettings) {
     this->setIfPresent(parsedSettings, "mqtt_username", mqttUsername);
     this->setIfPresent(parsedSettings, "mqtt_password", mqttPassword);
     this->setIfPresent(parsedSettings, "mqtt_topic_pattern", mqttTopicPattern);
+    this->setIfPresent(parsedSettings, "mqtt_update_topic_pattern", mqttUpdateTopicPattern);
     this->setIfPresent(parsedSettings, "discovery_port", discoveryPort);
+    this->setIfPresent(parsedSettings, "listen_repeats", listenRepeats);
 
     if (parsedSettings.containsKey("radio_interface_type")) {
       this->radioInterfaceType = Settings::typeFromString(parsedSettings["radio_interface_type"]);
@@ -140,7 +142,9 @@ void Settings::serialize(Stream& stream, const bool prettyPrint) {
   root["mqtt_username"] = this->mqttUsername;
   root["mqtt_password"] = this->mqttPassword;
   root["mqtt_topic_pattern"] = this->mqttTopicPattern;
+  root["mqtt_update_topic_pattern"] = this->mqttUpdateTopicPattern;
   root["discovery_port"] = this->discoveryPort;
+  root["listen_repeats"] = this->listenRepeats;
 
   if (this->deviceIds) {
     JsonArray& arr = jsonBuffer.createArray();

+ 3 - 0
lib/Settings/Settings.h

@@ -62,6 +62,7 @@ public:
     numGatewayConfigs(0),
     packetRepeats(10),
     httpRepeatFactor(5),
+    listenRepeats(3),
     _autoRestartPeriod(0),
     discoveryPort(48899)
   { }
@@ -107,7 +108,9 @@ public:
   String mqttUsername;
   String mqttPassword;
   String mqttTopicPattern;
+  String mqttUpdateTopicPattern;
   uint16_t discoveryPort;
+  uint8_t listenRepeats;
 
 protected:
   size_t _autoRestartPeriod;

+ 9 - 9
lib/WebServer/WebServer.h

@@ -16,30 +16,30 @@
 class WebServer : public ESP8266WebServer {
 public:
   WebServer(int port) : ESP8266WebServer(port) { }
-  
+
   bool matchesPattern(const String& pattern, const String& url);
   void onPattern(const String& pattern, const HTTPMethod method, PatternHandler::TPatternHandlerFn fn);
   void requireAuthentication(const String& username, const String& password);
   void disableAuthentication();
-  
-  inline bool clientConnected() { 
+
+  inline bool clientConnected() {
     return _currentClient && _currentClient.connected();
   }
-  
-  // These are copied / patched from ESP8266WebServer because they aren't 
+
+  // These are copied / patched from ESP8266WebServer because they aren't
   // virtual. (*barf*)
   void handleClient();
   void _handleRequest();
-  
+
   bool authenticationRequired() {
     return authEnabled;
   }
-  
+
 protected:
-  
+
   bool authEnabled;
   String username;
   String password;
 };
 
-#endif
+#endif

+ 44 - 1
src/main.cpp

@@ -6,7 +6,6 @@
 #include <GithubClient.h>
 #include <IntParsing.h>
 #include <Size.h>
-#include <MiLightClient.h>
 #include <MiLightRadioConfig.h>
 #include <MiLightHttpServer.h>
 #include <Settings.h>
@@ -16,6 +15,7 @@
 #include <MqttClient.h>
 #include <RGBConverter.h>
 #include <MiLightDiscoveryServer.h>
+#include <MiLightClient.h>
 
 WiFiManager wifiManager;
 
@@ -26,9 +26,11 @@ MiLightRadioFactory* radioFactory = NULL;
 MiLightHttpServer *httpServer = NULL;
 MqttClient* mqttClient = NULL;
 MiLightDiscoveryServer* discoveryServer = NULL;
+uint8_t currentRadioType = 0;
 
 int numUdpServers = 0;
 MiLightUdpServer** udpServers;
+WiFiUDP udpSeder;
 
 void initMilightUdpServers() {
   if (udpServers) {
@@ -63,6 +65,42 @@ void initMilightUdpServers() {
   }
 }
 
+void onPacketSentHandler(uint8_t* packet, const MiLightRadioConfig& config) {
+  StaticJsonBuffer<200> buffer;
+  JsonObject& result = buffer.createObject();
+  config.packetFormatter->parsePacket(packet, result);
+
+  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);
+  }
+}
+
+void handleListen() {
+  if (! settings.listenRepeats) {
+    return;
+  }
+
+  MiLightRadioConfig* config = MiLightRadioConfig::ALL_CONFIGS[
+    currentRadioType++ % MiLightRadioConfig::NUM_CONFIGS
+  ];
+  milightClient->prepare(*config);
+
+  for (size_t i = 0; i < settings.listenRepeats; i++) {
+    if (milightClient->available()) {
+      uint8_t readPacket[9];
+      milightClient->read(readPacket);
+
+      onPacketSentHandler(readPacket, *config);
+    }
+  }
+}
 
 void applySettings() {
   if (milightClient) {
@@ -83,6 +121,7 @@ void applySettings() {
 
   milightClient = new MiLightClient(radioFactory);
   milightClient->begin();
+  milightClient->onPacketSent(onPacketSentHandler);
 
   if (settings.mqttServer().length() > 0) {
     mqttClient = new MqttClient(settings, milightClient);
@@ -134,6 +173,8 @@ void setup() {
   httpServer->onSettingsSaved(applySettings);
   httpServer->on("/description.xml", HTTP_GET, []() { SSDP.schema(httpServer->client()); });
   httpServer->begin();
+
+  Serial.println(F("Setup complete"));
 }
 
 void loop() {
@@ -153,6 +194,8 @@ void loop() {
     discoveryServer->handleClient();
   }
 
+  handleListen();
+
   if (shouldRestart()) {
     Serial.println(F("Auto-restart triggered. Restarting..."));
     ESP.restart();