Forráskód Böngészése

Merge pull request #86 from sidoh/v1.3.0

v1.3.0
Chris Mullins 8 éve
szülő
commit
93e2500a5b

+ 39 - 4
data/web/index.html

@@ -12,7 +12,7 @@
   <link href="https://gitcdn.github.io/bootstrap-toggle/2.2.2/css/bootstrap-toggle.min.css" rel="stylesheet"/>
   <link href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/9.7.0/css/bootstrap-slider.min.css" rel="stylesheet"/>
   <link href="https://cdnjs.cloudflare.com/ajax/libs/selectize.js/0.12.4/css/selectize.bootstrap3.min.css" rel="stylesheet"/>
-
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <!--[if lt IE 9]>
     <script src="https://cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7.3/html5shiv.js"></script>
   <![endif]-->
@@ -124,8 +124,8 @@
   <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", "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_username", "mqtt_password", "radio_interface_type"
     ];
 
     var FORM_SETTINGS_HELP = {
@@ -141,7 +141,9 @@
       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/:type/:group. See README for further details.",
+      discovery_port : "UDP port to listen for discovery packets on. Defaults to " +
+        "the same port used by MiLight devices, 48899. Use 0 to disable."
     }
 
     var UDP_PROTOCOL_VERSIONS = [ 5, 6 ];
@@ -721,6 +723,29 @@
     </div>
   </div>
 
+  <div id="restore-settings-modal" class="modal fade" role="dialog">
+    <div class="modal-dialog">
+      <!-- Modal content-->
+      <div class="modal-content">
+        <div class="modal-header">
+          <button type="button" class="close" data-dismiss="modal">&times;</button>
+          <h2 class="modal-title">Restore Settings</h2>
+        </div>
+        <div class="modal-body">
+          <form action="/settings" method="post" enctype="multipart/form-data">
+            <input type="file" name="file"/>
+            <p>&nbsp;</p>
+            <input type="submit" name="submit" class="btn btn-success"/>
+          </form>
+        </div>
+        <div class="modal-footer">
+          <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+        </div>
+      </div>
+
+    </div>
+  </div>
+
   <div class="container">
     <div class="row header-row">
       <div class="col-sm-12">
@@ -1016,6 +1041,10 @@
           Restart
         </button>
 
+        <button type="button" class="btn btn-danger system-btn" data-command="clear_wifi_config">
+          Clear Wifi Config
+        </button>
+
         <button type="button" id="updates-btn" class="btn btn-primary">
           Check for Updates
         </button>
@@ -1023,6 +1052,12 @@
         <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#update-firmware-modal">
           Update Firmware
         </button>
+
+        <a href="/settings" download="settings.json" class="btn btn-primary">Backup Settings</a>
+
+        <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#restore-settings-modal">
+          Restore Settings
+        </button>
       </div>
     </div>
   </div>

+ 23 - 10
lib/MiLight/MiLightClient.cpp

@@ -222,17 +222,11 @@ void MiLightClient::command(uint8_t command, uint8_t arg) {
 }
 
 void MiLightClient::update(const JsonObject& request) {
-  if (request.containsKey("status") || request.containsKey("state")) {
-    String strStatus;
+  const uint8_t parsedStatus = this->parseStatus(request);
 
-    if (request.containsKey("status")) {
-      strStatus = request.get<char*>("status");
-    } else {
-      strStatus = request.get<char*>("state");
-    }
-
-    MiLightStatus status = (strStatus.equalsIgnoreCase("on") || strStatus.equalsIgnoreCase("true")) ? ON : OFF;
-    this->updateStatus(status);
+  // Always turn on first
+  if (parsedStatus == ON) {
+    this->updateStatus(ON);
   }
 
   if (request.containsKey("command")) {
@@ -308,6 +302,11 @@ void MiLightClient::update(const JsonObject& request) {
   if (request.containsKey("mode")) {
     this->updateMode(request["mode"]);
   }
+
+  // Always turn off last
+  if (parsedStatus == OFF) {
+    this->updateStatus(OFF);
+  }
 }
 
 void MiLightClient::handleCommand(const String& command) {
@@ -338,6 +337,20 @@ void MiLightClient::handleCommand(const String& command) {
   }
 }
 
+uint8_t MiLightClient::parseStatus(const JsonObject& object) {
+  String strStatus;
+
+  if (object.containsKey("status")) {
+    strStatus = object.get<char*>("status");
+  } else if (object.containsKey("state")) {
+    strStatus = object.get<char*>("state");
+  } else {
+    return 255;
+  }
+
+  return (strStatus.equalsIgnoreCase("on") || strStatus.equalsIgnoreCase("true")) ? ON : OFF;
+}
+
 void MiLightClient::formatPacket(uint8_t* packet, char* buffer) {
   formatter->format(packet, buffer);
 }

+ 1 - 0
lib/MiLight/MiLightClient.h

@@ -72,6 +72,7 @@ protected:
   unsigned int resendCount;
 
   MiLightRadio* switchRadio(const MiLightRadioType type);
+  uint8_t parseStatus(const JsonObject& object);
 
   void flushPacket();
 };

+ 2 - 0
lib/Settings/Settings.cpp

@@ -76,6 +76,7 @@ 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, "discovery_port", discoveryPort);
 
     if (parsedSettings.containsKey("radio_interface_type")) {
       this->radioInterfaceType = Settings::typeFromString(parsedSettings["radio_interface_type"]);
@@ -139,6 +140,7 @@ 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["discovery_port"] = this->discoveryPort;
 
   if (this->deviceIds) {
     JsonArray& arr = jsonBuffer.createArray();

+ 3 - 1
lib/Settings/Settings.h

@@ -62,7 +62,8 @@ public:
     numGatewayConfigs(0),
     packetRepeats(10),
     httpRepeatFactor(5),
-    _autoRestartPeriod(0)
+    _autoRestartPeriod(0),
+    discoveryPort(48899)
   { }
 
   ~Settings() {
@@ -106,6 +107,7 @@ public:
   String mqttUsername;
   String mqttPassword;
   String mqttTopicPattern;
+  uint16_t discoveryPort;
 
 protected:
   size_t _autoRestartPeriod;

+ 85 - 0
lib/Udp/MiLightDiscoveryServer.cpp

@@ -0,0 +1,85 @@
+#include <MiLightDiscoveryServer.h>
+#include <Size.h>
+#include <ESP8266WiFi.h>
+
+const char V3_SEARCH_STRING[] = "Link_Wi-Fi";
+const char V6_SEARCH_STRING[] = "HF-A11ASSISTHREAD";
+
+MiLightDiscoveryServer::MiLightDiscoveryServer(Settings& settings)
+  : settings(settings)
+{ }
+
+MiLightDiscoveryServer::MiLightDiscoveryServer(MiLightDiscoveryServer& other)
+  : settings(other.settings)
+{ }
+
+MiLightDiscoveryServer& MiLightDiscoveryServer::operator=(MiLightDiscoveryServer other) {
+  this->settings = other.settings;
+  this->socket = other.socket;
+}
+
+MiLightDiscoveryServer::~MiLightDiscoveryServer() {
+  socket.stop();
+}
+
+void MiLightDiscoveryServer::begin() {
+  socket.begin(settings.discoveryPort);
+}
+
+void MiLightDiscoveryServer::handleClient() {
+  size_t packetSize = socket.parsePacket();
+
+  if (packetSize) {
+    char buffer[size(V6_SEARCH_STRING) + 1];
+    socket.read(buffer, packetSize);
+    buffer[packetSize] = 0;
+
+#ifdef MILIGHT_UDP_DEBUG
+    printf("Got discovery packet: %s\n", buffer);
+#endif
+
+    if (strcmp(buffer, V3_SEARCH_STRING) == 0) {
+      handleDiscovery(5);
+    } else if (strcmp(buffer, V6_SEARCH_STRING) == 0) {
+      handleDiscovery(6);
+    }
+  }
+}
+
+void MiLightDiscoveryServer::handleDiscovery(uint8_t version) {
+#ifdef MILIGHT_UDP_DEBUG
+  printf("Handling discovery for version: %u, %d configs to consider\n", version, settings.numGatewayConfigs);
+#endif
+
+  char buffer[40];
+
+  for (size_t i = 0; i < settings.numGatewayConfigs; i++) {
+    GatewayConfig* config = settings.gatewayConfigs[i];
+
+    if (config->protocolVersion != version) {
+      continue;
+    }
+
+    IPAddress addr = WiFi.localIP();
+    char* ptr = buffer;
+    ptr += sprintf_P(
+      buffer,
+      PSTR("%d.%d.%d.%d,00000000%02X%02X"),
+      addr[0], addr[1], addr[2], addr[3],
+      (config->deviceId >> 8), (config->deviceId & 0xFF)
+    );
+
+    if (config->protocolVersion == 5) {
+      sendResponse(buffer);
+    } else {
+      sprintf_P(ptr, PSTR(",HF-LPB100"));
+      sendResponse(buffer);
+    }
+  }
+}
+
+void MiLightDiscoveryServer::sendResponse(char* buffer) {
+  socket.beginPacket(socket.remoteIP(), socket.remotePort());
+  socket.write(buffer);
+  socket.endPacket();
+}

+ 25 - 0
lib/Udp/MiLightDiscoveryServer.h

@@ -0,0 +1,25 @@
+#include <WiFiUdp.h>
+#include <Settings.h>
+
+#ifndef MILIGHT_DISCOVERY_SERVER_H
+#define MILIGHT_DISCOVERY_SERVER_H
+
+class MiLightDiscoveryServer {
+public:
+  MiLightDiscoveryServer(Settings& settings);
+  MiLightDiscoveryServer(MiLightDiscoveryServer&);
+  MiLightDiscoveryServer& operator=(MiLightDiscoveryServer other);
+  ~MiLightDiscoveryServer();
+
+  void begin();
+  void handleClient();
+
+private:
+  Settings& settings;
+  WiFiUDP socket;
+
+  void handleDiscovery(uint8_t version);
+  void sendResponse(char* buffer);
+};
+
+#endif

+ 6 - 6
lib/Udp/MiLightUdpServer.cpp

@@ -4,7 +4,7 @@
 #include <ESP8266WiFi.h>
 
 MiLightUdpServer::MiLightUdpServer(MiLightClient*& client, uint16_t port, uint16_t deviceId)
-  : client(client), 
+  : client(client),
     port(port),
     deviceId(deviceId),
     lastGroup(0)
@@ -24,10 +24,10 @@ void MiLightUdpServer::stop() {
 
 void MiLightUdpServer::handleClient() {
   const size_t packetSize = socket.parsePacket();
-  
+
   if (packetSize) {
     socket.read(packetBuffer, packetSize);
-    
+
 #ifdef MILIGHT_UDP_DEBUG
     printf("[MiLightUdpServer port %d] - Handling packet: ", port);
     for (size_t i = 0; i < packetSize; i++) {
@@ -35,7 +35,7 @@ void MiLightUdpServer::handleClient() {
     }
     printf("\n");
 #endif
-    
+
     handlePacket(packetBuffer, packetSize);
   }
 }
@@ -46,6 +46,6 @@ MiLightUdpServer* MiLightUdpServer::fromVersion(uint8_t version, MiLightClient*&
   } else if (version == 6) {
     return new V6MiLightUdpServer(client, port, deviceId);
   }
-  
+
   return NULL;
-}
+}

+ 14 - 2
lib/WebServer/MiLightHttpServer.cpp

@@ -14,7 +14,7 @@ void MiLightHttpServer::begin() {
   server.on("/", HTTP_GET, handleServeFile(WEB_INDEX_FILENAME, "text/html", DEFAULT_INDEX_PAGE));
   server.on("/settings", HTTP_GET, handleServeFile(SETTINGS_FILE, APPLICATION_JSON));
   server.on("/settings", HTTP_PUT, [this]() { handleUpdateSettings(); });
-  server.on("/settings", HTTP_POST, [this]() { server.send(200, TEXT_PLAIN, "success"); }, handleUpdateFile(SETTINGS_FILE));
+  server.on("/settings", HTTP_POST, [this]() { server.send_P(200, TEXT_PLAIN, PSTR("success. rebooting")); ESP.restart(); }, handleUpdateFile(SETTINGS_FILE));
   server.on("/radio_configs", HTTP_GET, [this]() { handleGetRadioConfigs(); });
   server.onPattern("/gateway_traffic/:type", HTTP_GET, [this](const UrlTokenBindings* b) { handleListenGateway(b); });
   server.onPattern("/gateways/:device_id/:type/:group_id", HTTP_ANY, [this](const UrlTokenBindings* b) { handleUpdateGroup(b); });
@@ -128,7 +128,19 @@ void MiLightHttpServer::handleSystemPost() {
       delay(100);
 
       ESP.restart();
-    }
+
+      handled = true;
+    } else if (request["command"] == "clear_wifi_config") {
+        Serial.println(F("Resetting Wifi and then Restarting..."));
+        server.send(200, TEXT_PLAIN, "true");
+
+        delay(100);
+        ESP.eraseConfig();
+        delay(100);
+        ESP.restart();
+
+        handled = true;
+      }
   }
 
   if (handled) {

+ 1 - 1
platformio.ini

@@ -18,7 +18,7 @@ lib_deps_external =
   ArduinoJson
   PubSubClient
   https://github.com/ratkins/RGBConverter
-build_flags = !python .get_version.py
+build_flags = !python .get_version.py -D MILIGHT_UDP_DEBUG 
 # -D MQTT_DEBUG
 # -D MILIGHT_UDP_DEBUG
 # -D DEBUG_PRINTF

+ 19 - 4
src/main.cpp

@@ -15,15 +15,17 @@
 #include <ESP8266SSDP.h>
 #include <MqttClient.h>
 #include <RGBConverter.h>
+#include <MiLightDiscoveryServer.h>
 
 WiFiManager wifiManager;
 
 Settings settings;
 
-MiLightClient* milightClient;
-MiLightRadioFactory* radioFactory;
-MiLightHttpServer *httpServer;
-MqttClient* mqttClient;
+MiLightClient* milightClient = NULL;
+MiLightRadioFactory* radioFactory = NULL;
+MiLightHttpServer *httpServer = NULL;
+MqttClient* mqttClient = NULL;
+MiLightDiscoveryServer* discoveryServer = NULL;
 
 int numUdpServers = 0;
 MiLightUdpServer** udpServers;
@@ -88,6 +90,15 @@ void applySettings() {
   }
 
   initMilightUdpServers();
+
+  if (discoveryServer) {
+    delete discoveryServer;
+    discoveryServer = NULL;
+  }
+  if (settings.discoveryPort != 0) {
+    discoveryServer = new MiLightDiscoveryServer(settings);
+    discoveryServer->begin();
+  }
 }
 
 bool shouldRestart() {
@@ -138,6 +149,10 @@ void loop() {
     }
   }
 
+  if (discoveryServer) {
+    discoveryServer->handleClient();
+  }
+
   if (shouldRestart()) {
     Serial.println(F("Auto-restart triggered. Restarting..."));
     ESP.restart();