Skip to content

Commit

Permalink
Wallbox or heat pump may be missing, but not both
Browse files Browse the repository at this point in the history
  • Loading branch information
ledermann committed Jul 25, 2024
1 parent d5c1860 commit 5c7b765
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 14 deletions.
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ This enables SOLECTRUS to accurately calculate the electricity usage and costs f
- InfluxDB 2 database with a bucket filled with values for:
- Grid import power
- House power
- Heat pump power (optional)
- Wallbox power (optional)
- Heatpump power and/or Wallbox power
- Linux machine with Docker installed

## Getting started
Expand All @@ -32,7 +31,6 @@ This enables SOLECTRUS to accurately calculate the electricity usage and costs f

The Docker image supports multiple platforms: `linux/amd64`, `linux/arm64`


To force a data rebuild, you can delete the measurement from the InfluxDB database:

```bash
Expand Down
39 changes: 37 additions & 2 deletions lib/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
require 'active_support/core_ext'
require 'null_logger'

class Config
class Config # rubocop:disable Metrics/ClassLength
attr_accessor :influx_schema,
:influx_host,
:influx_port,
Expand Down Expand Up @@ -32,6 +32,7 @@ def initialize(env, logger: NullLogger.new)
@time_zone = env.fetch('TZ', 'Europe/Berlin')

init_sensors(env)
validate_sensors!
end

def influx_url
Expand All @@ -50,6 +51,20 @@ def field(sensor_name)
@field[sensor_name] ||= splitted_sensor_name(sensor_name)&.last
end

def exists?(sensor_name)
case sensor_name
when *SENSOR_NAMES
measurement(sensor_name).present? && field(sensor_name).present?
else
raise ArgumentError,
"Unknown or invalid sensor name: #{sensor_name.inspect}"
end
end

def sensor_names
@sensor_names ||= SENSOR_NAMES.filter { |sensor_name| exists?(sensor_name) }
end

private

def validate_url!(url)
Expand All @@ -60,7 +75,8 @@ def init_sensors(env)
logger.info 'Sensor initialization started'
SENSOR_NAMES.each do |sensor_name|
var_sensor = var_for(sensor_name)
value = env.fetch(var_sensor)
value = env.fetch(var_sensor, nil)
next unless value

validate!(sensor_name, value)
define_sensor(sensor_name, value)
Expand All @@ -72,6 +88,23 @@ def init_sensors(env)
logger.info 'Sensor initialization completed'
end

def validate_sensors!
unless exists?(:wallbox_power) || exists?(:heatpump_power)
raise Error,
'At least one of INFLUX_SENSOR_WALLBOX_POWER or INFLUX_SENSOR_HEATPUMP_POWER must be set.'
end

unless exists?(:grid_import_power)
raise Error, 'INFLUX_SENSOR_GRID_IMPORT_POWER must be set.'
end

unless exists?(:house_power)
raise Error, 'INFLUX_SENSOR_HOUSE_POWER must be set.'
end

true
end

class Error < RuntimeError
end

Expand Down Expand Up @@ -130,6 +163,8 @@ def validate!(sensor_name, value)
end

def splitted_sensor_name(sensor_name)
return unless respond_to?(sensor_name.downcase)

public_send(sensor_name.downcase)&.split(':')
end
end
2 changes: 1 addition & 1 deletion lib/flux/extractor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def records(day)
query_string = <<~FLUX
#{from_bucket}
|> #{day_range(day)}
|> #{filter(selected_sensors: Config::SENSOR_NAMES)}
|> #{filter(selected_sensors: config.sensor_names)}
|> aggregateWindow(every: 1m, fn: mean)
|> fill(usePrevious: true)
FLUX
Expand Down
2 changes: 1 addition & 1 deletion lib/flux/first_sensor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def time
query_string = <<~FLUX
#{from_bucket}
|> #{range(start: Time.at(0))}
|> #{filter(selected_sensors: Config::SENSOR_NAMES)}
|> #{filter(selected_sensors: config.sensor_names)}
|> first()
|> keep(columns: ["_time"])
|> min(column: "_time")
Expand Down
27 changes: 21 additions & 6 deletions lib/processor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ class Processor
def initialize(day_records:, config:)
@day_records = day_records
@config = config

@wallbox_present = config.exists?(:wallbox_power)
@heatpump_present = config.exists?(:heatpump_power)
end

attr_reader :day_records, :config
attr_reader :day_records, :config, :wallbox_present, :heatpump_present

def call
group_by_hour(
Expand All @@ -25,8 +28,14 @@ def point(record)
)

result.add_field('house_power_grid', record[:house_power_grid])
result.add_field('wallbox_power_grid', record[:wallbox_power_grid])
result.add_field('heatpump_power_grid', record[:heatpump_power_grid])

if wallbox_present
result.add_field('wallbox_power_grid', record[:wallbox_power_grid])
end

if heatpump_present
result.add_field('heatpump_power_grid', record[:heatpump_power_grid])
end

result
end
Expand All @@ -38,9 +47,11 @@ def group_by_hour(splitted)
{
time: items.first[:time].beginning_of_hour,
house_power_grid: sum(items, :house_power_grid),
wallbox_power_grid: sum(items, :wallbox_power_grid),
heatpump_power_grid: sum(items, :heatpump_power_grid),
}
wallbox_power_grid:
wallbox_present ? sum(items, :wallbox_power_grid) : nil,
heatpump_power_grid:
heatpump_present ? sum(items, :heatpump_power_grid) : nil,
}.compact
end
end

Expand All @@ -59,10 +70,14 @@ def house_power(record)
end

def wallbox_power(record)
return 0 unless config.exists?(:wallbox_power)

power_value(record, :wallbox_power)
end

def heatpump_power(record)
return 0 unless config.exists?(:heatpump_power)

power_value(record, :heatpump_power)
end

Expand Down
56 changes: 55 additions & 1 deletion spec/lib/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,22 @@
end
end

describe 'valid options (no wallbox)' do
let(:env) { valid_env.except('INFLUX_SENSOR_WALLBOX_POWER') }

it 'initializes successfully' do
expect(config).to be_a(described_class)
end
end

describe 'valid options (no heatpump)' do
let(:env) { valid_env.except('INFLUX_SENSOR_HEATPUMP_POWER') }

it 'initializes successfully' do
expect(config).to be_a(described_class)
end
end

describe 'Influx methods' do
let(:env) { valid_env }

Expand All @@ -50,7 +66,45 @@
let(:env) { {} }

it 'raises an exception' do
expect { described_class.new(env) }.to raise_error(Exception)
expect { described_class.new(env) }.to raise_error(KeyError)
end
end

context 'when no house_power' do
let(:env) { valid_env.except('INFLUX_SENSOR_HOUSE_POWER') }

it 'raises an exception' do
expect { described_class.new(env) }.to raise_error(
Config::Error,
/must be set/,
)
end
end

context 'when no grid_import_power' do
let(:env) { valid_env.except('INFLUX_SENSOR_GRID_IMPORT_POWER') }

it 'raises an exception' do
expect { described_class.new(env) }.to raise_error(
Config::Error,
/must be set/,
)
end
end

context 'when no heatpump AND no wallbox' do
let(:env) do
valid_env.except(
'INFLUX_SENSOR_HEATPUMP_POWER',
'INFLUX_SENSOR_WALLBOX_POWER',
)
end

it 'raises an exception' do
expect { described_class.new(env) }.to raise_error(
Config::Error,
/At least one of/,
)
end
end
end
Expand Down

0 comments on commit 5c7b765

Please sign in to comment.