Skip to content

Commit

Permalink
Merge branch 'release/0.5.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
ledermann committed Jan 13, 2025
2 parents ed9c50f + 6541f3a commit 724b90f
Show file tree
Hide file tree
Showing 11 changed files with 122 additions and 42 deletions.
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ MQTT_PASSWORD=my-mqtt-password
#
# For operators allowed in formulas see here:
# https://github.com/rubysolo/dentaku?tab=readme-ov-file#built-in-operators-and-functions
#
# A formula can used for simple values, too. For example, to convert a value from kW to W:
# MAPPING_7_TOPIC=my/washing-machine/energy
# MAPPING_7_MEASUREMENT=Washer
# MAPPING_7_FIELD=power
# MAPPING_7_TYPE=float
# MAPPING_7_FORMULA="{value} * 1000"
#
# (just use the `{value}` placeholder to reference the value)

MAPPING_0_TOPIC=senec/0/ENERGY/GUI_INVERTER_POWER
MAPPING_0_MEASUREMENT=PV
Expand Down
2 changes: 1 addition & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ require:
- rubocop-performance

AllCops:
TargetRubyVersion: 3.3
TargetRubyVersion: 3.4
Exclude:
- Gemfile
- 'vendor/**/*'
Expand Down
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.3.6
3.4.1
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM ruby:3.3.6-alpine AS builder
FROM ruby:3.4.1-alpine AS builder
RUN apk add --no-cache build-base

WORKDIR /mqtt-collector
Expand All @@ -8,7 +8,7 @@ RUN bundle config --local frozen 1 && \
bundle install -j4 --retry 3 && \
bundle clean --force

FROM ruby:3.3.6-alpine
FROM ruby:3.4.1-alpine
LABEL maintainer="georg@ledermann.dev"

# Add tzdata to get correct timezone
Expand Down
51 changes: 28 additions & 23 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,32 @@ GEM
specs:
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
amazing_print (1.6.0)
amazing_print (1.7.2)
ast (2.4.2)
base64 (0.2.0)
bigdecimal (3.1.8)
bigdecimal (3.1.9)
coderay (1.1.3)
concurrent-ruby (1.3.4)
crack (1.0.0)
bigdecimal
rexml
csv (3.3.0)
csv (3.3.2)
dentaku (3.5.4)
bigdecimal
concurrent-ruby
diff-lcs (1.5.1)
docile (1.4.1)
dotenv (3.1.4)
ffi (1.17.0)
dotenv (3.1.7)
ffi (1.17.1)
formatador (1.1.0)
guard (2.19.0)
guard (2.19.1)
formatador (>= 0.2.4)
listen (>= 2.7, < 4.0)
logger (~> 1.6)
lumberjack (>= 1.0.12, < 2.0)
nenv (~> 0.1)
notiffany (~> 0.0)
ostruct (~> 0.6)
pry (>= 0.13.0)
shellany (~> 0.0)
thor (>= 0.18.1)
Expand All @@ -35,16 +37,17 @@ GEM
guard (~> 2.1)
guard-compat (~> 1.1)
rspec (>= 2.99.0, < 4.0)
hashdiff (1.1.1)
influxdb-client (3.1.0)
json (2.8.1)
hashdiff (1.1.2)
influxdb-client (3.2.0)
csv
json (2.9.1)
jsonpath (1.1.5)
multi_json
language_server-protocol (3.17.0.3)
listen (3.9.0)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
logger (1.6.1)
logger (1.6.5)
lumberjack (1.2.10)
method_source (1.1.0)
mqtt (0.6.0)
Expand All @@ -58,7 +61,7 @@ GEM
parser (3.3.6.0)
ast (~> 2.4.1)
racc
pry (0.14.2)
pry (0.15.2)
coderay (~> 1.1)
method_source (~> 1.0)
public_suffix (6.0.1)
Expand All @@ -68,8 +71,8 @@ GEM
rb-fsevent (0.11.2)
rb-inotify (0.11.1)
ffi (~> 1.0)
regexp_parser (2.9.2)
rexml (3.3.9)
regexp_parser (2.10.0)
rexml (3.4.0)
rspec (3.13.0)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
Expand All @@ -82,25 +85,25 @@ GEM
rspec-mocks (3.13.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-support (3.13.1)
rubocop (1.68.0)
rspec-support (3.13.2)
rubocop (1.70.0)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 2.4, < 3.0)
rubocop-ast (>= 1.32.2, < 2.0)
regexp_parser (>= 2.9.3, < 3.0)
rubocop-ast (>= 1.36.2, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.34.1)
unicode-display_width (>= 2.4.0, < 4.0)
rubocop-ast (1.37.0)
parser (>= 3.3.1.0)
rubocop-performance (1.22.1)
rubocop-performance (1.23.1)
rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
rubocop-rake (0.6.0)
rubocop (~> 1.0)
rubocop-rspec (3.2.0)
rubocop-rspec (3.3.0)
rubocop (~> 1.61)
ruby-progressbar (1.13.0)
shellany (0.0.1)
Expand All @@ -111,7 +114,9 @@ GEM
simplecov-html (0.13.1)
simplecov_json_formatter (0.1.4)
thor (1.3.2)
unicode-display_width (2.6.0)
unicode-display_width (3.1.3)
unicode-emoji (~> 4.0, >= 4.0.4)
unicode-emoji (4.0.4)
vcr (6.3.1)
base64
webmock (3.24.0)
Expand Down Expand Up @@ -145,4 +150,4 @@ DEPENDENCIES
webmock

BUNDLED WITH
2.5.23
2.6.2
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2023-2024 Georg Ledermann <georg@ledermann.dev> and contributors
Copyright (c) 2023-2025 Georg Ledermann <georg@ledermann.dev> and contributors
Inspired by code provided by Sebastian Löb (@loebse) and Michael Heß (@GrimmiMeloni)

Permission is hereby granted, free of charge, to any person obtaining a copy
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,5 @@ bundle exec rubocop

## License

Copyright (c) 2023-2024 Georg Ledermann <georg@ledermann.dev> and contributors.\
Copyright (c) 2023-2025 Georg Ledermann <georg@ledermann.dev> and contributors.\
Inspired by code provided by Sebastian Löb (@loebse) and Michael Heß (@GrimmiMeloni)
2 changes: 1 addition & 1 deletion app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"Version #{ENV.fetch('VERSION', '<unknown>')}, " \
"built at #{ENV.fetch('BUILDTIME', '<unknown>')}"
logger.info 'https://github.com/solectrus/mqtt-collector'
logger.info 'Copyright (c) 2023-2024 Georg Ledermann and contributors, released under the MIT License'
logger.info 'Copyright (c) 2023-2025 Georg Ledermann and contributors, released under the MIT License'
logger.info "\n"

config = Config.new(ENV, logger:)
Expand Down
12 changes: 9 additions & 3 deletions lib/mapper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,13 @@ def value_from(message, mapping)
if mapping[:json_key] || mapping[:json_path]
message = extract_from_json(message, mapping)
elsif mapping[:json_formula]
message = evaluate_from_json(message, mapping)
message = evaluate_from_json(message, mapping[:json_formula])
elsif mapping[:formula]
message = evaluate_from_json({ value: message }.to_json, mapping[:formula])
end
unless message
config.logger.warn ' Value not found, ignoring.'
return
end

convert_type(message, mapping)
Expand Down Expand Up @@ -133,11 +139,11 @@ def extract_from_json(message, mapping)
end
end

def evaluate_from_json(message, mapping)
def evaluate_from_json(message, formula)
json = parse_json(message)
return unless json

Evaluator.new(expression: mapping[:json_formula], data: json).run
Evaluator.new(expression: formula, data: json).run
end

def parse_json(message)
Expand Down
48 changes: 48 additions & 0 deletions spec/lib/evaluator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,54 @@
end
end

context 'with valid expression using IF' do
let(:expression) { "IF({x} > {y} AND {x} < {z}, 'TRUE', 'FALSE')" }

context 'when condition is true' do
let(:data) { { 'x' => 6, 'y' => 2, 'z' => 10 } }

it 'evaluates the expression' do
expect(evaluator.run).to eq('TRUE')
end
end

context 'when condition is false' do
let(:data) { { 'x' => 1, 'y' => 3, 'z' => 10 } }

it 'evaluates the expression' do
expect(evaluator.run).to eq('FALSE')
end
end
end

context 'with valid expression using nested IF' do
let(:expression) { "IF({x} == {y}, 'EQUAL', IF({x} > {y}, 'GREATER', 'LESS'))" }

context 'when case 1' do
let(:data) { { 'x' => 6, 'y' => 2 } }

it 'evaluates the expression' do
expect(evaluator.run).to eq('GREATER')
end
end

context 'when case 2' do
let(:data) { { 'x' => 6, 'y' => 6 } }

it 'evaluates the expression' do
expect(evaluator.run).to eq('EQUAL')
end
end

context 'when case 3' do
let(:data) { { 'x' => 6, 'y' => 7 } }

it 'evaluates the expression' do
expect(evaluator.run).to eq('LESS')
end
end
end

context 'with invalid expression (missing curley braces)' do
let(:expression) { '(a + b) / 2' }
let(:data) { { 'a' => 2, 'b' => 5 } }
Expand Down
30 changes: 21 additions & 9 deletions spec/lib/mapper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@
'MAPPING_22_MEASUREMENT' => 'WALLBOX',
'MAPPING_22_FIELD' => 'power',
'MAPPING_22_TYPE' => 'float',
#
'MAPPING_23_TOPIC' => 'somewhere/power-kwh',
'MAPPING_23_FORMULA' => '{value} * 1000',
'MAPPING_23_MEASUREMENT' => 'Consumer',
'MAPPING_23_FIELD' => 'power',
'MAPPING_23_TYPE' => 'float',
}.freeze

EXPECTED_TOPICS = %w[
Expand All @@ -164,6 +170,7 @@
somewhere/ATTR
somewhere/HEATPUMP/POWER
somewhere/STAT_STATE_OK
somewhere/power-kwh
].freeze

describe Mapper do
Expand Down Expand Up @@ -413,12 +420,23 @@
)
end

it 'maps NULL to 0' do
it 'maps plain value and calculates formula' do
hash = mapper.records_for('somewhere/power-kwh', '123.45')

expect(hash).to eq(
[
{ measurement: 'Consumer', field: 'power', value: 123_450 },
],
)
end

it 'ignores NULL value' do
hash =
mapper.records_for('senec/0/WALLBOX/APPARENT_CHARGING_POWER/0', nil)

expect(logger.warn_messages).to include(/ignoring/)
expect(hash).to eq(
[{ field: 'wallbox_power0', measurement: 'PV', value: 0 }],
[],
)
end

Expand All @@ -427,13 +445,7 @@

expect(logger.warn_messages).to include(/Failed to parse JSON/)
expect(hash).to eq(
[
{ field: 'leaving_temp', measurement: 'HEATPUMP', value: 0.0 },
{ field: 'inlet_temp', measurement: 'HEATPUMP', value: 0.0 },
{ field: 'water_flow', measurement: 'HEATPUMP', value: 0.0 },
{ field: 'temp_diff', measurement: 'HEATPUMP', value: 0.0 },
{ field: 'heat', measurement: 'HEATPUMP', value: 0.0 },
],
[],
)
end

Expand Down

0 comments on commit 724b90f

Please sign in to comment.