From cc087aee3a548feba6db69a998d4b4106d0aab91 Mon Sep 17 00:00:00 2001 From: Chris Mullins Date: Sat, 20 Apr 2019 13:26:55 -0700 Subject: [PATCH] add UDP tests (#438) --- lib/Udp/MiLightDiscoveryServer.cpp | 6 +- test/remote/Gemfile | 1 + test/remote/Gemfile.lock | 2 + test/remote/espmh.env.example | 7 +- test/remote/helpers/mqtt_helpers.rb | 31 ++++++ test/remote/lib/api_client.rb | 4 + test/remote/spec/mqtt_spec.rb | 17 +--- test/remote/spec/spec_helper.rb | 5 + test/remote/spec/state_spec.rb | 7 +- test/remote/spec/udp_spec.rb | 151 ++++++++++++++++++++++++++++ 10 files changed, 211 insertions(+), 20 deletions(-) create mode 100644 test/remote/helpers/mqtt_helpers.rb create mode 100644 test/remote/spec/udp_spec.rb diff --git a/lib/Udp/MiLightDiscoveryServer.cpp b/lib/Udp/MiLightDiscoveryServer.cpp index dbc74026..32837538 100644 --- a/lib/Udp/MiLightDiscoveryServer.cpp +++ b/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(); diff --git a/test/remote/Gemfile b/test/remote/Gemfile index 60477ddc..0c6688f3 100644 --- a/test/remote/Gemfile +++ b/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' diff --git a/test/remote/Gemfile.lock b/test/remote/Gemfile.lock index cf1b9b21..dfbd5dd9 100644 --- a/test/remote/Gemfile.lock +++ b/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 diff --git a/test/remote/espmh.env.example b/test/remote/espmh.env.example index 56f9ba17..6f3aec84 100644 --- a/test/remote/espmh.env.example +++ b/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 \ No newline at end of file +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 \ No newline at end of file diff --git a/test/remote/helpers/mqtt_helpers.rb b/test/remote/helpers/mqtt_helpers.rb new file mode 100644 index 00000000..02a0fd70 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/test/remote/lib/api_client.rb b/test/remote/lib/api_client.rb index ca979342..e8064d4c 100644 --- a/test/remote/lib/api_client.rb +++ b/test/remote/lib/api_client.rb @@ -62,6 +62,10 @@ def upload_json(path, file) `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 diff --git a/test/remote/spec/mqtt_spec.rb b/test/remote/spec/mqtt_spec.rb index eeeb0233..2902d10a 100644 --- a/test/remote/spec/mqtt_spec.rb +++ b/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 @@ } @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 diff --git a/test/remote/spec/spec_helper.rb b/test/remote/spec/spec_helper.rb index a248f7c3..74c6882e 100644 --- a/test/remote/spec/spec_helper.rb +++ b/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 @@ # # 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. diff --git a/test/remote/spec/state_spec.rb b/test/remote/spec/state_spec.rb index 60087056..8741ab9a 100644 --- a/test/remote/spec/state_spec.rb +++ b/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 @@ 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) diff --git a/test/remote/spec/udp_spec.rb b/test/remote/spec/udp_spec.rb new file mode 100644 index 00000000..8407c568 --- /dev/null +++ b/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 = '' + + @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 \ No newline at end of file