Compare commits

...

8 Commits

Author SHA1 Message Date
7ff22cf6ec No password in git 2026-04-26 14:21:48 +02:00
b4dd203718 No password in git 2026-04-26 14:19:54 +02:00
2eaaf8d6d8 Cronjob added inside container 2026-04-26 13:25:30 +02:00
=
a5aa6de67b Post to MQTT
Co-authored-by: Copilot <copilot@github.com>
2026-04-26 13:04:29 +02:00
=
d5a4a8c791 Merge branch 'master' 2026-03-23 17:51:23 +01:00
=
5b7b913588 ignore data folder 2026-03-23 16:25:59 +01:00
=
ca5a4a1b5d Fixes active record 7 compatibility 2026-03-23 16:23:15 +01:00
9f7f8a4d80 Initial commit 2026-03-23 16:13:52 +01:00
13 changed files with 175 additions and 61 deletions

2
.gitignore vendored
View File

@@ -2,4 +2,4 @@
.config .config
.project .project
*.pid *.pid
data config/smtp.yml

View File

@@ -1,4 +1,4 @@
FROM ruby:2.7 FROM ruby:3.1
ENV BUILD_PACKAGES="apt-utils build-essential curl less nodejs sudo wget zsh libmariadb-dev libserialport-dev cron" ENV BUILD_PACKAGES="apt-utils build-essential curl less nodejs sudo wget zsh libmariadb-dev libserialport-dev cron"
@@ -14,8 +14,18 @@ COPY Gemfile Gemfile.lock ./
RUN \ RUN \
apt-get update -qq && \ apt-get update -qq && \
apt-get install -y $BUILD_PACKAGES && \ apt-get install -y $BUILD_PACKAGES && \
bundle install # Fix bundler version BEFORE bundle install
gem install bundler -v "$(grep -A 1 'BUNDLED WITH' Gemfile.lock | tail -n 1)" && \
bundle install && \
rm -rf /var/lib/apt/lists/*
COPY . . # Copy cron file (if you use /etc/cron.d style)
COPY reportcron /etc/cron.d/reportcron
RUN chmod 0644 /etc/cron.d/reportcron && touch /var/log/cron.log
# Add entrypoint script
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
CMD ["/bin/bash -c ruby ./smartmeter.rb"]

View File

@@ -12,3 +12,4 @@ gem 'numo-narray'
gem 'i18n' gem 'i18n'
gem 'gruff' gem 'gruff'
gem 'net-http' # to avoid error: uninitialized constant ... gem 'net-http' # to avoid error: uninitialized constant ...
gem 'mqtt'

View File

@@ -1,13 +1,13 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
activemodel (7.1.5.1) activemodel (7.1.6)
activesupport (= 7.1.5.1) activesupport (= 7.1.6)
activerecord (7.1.5.1) activerecord (7.1.6)
activemodel (= 7.1.5.1) activemodel (= 7.1.6)
activesupport (= 7.1.5.1) activesupport (= 7.1.6)
timeout (>= 0.4.0) timeout (>= 0.4.0)
activesupport (7.1.5.1) activesupport (7.1.6)
base64 base64
benchmark (>= 0.3) benchmark (>= 0.3)
bigdecimal bigdecimal
@@ -20,53 +20,55 @@ GEM
mutex_m mutex_m
securerandom (>= 0.3) securerandom (>= 0.3)
tzinfo (~> 2.0) tzinfo (~> 2.0)
base64 (0.2.0) base64 (0.3.0)
benchmark (0.4.0) benchmark (0.5.0)
bigdecimal (3.1.9) bigdecimal (4.1.2)
concurrent-ruby (1.3.4) concurrent-ruby (1.3.6)
connection_pool (2.4.1) connection_pool (2.5.5)
daemons (1.4.1) daemons (1.4.1)
date (3.4.1) date (3.5.1)
drb (2.2.1) drb (2.2.3)
et-orbi (1.2.11) et-orbi (1.4.0)
tzinfo tzinfo
fugit (1.11.1) fugit (1.12.1)
et-orbi (~> 1, >= 1.2.11) et-orbi (~> 1.4)
raabro (~> 1.4) raabro (~> 1.4)
gruff (0.24.0) gruff (0.24.0)
histogram histogram
rmagick (>= 5.3) rmagick (>= 5.3)
histogram (0.2.4.1) histogram (0.2.4.1)
i18n (1.14.6) i18n (1.14.8)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
logger (1.6.4) logger (1.7.0)
mail (2.8.1) mail (2.9.0)
logger
mini_mime (>= 0.1.1) mini_mime (>= 0.1.1)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
mini_mime (1.1.5) mini_mime (1.1.5)
mini_portile2 (2.8.8) minitest (5.26.1)
minitest (5.25.4) mqtt (0.7.0)
logger
mutex_m (0.3.0) mutex_m (0.3.0)
mysql2 (0.5.6) mysql2 (0.5.7)
net-http (0.6.0) bigdecimal
uri net-http (0.9.1)
net-imap (0.4.18) uri (>= 0.11.1)
net-imap (0.4.24)
date date
net-protocol net-protocol
net-pop (0.1.2) net-pop (0.1.2)
net-protocol net-protocol
net-protocol (0.2.2) net-protocol (0.2.2)
timeout timeout
net-smtp (0.5.0) net-smtp (0.5.1)
net-protocol net-protocol
nokogiri (1.15.7) nokogiri (1.15.7-aarch64-linux)
mini_portile2 (~> 2.8.2)
racc (~> 1.4) racc (~> 1.4)
numo-narray (0.9.2.1) numo-narray (0.9.2.1)
observer (0.1.2) observer (0.1.2)
pkg-config (1.5.8) pkg-config (1.6.5)
raabro (1.4.0) raabro (1.4.0)
racc (1.8.1) racc (1.8.1)
rmagick (5.5.0) rmagick (5.5.0)
@@ -75,12 +77,12 @@ GEM
rufus-scheduler (3.9.2) rufus-scheduler (3.9.2)
fugit (~> 1.1, >= 1.11.1) fugit (~> 1.1, >= 1.11.1)
securerandom (0.3.2) securerandom (0.3.2)
serialport (1.3.2) serialport (1.4.0)
state_pattern (2.0.2) state_pattern (2.0.2)
timeout (0.4.3) timeout (0.6.1)
tzinfo (2.0.6) tzinfo (2.0.6)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
uri (1.0.2) uri (1.1.1)
PLATFORMS PLATFORMS
ruby ruby
@@ -91,6 +93,7 @@ DEPENDENCIES
gruff gruff
i18n i18n
mail mail
mqtt
mysql2 mysql2
net-http net-http
nokogiri nokogiri

18
LICENSE Normal file
View File

@@ -0,0 +1,18 @@
MIT License
Copyright (c) 2026 halteren
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -11,7 +11,7 @@ namespace :db do
task :migrate do task :migrate do
connection_details = YAML::load(File.open('config/database.yml')) connection_details = YAML::load(File.open('config/database.yml'))
ActiveRecord::Base.establish_connection(connection_details) ActiveRecord::Base.establish_connection(connection_details)
ActiveRecord::Migrator.migrate("db/migrate/") ActiveRecord::MigrationContext.new("db/migrate/").migrate
end end
desc "Create the db" desc "Create the db"

View File

@@ -94,14 +94,10 @@ class InSyncState < StatePattern::State
reading.save reading.save
end end
# Write to EmonHub # Post current_kw_consumed and current_kw_produced to MQTT
begin MqttClient.publish("home/energy/current_kw_consumed", reading.current_kw_consumed.to_s)
TCPSocket.open("printserver",5050){|s| MqttClient.publish("home/energy/current_kw_produced", reading.current_kw_produced.to_s)
s.write(sprintf("8 %d %d\r\n", reading.current_kw_consumed*1000, reading.current_kw_produced*1000))
}
rescue
p "Socket problem."
end
# Result # Result
return reading return reading

77
app/helpers/MqttClient.rb Normal file
View File

@@ -0,0 +1,77 @@
begin
require "mqtt"
rescue LoadError
# Fallback to mosquitto_pub when the mqtt gem is not available.
end
module MqttClient
class << self
def publish(topic, payload, retain: false, qos: 0)
return false if topic.nil? || topic.empty?
if defined?(MQTT)
publish_with_ruby_mqtt(topic, payload, retain, qos)
else
publish_with_mosquitto_pub(topic, payload, retain, qos)
end
rescue StandardError => e
warn("[MqttClient] publish failed: #{e.class}: #{e.message}")
false
end
private
def publish_with_ruby_mqtt(topic, payload, retain, qos)
options = {
host: mqtt_host,
port: mqtt_port,
keep_alive: 30
}
options[:username] = mqtt_username if mqtt_username
options[:password] = mqtt_password if mqtt_password
MQTT::Client.connect(options) do |client|
client.publish(topic, payload.to_s, retain, qos)
end
true
end
def publish_with_mosquitto_pub(topic, payload, retain, qos)
command = [
"mosquitto_pub",
"-h", mqtt_host,
"-p", mqtt_port.to_s,
"-q", qos.to_s,
"-t", topic,
"-m", payload.to_s
]
command << "-r" if retain
ok = system(*command)
warn("[MqttClient] mosquitto_pub failed") unless ok
ok
end
def mqtt_host
ENV.fetch("MQTT_HOST", "localhost")
end
def mqtt_port
Integer(ENV.fetch("MQTT_PORT", "1883"))
rescue ArgumentError
1883
end
def mqtt_username
v = ENV["MQTT_USERNAME"]
v.nil? || v.strip.empty? ? nil : v
end
def mqtt_password
ENV["MQTT_PASSWORD"]
end
end
end

View File

@@ -1,4 +1,4 @@
class CreatesPrices << ActiveRecord::Migration[4.2] class CreatesPrices < ActiveRecord::Migration[4.2]
def change def change
create_table :prices do |t| create_table :prices do |t|
t.datetime :hour t.datetime :hour
@@ -7,4 +7,5 @@ class CreatesPrices << ActiveRecord::Migration[4.2]
end end
add_index :prices, :hour add_index :prices, :hour
end
end end

View File

@@ -4,7 +4,7 @@ services:
restart: unless-stopped restart: unless-stopped
image: mysql:8.3 image: mysql:8.3
volumes: volumes:
- /home/pcog/smartmeter/data:/var/lib/mysql - mysql-data:/var/lib/mysql
ports: ports:
- 3306:3306 - 3306:3306
environment: environment:
@@ -12,24 +12,19 @@ services:
MYSQL_DATABASE: smartmeter MYSQL_DATABASE: smartmeter
smartmeter: smartmeter:
container_name: smartmeter container_name: smartmeter
labels:
- diun.enable=false
restart: unless-stopped restart: unless-stopped
build: . build: .
environment:
MQTT_HOST: 10.0.0.240
command: 'ruby ./smartmeter.rb' command: 'ruby ./smartmeter.rb'
devices: devices:
- "/dev/ttyUSB1:/dev/ttyUSB0" - "/dev/ttyUSB0:/dev/ttyUSB0"
volumes: volumes:
- .:/usr/src/app - .:/usr/src/app
depends_on: depends_on:
- db - db
rstudio:
container_name: rstudio
restart: unless-stopped
build: ./rstudio
environment:
PASSWORD: secret
volumes:
- ./rstudio:/home/rstudio/smartmeter
ports:
- 8787:8787
volumes:
mysql-data:

10
docker-entrypoint.sh Normal file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -e
unset BUNDLE_PATH
unset BUNDLE_BIN
# Start cron in background
cron
# Start your Ruby process in foreground
ruby /usr/src/app/smartmeter.rb

3
reportcron Normal file
View File

@@ -0,0 +1,3 @@
PATH=/usr/local/bundle/bin:/usr/local/bundle/gems/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
GEM_HOME=/usr/local/bundle
1 7 * * * root cd /usr/src/app && bundle exec ruby report_mailer.rb >> /var/log/cron.log 2>&1