Chris Mullins 6 роки тому
батько
коміт
cc087aee3a

+ 5 - 1
lib/Udp/MiLightDiscoveryServer.cpp

@@ -49,7 +49,7 @@ void MiLightDiscoveryServer::handleClient() {
 
 void MiLightDiscoveryServer::handleDiscovery(uint8_t version) {
 #ifdef MILIGHT_UDP_DEBUG
-  printf("Handling discovery for version: %u, %d configs to consider\n", version, settings.numGatewayConfigs);
+  printf_P(PSTR("Handling discovery for version: %u, %d configs to consider\n"), version, settings.numGatewayConfigs);
 #endif
 
   char buffer[40];
@@ -80,6 +80,10 @@ void MiLightDiscoveryServer::handleDiscovery(uint8_t version) {
 }
 
 void MiLightDiscoveryServer::sendResponse(char* buffer) {
+#ifdef MILIGHT_UDP_DEBUG
+  printf_P(PSTR("Sending response: %s\n"), buffer);
+#endif
+
   socket.beginPacket(socket.remoteIP(), socket.remotePort());
   socket.write(buffer);
   socket.endPacket();

+ 1 - 0
test/remote/Gemfile

@@ -7,3 +7,4 @@ gem 'mqtt', '~> 0.5'
 gem 'dotenv', '~> 2.6'
 gem 'multipart-post'
 gem 'net-ping'
+gem 'milight-easybulb', '~> 1.0'

+ 2 - 0
test/remote/Gemfile.lock

@@ -3,6 +3,7 @@ GEM
   specs:
     diff-lcs (1.3)
     dotenv (2.6.0)
+    milight-easybulb (1.0.0)
     mqtt (0.5.0)
     multipart-post (2.0.0)
     net-ping (2.0.5)
@@ -25,6 +26,7 @@ PLATFORMS
 
 DEPENDENCIES
   dotenv (~> 2.6)
+  milight-easybulb (~> 1.0)
   mqtt (~> 0.5)
   multipart-post
   net-ping

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

@@ -12,4 +12,9 @@ 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
+ESPMH_STATIC_IP_GATEWAY=192.168.1.1
+
+# Settings to test UDP server
+ESPMH_V5_UDP_PORT=8888
+ESPMH_V6_UDP_PORT=8889
+ESPMH_DISCOVERY_PORT=8877

+ 31 - 0
test/remote/helpers/mqtt_helpers.rb

@@ -0,0 +1,31 @@
+require 'mqtt_client'
+
+module MqttHelpers
+  def mqtt_topic_prefix
+    ENV.fetch('ESPMH_MQTT_TOPIC_PREFIX')
+  end
+
+  def mqtt_parameters
+    topic_prefix = mqtt_topic_prefix()
+
+    {
+      mqtt_server: ENV.fetch('ESPMH_MQTT_SERVER'),
+      mqtt_username: ENV.fetch('ESPMH_MQTT_USERNAME'),
+      mqtt_password: ENV.fetch('ESPMH_MQTT_PASSWORD'),
+      mqtt_topic_pattern: "#{topic_prefix}commands/:device_id/:device_type/:group_id",
+      mqtt_state_topic_pattern: "#{topic_prefix}state/:device_id/:device_type/:group_id",
+      mqtt_update_topic_pattern: "#{topic_prefix}updates/:device_id/:device_type/:group_id"
+    }
+  end
+
+  def create_mqtt_client
+    params = mqtt_parameters
+
+    MqttClient.new(
+      params[:mqtt_server],
+      params[:mqtt_username],
+      params[:mqtt_password],
+      mqtt_topic_prefix()
+    )
+  end
+end

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

@@ -62,6 +62,10 @@ class ApiClient
     `curl -s "http://#{@host}#{path}" -X POST -F 'f=@#{file}'`
   end
 
+  def patch_settings(settings)
+    put('/settings', settings)
+  end
+
   def get(path)
     request(:Get, path)
   end

+ 5 - 12
test/remote/spec/mqtt_spec.rb

@@ -1,22 +1,17 @@
 require 'api_client'
-require 'mqtt_client'
 
 RSpec.describe 'State' do
   before(:all) do
     @client = ApiClient.new(ENV.fetch('ESPMH_HOSTNAME'), ENV.fetch('ESPMH_TEST_DEVICE_ID_BASE'))
     @client.upload_json('/settings', 'settings.json')
 
-    @topic_prefix = ENV.fetch('ESPMH_MQTT_TOPIC_PREFIX')
-    @updates_topic = "#{@topic_prefix}updates/:device_id/:device_type/:group_id"
+    mqtt_params = mqtt_parameters()
+    @updates_topic = mqtt_params[:updates_topic]
+    @topic_prefix = mqtt_topic_prefix()
 
     @client.put(
       '/settings',
-      mqtt_server: ENV.fetch('ESPMH_MQTT_SERVER'),
-      mqtt_username: ENV.fetch('ESPMH_MQTT_USERNAME'),
-      mqtt_password: ENV.fetch('ESPMH_MQTT_PASSWORD'),
-      mqtt_topic_pattern: "#{@topic_prefix}commands/:device_id/:device_type/:group_id",
-      mqtt_state_topic_pattern: "#{@topic_prefix}state/:device_id/:device_type/:group_id",
-      mqtt_update_topic_pattern: @updates_topic
+      mqtt_params
     )
   end
 
@@ -28,9 +23,7 @@ RSpec.describe 'State' do
     }
     @client.delete_state(@id_params)
 
-    @mqtt_client = MqttClient.new(
-      *%w(SERVER USERNAME PASSWORD).map { |x| ENV.fetch("ESPMH_MQTT_#{x}") } << @topic_prefix
-    )
+    @mqtt_client = create_mqtt_client()
   end
 
   context 'deleting' do

+ 5 - 0
test/remote/spec/spec_helper.rb

@@ -1,4 +1,6 @@
 require 'dotenv'
+require './helpers/state_helpers'
+require './helpers/mqtt_helpers'
 
 Dotenv.load('espmh.env')
 
@@ -18,6 +20,9 @@ Dotenv.load('espmh.env')
 #
 # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
 RSpec.configure do |config|
+  config.include StateHelpers
+  config.include MqttHelpers
+
   # rspec-expectations config goes here. You can use an alternate
   # assertion/expectation library such as wrong or the stdlib/minitest
   # assertions if you prefer.

+ 1 - 6
test/remote/spec/state_spec.rb

@@ -1,9 +1,4 @@
 require 'api_client'
-require './helpers/state_helpers'
-
-RSpec.configure do |c|
-  c.include StateHelpers
-end
 
 RSpec.describe 'State' do
   before(:all) do
@@ -182,7 +177,7 @@ RSpec.describe 'State' do
       resulting_state = @client.get_state(group_0_params)
       expect(resulting_state).to_not include('level')
 
-      # white mode -> color.  
+      # white mode -> color.
       white_mode_desired_state = {'status' => 'ON', 'color_temp' => 253, 'level' => 11}
       @client.patch_state(white_mode_desired_state, group_0_params)
       @client.patch_state({'hue' => 10}, @id_params)

+ 151 - 0
test/remote/spec/udp_spec.rb

@@ -0,0 +1,151 @@
+require 'api_client'
+require 'milight'
+
+RSpec.describe 'UDP servers' do
+  before(:all) do
+    @host = ENV.fetch('ESPMH_HOSTNAME')
+    @client = ApiClient.new(@host, ENV.fetch('ESPMH_TEST_DEVICE_ID_BASE'))
+    @client.upload_json('/settings', 'settings.json')
+
+    @client.patch_settings( mqtt_parameters() )
+    @client.patch_settings( mqtt_update_topic_pattern: '' )
+  end
+
+  before(:each) do
+    @id_params = {
+      id: @client.generate_id,
+      type: 'rgbw',
+      group_id: 1
+    }
+    @v6_id_params = {
+      id: @client.generate_id,
+      type: 'rgbw',
+      group_id: 1
+    }
+    @client.delete_state(@id_params)
+
+    @v5_udp_port = ENV.fetch('ESPMH_V5_UDP_PORT')
+    @v6_udp_port = ENV.fetch('ESPMH_V6_UDP_PORT')
+    @discovery_port = ENV.fetch('ESPMH_DISCOVERY_PORT')
+
+    @client.patch_settings(
+      gateway_configs: [
+        [
+          @id_params[:id], # device ID
+          @v5_udp_port,
+          5                # protocol version (gem uses v5)
+        ],
+        [
+          @v6_id_params[:id], # device ID
+          @v6_udp_port,
+          6                # protocol version
+        ]
+      ]
+    )
+    @udp_client = Milight::Controller.new(ENV.fetch('ESPMH_HOSTNAME'), @v5_udp_port)
+    @mqtt_client = create_mqtt_client()
+  end
+
+  context 'on/off commands' do
+    it 'should result in state changes' do
+      @udp_client.group(@id_params[:group_id]).on
+
+      # Wait for packet to be processed
+      sleep 1
+
+      state = @client.get_state(@id_params)
+      expect(state['status']).to eq('ON')
+
+      @udp_client.group(@id_params[:group_id]).off
+
+      # Wait for packet to be processed
+      sleep 1
+
+      state = @client.get_state(@id_params)
+      expect(state['status']).to eq('OFF')
+    end
+
+    it 'should result in an MQTT update' do
+      desired_state = {
+        'status' => 'ON',
+        'level' => 48
+      }
+      seen_state = false
+
+      @mqtt_client.on_state(@id_params) do |id, message|
+        seen_state = (id == @id_params && desired_state.all? { |k,v| v == message[k] })
+      end
+      @udp_client.group(@id_params[:group_id]).on.brightness(48)
+      @mqtt_client.wait_for_listeners
+
+      expect(seen_state).to eq(true)
+    end
+  end
+
+  context 'color and brightness commands' do
+    it 'should result in state changes' do
+      desired_state = {
+        'status' => 'ON',
+        'level' => 48,
+        'hue' => 357
+      }
+      seen_state = false
+
+      @mqtt_client.on_state(@id_params) do |id, message|
+        seen_state = (id == @id_params && desired_state.all? { |k,v| v == message[k] })
+      end
+
+      @udp_client.group(@id_params[:group_id])
+        .on
+        .colour('#ff0000')
+        .brightness(48)
+
+      @mqtt_client.wait_for_listeners
+
+      expect(seen_state).to eq(true)
+    end
+  end
+
+  context 'discovery' do
+    before(:all) do
+      @client.patch_settings(
+        discovery_port: ENV.fetch('ESPMH_DISCOVERY_PORT')
+      )
+
+      @discovery_host = '<broadcast>'
+
+      @discovery_socket = UDPSocket.new
+      @discovery_socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
+      @discovery_socket.bind('0.0.0.0', 0)
+    end
+
+    it 'should respond to v5 discovery' do
+      @discovery_socket.send('Link_Wi-Fi', 0, @discovery_host, @discovery_port)
+
+      # wait for response
+      sleep 1
+
+      response, _ = @discovery_socket.recvfrom_nonblock(1024)
+      response = response.split(',')
+
+      expect(response.length).to      eq(2), "Should be a comma-separated list with two elements"
+      expect(response[0]).to          eq(@host)
+      expect(response[1].to_i(16)).to eq(@id_params[:id])
+    end
+
+    it 'should respond to v6 discovery' do
+      @discovery_socket.send('HF-A11ASSISTHREAD', 0, @host, @discovery_port)
+
+      # wait for response
+      sleep 1
+
+      response, _ = @discovery_socket.recvfrom_nonblock(1024)
+      response = response.split(',')
+
+      expect(response.length).to      eq(3), "Should be a comma-separated list with three elements"
+      expect(response[0]).to          eq(@host)
+      expect(response[1].to_i(16)).to eq(@v6_id_params[:id])
+      expect(response[2]).to          eq('HF-LPB100')
+    end
+  end
+end