Explorar o código

Add setting for static IP address (#425)

Few patches to @iMartyn's static IP changes:

* Minor formatting
* Add to integration test
* Add settings UI
* Change default to empty string (from 0.0.0.0)
Chris Mullins %!s(int64=6) %!d(string=hai) anos
pai
achega
9798e2c2ad

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 2 - 2
dist/index.html.gz.h


+ 6 - 0
lib/Settings/Settings.cpp

@@ -111,6 +111,9 @@ void Settings::patch(JsonObject& parsedSettings) {
     this->setIfPresent(parsedSettings, "enable_automatic_mode_switching", enableAutomaticModeSwitching);
     this->setIfPresent(parsedSettings, "led_mode_packet_count", ledModePacketCount);
     this->setIfPresent(parsedSettings, "hostname", hostname);
+    this->setIfPresent(parsedSettings, "wifi_static_ip", wifiStaticIP);
+    this->setIfPresent(parsedSettings, "wifi_static_ip_gateway", wifiStaticIPGateway);
+    this->setIfPresent(parsedSettings, "wifi_static_ip_netmask", wifiStaticIPNetmask);
 
     if (parsedSettings.containsKey("rf24_channels")) {
       JsonArray& arr = parsedSettings["rf24_channels"];
@@ -232,6 +235,9 @@ void Settings::serialize(Stream& stream, const bool prettyPrint) {
   root["hostname"] = this->hostname;
   root["rf24_power_level"] = RF24PowerLevelHelpers::nameFromValue(this->rf24PowerLevel);
   root["rf24_listen_channel"] = RF24ChannelHelpers::nameFromValue(rf24ListenChannel);
+  root["wifi_static_ip"] = this->wifiStaticIP;
+  root["wifi_static_ip_gateway"] = this->wifiStaticIPGateway;
+  root["wifi_static_ip_netmask"] = this->wifiStaticIPNetmask;
 
   JsonArray& channelArr = jsonBuffer.createArray();
   JsonHelpers::vectorToJsonArr<RF24Channel>(channelArr, rf24Channels, RF24ChannelHelpers::nameFromValue);

+ 4 - 0
lib/Settings/Settings.h

@@ -45,6 +45,7 @@
 
 #define MINIMUM_RESTART_PERIOD 1
 #define DEFAULT_MQTT_PORT 1883
+#define MAX_IP_ADDR_LEN 15
 
 enum RadioInterfaceType {
   nRF24 = 0,
@@ -186,6 +187,9 @@ public:
   RF24PowerLevel rf24PowerLevel;
   std::vector<RF24Channel> rf24Channels;
   RF24Channel rf24ListenChannel;
+  String wifiStaticIP;
+  String wifiStaticIPNetmask;
+  String wifiStaticIPGateway;
 
 
 protected:

+ 52 - 0
src/main.cpp

@@ -24,6 +24,10 @@
 #include <LEDStatus.h>
 
 WiFiManager wifiManager;
+// because of callbacks, these need to be in the higher scope :(
+WiFiManagerParameter* wifiStaticIP = NULL;
+WiFiManagerParameter* wifiStaticIPNetmask = NULL;
+WiFiManagerParameter* wifiStaticIPGateway = NULL;
 
 static LEDStatus *ledStatus;
 
@@ -266,6 +270,13 @@ void handleLED() {
   ledStatus->handle();
 }
 
+void wifiExtraSettingsChange() {
+  settings.wifiStaticIP = wifiStaticIP->getValue();
+  settings.wifiStaticIPNetmask = wifiStaticIPNetmask->getValue();
+  settings.wifiStaticIPGateway = wifiStaticIPGateway->getValue();
+  settings.save();
+}
+
 void setup() {
   Serial.begin(9600);
   String ssid = "ESP" + String(ESP.getChipId());
@@ -289,7 +300,48 @@ void setup() {
   // that change is only on the development branch so we are going to continue to use this fork until
   // that is merged and ready.
   wifiManager.setSetupLoopCallback(handleLED);
+  
+  // Allows us to have static IP config in the captive portal. Yucky pointers to pointers, just to have the settings carry through
+  wifiManager.setSaveConfigCallback(wifiExtraSettingsChange);
+
+  wifiStaticIP = new WiFiManagerParameter(
+    "staticIP",
+    "Static IP (Leave blank for dhcp)",
+    settings.wifiStaticIP.c_str(),
+    MAX_IP_ADDR_LEN
+  );
+  wifiManager.addParameter(wifiStaticIP);
+
+  wifiStaticIPNetmask = new WiFiManagerParameter(
+    "netmask",
+    "Netmask (required if IP given)",
+    settings.wifiStaticIPNetmask.c_str(),
+    MAX_IP_ADDR_LEN
+  );
+  wifiManager.addParameter(wifiStaticIPNetmask);
+
+  wifiStaticIPGateway = new WiFiManagerParameter(
+    "gateway",
+    "Default Gateway (optional, only used if static IP)",
+    settings.wifiStaticIPGateway.c_str(),
+    MAX_IP_ADDR_LEN
+  );
+  wifiManager.addParameter(wifiStaticIPGateway);
+
+  // We have a saved static IP, let's try and use it.
+  if (settings.wifiStaticIP.length() > 0) {
+    Serial.printf_P(PSTR("We have a static IP: %s\n"), settings.wifiStaticIP.c_str());
+
+    IPAddress _ip, _subnet, _gw;
+    _ip.fromString(settings.wifiStaticIP);
+    _subnet.fromString(settings.wifiStaticIPNetmask);
+    _gw.fromString(settings.wifiStaticIPGateway);
+
+    wifiManager.setSTAStaticIPConfig(_ip,_gw,_subnet);
+  }
+
   wifiManager.setConfigPortalTimeout(180);
+
   if (wifiManager.autoConnect(ssid.c_str(), "milightHub")) {
     // set LED mode for successful operation
     ledStatus->continuous(settings.ledModeOperating);

+ 2 - 1
test/remote/Gemfile

@@ -4,4 +4,5 @@ source "https://rubygems.org"
 
 gem 'mqtt', '~> 0.5'
 gem 'dotenv', '~> 2.6'
-gem 'multipart-post'
+gem 'multipart-post'
+gem 'net-ping'

+ 2 - 0
test/remote/Gemfile.lock

@@ -4,6 +4,7 @@ GEM
     dotenv (2.6.0)
     mqtt (0.5.0)
     multipart-post (2.0.0)
+    net-ping (2.0.5)
 
 PLATFORMS
   ruby
@@ -12,6 +13,7 @@ DEPENDENCIES
   dotenv (~> 2.6)
   mqtt (~> 0.5)
   multipart-post
+  net-ping
 
 BUNDLED WITH
    1.17.2

+ 6 - 1
test/remote/espmh.env.example

@@ -7,4 +7,9 @@ ESPMH_TEST_DEVICE_ID_BASE=0x2200
 ESPMH_MQTT_SERVER=my-mqtt-server
 ESPMH_MQTT_USERNAME=username
 ESPMH_MQTT_PASSWORD=password
-ESPMH_MQTT_TOPIC_PREFIX=milight_test/
+ESPMH_MQTT_TOPIC_PREFIX=milight_test/
+
+# Settings to test static IP
+ESPMH_STATIC_IP=192.168.1.200
+ESPMH_STATIC_IP_NETMASK=255.255.255.0
+ESPMH_STATIC_IP_GATEWAY=192.168.1.1

+ 4 - 0
test/remote/lib/api_client.rb

@@ -25,6 +25,10 @@ class ApiClient
     @password = nil
   end
 
+  def reboot
+    post('/system', '{"command":"restart"}')
+  end
+
   def request(type, path, req_body = nil)
     uri = URI("http://#{@host}#{path}")
     Net::HTTP.start(uri.host, uri.port) do |http|

+ 40 - 0
test/remote/spec/settings_spec.rb

@@ -1,5 +1,6 @@
 require 'api_client'
 require 'tempfile'
+require 'net/ping'
 
 RSpec.describe 'Settings' do
   before(:all) do
@@ -92,4 +93,43 @@ RSpec.describe 'Settings' do
       expect(result['rf24_listen_channel']).to eq('LOW')
     end
   end
+
+  context 'static ip' do
+    it 'should boot with static IP when applied' do
+      static_ip = ENV.fetch('ESPMH_STATIC_IP')
+
+      @client.put(
+        '/settings',
+        wifi_static_ip: static_ip,
+        wifi_static_ip_netmask: ENV.fetch('ESPMH_STATIC_IP_NETMASK'),
+        wifi_static_ip_gateway: ENV.fetch('ESPMH_STATIC_IP_GATEWAY')
+      )
+
+      # Reboot to apply static ip
+      @client.reboot
+
+      # Wait for it to come back up
+      ping_test = Net::Ping::External.new(static_ip)
+
+      10.times do 
+        break if ping_test.ping?
+        sleep 1
+      end
+
+      expect(ping_test.ping?).to be(true)
+
+      static_client = ApiClient.new(static_ip, ENV.fetch('ESPMH_TEST_DEVICE_ID_BASE'))
+      static_client.put('/settings', wifi_static_ip: '')
+      static_client.reboot
+
+      ping_test = Net::Ping::External.new(ENV.fetch('ESPMH_HOSTNAME'))
+
+      10.times do 
+        break if ping_test.ping?
+        sleep 1
+      end
+
+      expect(ping_test.ping?).to be(true)
+    end
+  end
 end

+ 18 - 0
web/src/js/script.js

@@ -61,6 +61,24 @@ var UI_FIELDS = [ {
     type: "string",
     tab: "tab-wifi"
   }, {
+    tag: "wifi_static_ip",
+    friendly: "Static IP Address",
+    help: "Static IP address (leave blank to use DHCP)",
+    type: "string",
+    tab: "tab-wifi"
+  }, {
+    tag: "wifi_static_ip_netmask",
+    friendly: "Static IP Netmask",
+    help: "Netmask to use with Static IP",
+    type: "string",
+    tab: "tab-wifi"
+  }, {
+    tag: "wifi_static_ip_gateway",
+    friendly: "Static IP Gateway Address",
+    help: "IP address to use as gateway when a Static IP is speicifed",
+    type: "string",
+    tab: "tab-wifi"
+  }, {
     tag: "ce_pin",
     friendly: "CE / PKT pin",
     help: "Pin on ESP8266 used for 'CE' (for NRF24L01 interface) or 'PKT' (for 'PL1167/LT8900' interface)",