From 744d8d9d1831961db0f22b99c9752afc965256e4 Mon Sep 17 00:00:00 2001 From: Georg Ledermann Date: Sat, 7 Oct 2023 14:38:35 +0200 Subject: [PATCH] Handle additional topics Belongs to #46 --- .env.example | 7 +++ .env.test | 8 ++- .rubocop.yml | 3 + app/config.rb | 48 ++++++++++----- app/mapper.rb | 40 +++++++++++++ test/cassettes/influx_success.yml | 6 +- test/loop_test.rb | 2 +- test/mapper_test.rb | 97 ++++++++++++++++++++++++++----- 8 files changed, 176 insertions(+), 35 deletions(-) diff --git a/.env.example b/.env.example index 8a1e1a3..a3339e4 100644 --- a/.env.example +++ b/.env.example @@ -18,11 +18,18 @@ MQTT_TOPIC_BAT_POWER=senec/0/ENERGY/GUI_BAT_DATA_POWER MQTT_TOPIC_BAT_VOLTAGE=senec/0/ENERGY/GUI_BAT_DATA_VOLTAGE MQTT_TOPIC_CASE_TEMP=senec/0/TEMPMEASURE/CASE_TEMP MQTT_TOPIC_CURRENT_STATE=senec/0/ENERGY/STAT_STATE_Text +MQTT_TOPIC_CURRENT_STATE_CODE=senec/0/ENERGY/STAT_STATE +MQTT_TOPIC_APPLICATION_VERSION=senec/0/WIZARD/APPLICATION_VERSION MQTT_TOPIC_MPP1_POWER=senec/0/PV1/MPP_POWER/0 MQTT_TOPIC_MPP2_POWER=senec/0/PV1/MPP_POWER/1 MQTT_TOPIC_MPP3_POWER=senec/0/PV1/MPP_POWER/2 MQTT_TOPIC_INVERTER_POWER=senec/0/ENERGY/GUI_INVERTER_POWER MQTT_TOPIC_WALLBOX_CHARGE_POWER=senec/0/WALLBOX/APPARENT_CHARGING_POWER/0 +MQTT_TOPIC_WALLBOX_CHARGE_POWER0=senec/0/WALLBOX/APPARENT_CHARGING_POWER/0 +MQTT_TOPIC_WALLBOX_CHARGE_POWER1=senec/0/WALLBOX/APPARENT_CHARGING_POWER/1 +MQTT_TOPIC_WALLBOX_CHARGE_POWER2=senec/0/WALLBOX/APPARENT_CHARGING_POWER/2 +MQTT_TOPIC_WALLBOX_CHARGE_POWER3=senec/0/WALLBOX/APPARENT_CHARGING_POWER/3 +MQTT_TOPIC_POWER_RATIO=senec/0/PV1/POWER_RATIO # Example 2: For evcc # MQTT_TOPIC_HOUSE_POW=evcc/site/homePower diff --git a/.env.test b/.env.test index 66e3034..e396a06 100644 --- a/.env.test +++ b/.env.test @@ -14,11 +14,17 @@ MQTT_TOPIC_BAT_POWER=senec/0/ENERGY/GUI_BAT_DATA_POWER MQTT_TOPIC_BAT_VOLTAGE=senec/0/ENERGY/GUI_BAT_DATA_VOLTAGE MQTT_TOPIC_CASE_TEMP=senec/0/TEMPMEASURE/CASE_TEMP MQTT_TOPIC_CURRENT_STATE=senec/0/ENERGY/STAT_STATE_Text +MQTT_TOPIC_CURRENT_STATE_CODE=senec/0/ENERGY/STAT_STATE +MQTT_TOPIC_APPLICATION_VERSION=senec/0/WIZARD/APPLICATION_VERSION MQTT_TOPIC_MPP1_POWER=senec/0/PV1/MPP_POWER/0 MQTT_TOPIC_MPP2_POWER=senec/0/PV1/MPP_POWER/1 MQTT_TOPIC_MPP3_POWER=senec/0/PV1/MPP_POWER/2 MQTT_TOPIC_INVERTER_POWER=senec/0/ENERGY/GUI_INVERTER_POWER -MQTT_TOPIC_WALLBOX_CHARGE_POWER=senec/0/WALLBOX/APPARENT_CHARGING_POWER/0 +MQTT_TOPIC_WALLBOX_CHARGE_POWER0=senec/0/WALLBOX/APPARENT_CHARGING_POWER/0 +MQTT_TOPIC_WALLBOX_CHARGE_POWER1=senec/0/WALLBOX/APPARENT_CHARGING_POWER/1 +MQTT_TOPIC_WALLBOX_CHARGE_POWER2=senec/0/WALLBOX/APPARENT_CHARGING_POWER/2 +MQTT_TOPIC_WALLBOX_CHARGE_POWER3=senec/0/WALLBOX/APPARENT_CHARGING_POWER/3 +MQTT_TOPIC_POWER_RATIO=senec/0/PV1/POWER_RATIO # InfluxDB credentials INFLUX_HOST=localhost diff --git a/.rubocop.yml b/.rubocop.yml index 5a62feb..4425cb5 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -19,6 +19,9 @@ Style/FrozenStringLiteralComment: Metrics/MethodLength: Max: 20 +Metrics/ClassLength: + Max: 200 + Style/TrailingCommaInArguments: EnforcedStyleForMultiline: consistent_comma diff --git a/app/config.rb b/app/config.rb index 7e66bbd..198e521 100644 --- a/app/config.rb +++ b/app/config.rb @@ -1,5 +1,29 @@ require 'uri' +MQTT_TOPICS = %i[ + inverter_power + mpp1_power + mpp2_power + mpp3_power + house_pow + bat_fuel_charge + wallbox_charge_power + wallbox_charge_power0 + wallbox_charge_power1 + wallbox_charge_power2 + wallbox_charge_power3 + bat_power + grid_pow + power_ratio + current_state + current_state_code + current_state_ok + application_version + case_temp + bat_charge_current + bat_voltage +].freeze + Config = Struct.new( # MQTT credentials @@ -16,9 +40,17 @@ :mqtt_topic_house_pow, :mqtt_topic_bat_fuel_charge, :mqtt_topic_wallbox_charge_power, + :mqtt_topic_wallbox_charge_power0, + :mqtt_topic_wallbox_charge_power1, + :mqtt_topic_wallbox_charge_power2, + :mqtt_topic_wallbox_charge_power3, :mqtt_topic_bat_power, :mqtt_topic_grid_pow, + :mqtt_topic_power_ratio, :mqtt_topic_current_state, + :mqtt_topic_current_state_code, + :mqtt_topic_current_state_ok, + :mqtt_topic_application_version, :mqtt_topic_case_temp, :mqtt_topic_bat_charge_current, :mqtt_topic_bat_voltage, @@ -55,21 +87,7 @@ def self.mqtt_credentials_from_env end def self.mqtt_topics_from_env - %i[ - inverter_power - mpp1_power - mpp2_power - mpp3_power - house_pow - bat_fuel_charge - wallbox_charge_power - bat_power - grid_pow - current_state - case_temp - bat_charge_current - bat_voltage - ].each_with_object({}) do |topic, hash| + MQTT_TOPICS.each_with_object({}) do |topic, hash| value = ENV.fetch("MQTT_TOPIC_#{topic.to_s.upcase}", nil) next unless value diff --git a/app/mapper.rb b/app/mapper.rb index 6ee082b..10196f5 100644 --- a/app/mapper.rb +++ b/app/mapper.rb @@ -61,6 +61,22 @@ def map_wallbox_charge_power(value) { 'wallbox_charge_power' => value.to_f.round } end + def map_wallbox_charge_power0(value) + { 'wallbox_charge_power0' => value.to_f.round } + end + + def map_wallbox_charge_power1(value) + { 'wallbox_charge_power1' => value.to_f.round } + end + + def map_wallbox_charge_power2(value) + { 'wallbox_charge_power2' => value.to_f.round } + end + + def map_wallbox_charge_power3(value) + { 'wallbox_charge_power3' => value.to_f.round } + end + def map_bat_power(value) value = value.to_f.round value = -value if config.mqtt_flip_bat_power @@ -74,6 +90,14 @@ def map_bat_power(value) end end + def map_bat_voltage(value) + { 'bat_voltage' => value.to_f } + end + + def map_bat_charge_current(value) + { 'bat_charge_current' => value.to_f } + end + def map_grid_pow(value) value = value.to_f.round @@ -92,7 +116,23 @@ def map_current_state(value) { 'current_state' => value } end + def map_current_state_code(value) + { 'current_state_code' => value.to_i } + end + + def map_current_state_ok(value) + { 'current_state_ok' => value.in?(%w[true 1 OK]) } + end + def map_case_temp(value) { 'case_temp' => value.to_f.round(1) } end + + def map_power_ratio(value) + { 'power_ratio' => value.to_f.round } + end + + def map_application_version(value) + { 'application_version' => value } + end end diff --git a/test/cassettes/influx_success.yml b/test/cassettes/influx_success.yml index 82512ac..72e723d 100644 --- a/test/cassettes/influx_success.yml +++ b/test/cassettes/influx_success.yml @@ -5,7 +5,7 @@ http_interactions: uri: http://localhost:8086/api/v2/write?bucket=my-bucket&org=my-org&precision=s body: encoding: UTF-8 - string: my-measurement bat_fuel_charge=80.0 1695894918 + string: my-measurement bat_charge_current=80.0 1696681055 headers: Accept-Encoding: - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 @@ -27,9 +27,9 @@ http_interactions: X-Influxdb-Version: - v2.7.1 Date: - - Thu, 28 Sep 2023 09:55:18 GMT + - Sat, 07 Oct 2023 12:17:35 GMT body: encoding: UTF-8 string: '' - recorded_at: Thu, 28 Sep 2023 09:55:18 GMT + recorded_at: Sat, 07 Oct 2023 12:17:35 GMT recorded_with: VCR 6.2.0 diff --git a/test/loop_test.rb b/test/loop_test.rb index 8bc3c1d..b62d7df 100644 --- a/test/loop_test.rb +++ b/test/loop_test.rb @@ -26,6 +26,6 @@ def test_start VCR.use_cassette('influx_success') { Loop.start(config:, max_count: 1) } end - assert_match(/{"bat_fuel_charge"=>80/, out) + assert_match(/{"bat_charge_current"=>80/, out) end end diff --git a/test/mapper_test.rb b/test/mapper_test.rb index 453e54f..182421e 100644 --- a/test/mapper_test.rb +++ b/test/mapper_test.rb @@ -24,11 +24,18 @@ MQTT_TOPIC_BAT_VOLTAGE: 'senec/0/ENERGY/GUI_BAT_DATA_VOLTAGE', MQTT_TOPIC_CASE_TEMP: 'senec/0/TEMPMEASURE/CASE_TEMP', MQTT_TOPIC_CURRENT_STATE: 'senec/0/ENERGY/STAT_STATE_Text', + MQTT_TOPIC_CURRENT_STATE_CODE: 'senec/0/ENERGY/STAT_STATE', + MQTT_TOPIC_APPLICATION_VERSION: 'senec/0/WIZARD/APPLICATION_VERSION', MQTT_TOPIC_MPP1_POWER: 'senec/0/PV1/MPP_POWER/0', MQTT_TOPIC_MPP2_POWER: 'senec/0/PV1/MPP_POWER/1', MQTT_TOPIC_MPP3_POWER: 'senec/0/PV1/MPP_POWER/2', MQTT_TOPIC_INVERTER_POWER: 'senec/0/ENERGY/GUI_INVERTER_POWER', MQTT_TOPIC_WALLBOX_CHARGE_POWER: 'senec/0/WALLBOX/APPARENT_CHARGING_POWER/0', + MQTT_TOPIC_WALLBOX_CHARGE_POWER0: 'senec/0/WALLBOX/APPARENT_CHARGING_POWER/0', + MQTT_TOPIC_WALLBOX_CHARGE_POWER1: 'senec/0/WALLBOX/APPARENT_CHARGING_POWER/1', + MQTT_TOPIC_WALLBOX_CHARGE_POWER2: 'senec/0/WALLBOX/APPARENT_CHARGING_POWER/2', + MQTT_TOPIC_WALLBOX_CHARGE_POWER3: 'senec/0/WALLBOX/APPARENT_CHARGING_POWER/3', + MQTT_TOPIC_POWER_RATIO: 'senec/0/PV1/POWER_RATIO', }.freeze class MapperTest < Minitest::Test @@ -40,21 +47,31 @@ def mapper(config: nil) Mapper.new(config: config || default_config) end + EXPECTED_TOPICS = %w[ + senec/0/ENERGY/GUI_BAT_DATA_CURRENT + senec/0/ENERGY/GUI_BAT_DATA_FUEL_CHARGE + senec/0/ENERGY/GUI_BAT_DATA_POWER + senec/0/ENERGY/GUI_BAT_DATA_VOLTAGE + senec/0/ENERGY/GUI_GRID_POW + senec/0/ENERGY/GUI_HOUSE_POW + senec/0/ENERGY/GUI_INVERTER_POWER + senec/0/ENERGY/STAT_STATE + senec/0/ENERGY/STAT_STATE_Text + senec/0/PV1/MPP_POWER/0 + senec/0/PV1/MPP_POWER/1 + senec/0/PV1/MPP_POWER/2 + senec/0/PV1/POWER_RATIO + senec/0/TEMPMEASURE/CASE_TEMP + senec/0/WALLBOX/APPARENT_CHARGING_POWER/0 + senec/0/WALLBOX/APPARENT_CHARGING_POWER/0 + senec/0/WALLBOX/APPARENT_CHARGING_POWER/1 + senec/0/WALLBOX/APPARENT_CHARGING_POWER/2 + senec/0/WALLBOX/APPARENT_CHARGING_POWER/3 + senec/0/WIZARD/APPLICATION_VERSION + ].freeze + def test_topics - assert_equal %w[ - senec/0/ENERGY/GUI_BAT_DATA_FUEL_CHARGE - senec/0/ENERGY/GUI_BAT_DATA_POWER - senec/0/ENERGY/GUI_GRID_POW - senec/0/ENERGY/GUI_HOUSE_POW - senec/0/ENERGY/GUI_INVERTER_POWER - senec/0/ENERGY/STAT_STATE_Text - senec/0/PV1/MPP_POWER/0 - senec/0/PV1/MPP_POWER/1 - senec/0/PV1/MPP_POWER/2 - senec/0/TEMPMEASURE/CASE_TEMP - senec/0/WALLBOX/APPARENT_CHARGING_POWER/0 - ], - mapper.topics + assert_equal EXPECTED_TOPICS, mapper.topics end def test_call_with_inverter_power @@ -93,10 +110,42 @@ def test_call_with_bat_fuel_charge assert_equal({ 'bat_fuel_charge' => 123.5 }, hash) end - def test_call_with_wallbox_charge_power + def test_call_with_bat_charge_current + hash = mapper.call('senec/0/ENERGY/GUI_BAT_DATA_CURRENT', '1.612') + + assert_equal({ 'bat_charge_current' => 1.612 }, hash) + end + + def test_call_with_bat_voltage + hash = mapper.call('senec/0/ENERGY/GUI_BAT_DATA_VOLTAGE', '54.2') + + assert_equal({ 'bat_voltage' => 54.2 }, hash) + end + + def test_call_with_wallbox_charge_power0 hash = mapper.call('senec/0/WALLBOX/APPARENT_CHARGING_POWER/0', '123.45') assert_equal({ 'wallbox_charge_power' => 123 }, hash) + # TODO: Change to 'wallbox_charge_power0' in the next major version. + # Sum up 0..3 needs to be done in SOLECTRUS dashboard + end + + def test_call_with_wallbox_charge_power1 + hash = mapper.call('senec/0/WALLBOX/APPARENT_CHARGING_POWER/1', '123.45') + + assert_equal({ 'wallbox_charge_power1' => 123 }, hash) + end + + def test_call_with_wallbox_charge_power2 + hash = mapper.call('senec/0/WALLBOX/APPARENT_CHARGING_POWER/2', '123.45') + + assert_equal({ 'wallbox_charge_power2' => 123 }, hash) + end + + def test_call_with_wallbox_charge_power3 + hash = mapper.call('senec/0/WALLBOX/APPARENT_CHARGING_POWER/3', '123.45') + + assert_equal({ 'wallbox_charge_power3' => 123 }, hash) end def test_call_with_bat_power_plus @@ -144,12 +193,30 @@ def test_call_with_current_state assert_equal({ 'current_state' => 'LOADING' }, hash) end + def test_call_with_current_state_code + hash = mapper.call('senec/0/ENERGY/STAT_STATE', '14') + + assert_equal({ 'current_state_code' => 14 }, hash) + end + + def test_call_with_application_version + hash = mapper.call('senec/0/WIZARD/APPLICATION_VERSION', '826') + + assert_equal({ 'application_version' => '826' }, hash) + end + def test_call_with_case_temp hash = mapper.call('senec/0/TEMPMEASURE/CASE_TEMP', '35.2') assert_equal({ 'case_temp' => 35.2 }, hash) end + def test_call_with_power_ratio + hash = mapper.call('senec/0/PV1/POWER_RATIO', '70') + + assert_equal({ 'power_ratio' => 70 }, hash) + end + def test_call_with_unknown_topic assert_raises RuntimeError do mapper.call('this/is/an/unknown/topic', 'foo!')