Skip to content

Commit

Permalink
Add optional "essential" mode
Browse files Browse the repository at this point in the history
  • Loading branch information
ledermann committed Nov 25, 2024
1 parent b5659bc commit ba1c534
Show file tree
Hide file tree
Showing 7 changed files with 188 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@ INFLUX_ORG=solectrus
# Customize InfluxDB storage
INFLUX_BUCKET=my-solectrus-bucket
INFLUX_MEASUREMENT=shelly

# Optional: Essential mode to reduce data sent to InfluxDB
# When enabled, it minimizes the data sent to InfluxDB
# by skipping consecutive records with zero power values.
# INFLUX_MODE=essential
8 changes: 8 additions & 0 deletions lib/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
influx_org
influx_bucket
influx_measurement
influx_mode
].freeze

DEFAULTS = {
Expand All @@ -22,6 +23,7 @@
influx_schema: :http,
influx_port: 8086,
influx_measurement: 'Consumer',
influx_mode: :default,
}.freeze

Config =
Expand Down Expand Up @@ -117,6 +119,7 @@ def validate_influx_settings!
end

validate_url!(influx_url)
validate_mode!(influx_mode)
end

def validate_url!(url)
Expand All @@ -125,6 +128,10 @@ def validate_url!(url)
(uri.is_a?(URI::HTTP) && uri.host.present?) || throw("URL is invalid: #{url}")
end

def validate_mode!(mode)
%i[default essential].include?(mode) || throw("INFLUX_MODE is invalid: #{mode}")
end

def self.from_env(options = {})
new(
{
Expand All @@ -138,6 +145,7 @@ def self.from_env(options = {})
influx_org: ENV.fetch('INFLUX_ORG'),
influx_bucket: ENV.fetch('INFLUX_BUCKET', nil),
influx_measurement: ENV.fetch('INFLUX_MEASUREMENT', nil),
influx_mode: ENV.fetch('INFLUX_MODE', nil)&.to_sym,
}.merge(options),
)
end
Expand Down
36 changes: 35 additions & 1 deletion lib/shelly_pull.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,48 @@ def initialize(config:, queue:)
@queue = queue
@config = config
@count = 0
@last_record = nil
end

attr_reader :config, :queue, :count

def next
record = config.adapter.solectrus_record(@count += 1)
queue << record

queue << record if should_queue?(record)
queue << @last_record if should_queue_last_record?(record)

# Remember the last record for the next iteration
@last_record = record

record
end

private

def should_queue?(record)
case config.influx_mode
when :default
# Always queue this record
true

when :essential
# Only queue this record if the power is non-zero or if it just changed to zero
result = record.power? || @last_record.nil? || @last_record.power?
config.logger.info "Ignoring record ##{record.id} (zero power)" unless result

result
end
end

def should_queue_last_record?(record)
case config.influx_mode
when :default
false

when :essential
# To ensure that a curve always starts at zero, we queue the last record (if it changes from zero)
record.power? && @last_record && !@last_record.power?
end
end
end
4 changes: 4 additions & 0 deletions lib/solectrus_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,8 @@ def to_hash
@payload[method]
end
end

def power?
!power.round.zero?
end
end
11 changes: 11 additions & 0 deletions spec/lib/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
influx_token: 'this.is.just.an.example',
influx_org: 'solectrus',
influx_bucket: 'Consumer',
influx_mode: :essential,
}
end

Expand Down Expand Up @@ -51,6 +52,12 @@
end.to raise_error(Exception, /INFLUX_TOKEN is missing/)
end

it 'raises an error for invalid INFLUX_MODE' do
expect do
described_class.new(valid_options.merge(influx_mode: 'foo'))
end.to raise_error(Exception, /MODE is invalid/)
end

it 'initializes with valid options' do
expect { described_class.new(valid_options) }.not_to raise_error
end
Expand Down Expand Up @@ -138,5 +145,9 @@
it 'returns correct influx_measurement' do
expect(config.influx_measurement).to eq('Consumer')
end

it 'returns correct influx_mode' do
expect(config.influx_mode).to eq(:essential)
end
end
end
72 changes: 72 additions & 0 deletions spec/lib/shelly_pull_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,77 @@
expect(queue.length).to eq(0)
end
end

context 'when essential mode' do
let(:config) { Config.from_env(influx_mode: :essential) }

it 'queues record if power is non-zero' do
expect do
record1 = SolectrusRecord.new(id: 1, time: 1, payload: { power: 1 })
allow(config.adapter).to receive(:solectrus_record).and_return(record1)
shelly_pull.next
end.to change(queue, :length).by(1) # Because it's non-zero

expect do
record2 = SolectrusRecord.new(id: 2, time: 2, payload: { power: 2 })
allow(config.adapter).to receive(:solectrus_record).and_return(record2)
shelly_pull.next
end.to change(queue, :length).by(1) # Because it's non-zero
end

it 'queues record if power just changed to zero' do
expect do
record1 = SolectrusRecord.new(id: 3, time: 3, payload: { power: 1 })
allow(config.adapter).to receive(:solectrus_record).and_return(record1)
shelly_pull.next
end.to change(queue, :length).by(1) # Because it's non-zero

expect do
record2 = SolectrusRecord.new(id: 4, time: 4, payload: { power: 0 })
allow(config.adapter).to receive(:solectrus_record).and_return(record2)
shelly_pull.next
end.to change(queue, :length).by(1) # Because power just changed to zero
end

it 'does not queue record if power is zero' do
expect do
record1 = SolectrusRecord.new(id: 5, time: 5, payload: { power: 0 })
allow(config.adapter).to receive(:solectrus_record).and_return(record1)
shelly_pull.next
end.to change(queue, :length) # Because it's the first record

expect do
record2 = SolectrusRecord.new(id: 6, time: 6, payload: { power: 0 })
allow(config.adapter).to receive(:solectrus_record).and_return(record2)
shelly_pull.next
end.not_to change(queue, :length) # Because power is still zero
end

it 'does queue last_record before non-zero' do # rubocop:disable RSpec/MultipleExpectations
expect do
record1 = SolectrusRecord.new(id: 7, time: 7, payload: { power: 0 })
allow(config.adapter).to receive(:solectrus_record).and_return(record1)
shelly_pull.next
end.to change(queue, :length).by(1) # Because it's the first record

expect do
record2 = SolectrusRecord.new(id: 8, time: 8, payload: { power: 0 })
allow(config.adapter).to receive(:solectrus_record).and_return(record2)
shelly_pull.next
end.not_to change(queue, :length) # Because power is zero

expect do
record3 = SolectrusRecord.new(id: 9, time: 9, payload: { power: 0 })
allow(config.adapter).to receive(:solectrus_record).and_return(record3)
shelly_pull.next
end.not_to(change(queue, :length)) # Because power is still zero

expect do
record4 = SolectrusRecord.new(id: 10, time: 9, payload: { power: 1 })
allow(config.adapter).to receive(:solectrus_record).and_return(record4)
shelly_pull.next
end.to change(queue, :length).by(2) # Because power is non-zero and last_record power was zero
end
end
end
end
53 changes: 53 additions & 0 deletions spec/lib/solectrus_record_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
require 'solectrus_record'

describe SolectrusRecord do
subject(:record) { described_class.new(id: 1, time: Time.now, payload:) }

let(:payload) do
{
temp: 25.5,
power: 0.0,
power_a: 10.2,
power_b: 20.5,
power_c: 30.3,
response_duration: 20.5,
}
end

describe '#initialize' do
it 'assigns id and time' do
expect(record.id).to eq(1)
expect(record.time).to be_a(Time)
end
end

describe '#to_hash' do
it 'returns the payload hash' do
expect(record.to_hash).to eq(payload)
end
end

describe '#power?' do
context 'when power is zero' do
it 'returns false' do
expect(record.power?).to be(false)
end
end

context 'when power is non-zero' do
let(:payload) { super().merge(power: 42) }

it 'returns true' do
expect(record.power?).to be(true)
end
end
end

%i[temp power power_a power_b power_c response_duration].each do |method|
describe "##{method}" do
it "returns the value of #{method} from the payload" do
expect(record.send(method)).to eq(payload[method])
end
end
end
end

0 comments on commit ba1c534

Please sign in to comment.