diff --git a/Dockerfile b/Dockerfile index 3b7ca67..5c19585 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,5 @@ RUN \ apt-get install -y $BUILD_PACKAGES && \ bundle install -COPY . . CMD ["/bin/bash -c ruby ./smartmeter.rb"] diff --git a/Gemfile b/Gemfile index 4b71a07..8f68820 100644 --- a/Gemfile +++ b/Gemfile @@ -12,3 +12,4 @@ gem 'numo-narray' gem 'i18n' gem 'gruff' gem 'net-http' # to avoid error: uninitialized constant ... +gem 'mqtt' diff --git a/Gemfile.lock b/Gemfile.lock index 030190f..f95b5e4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,13 +1,13 @@ GEM remote: https://rubygems.org/ specs: - activemodel (7.1.5.1) - activesupport (= 7.1.5.1) - activerecord (7.1.5.1) - activemodel (= 7.1.5.1) - activesupport (= 7.1.5.1) + activemodel (7.1.6) + activesupport (= 7.1.6) + activerecord (7.1.6) + activemodel (= 7.1.6) + activesupport (= 7.1.6) timeout (>= 0.4.0) - activesupport (7.1.5.1) + activesupport (7.1.6) base64 benchmark (>= 0.3) bigdecimal @@ -20,53 +20,55 @@ GEM mutex_m securerandom (>= 0.3) tzinfo (~> 2.0) - base64 (0.2.0) - benchmark (0.4.0) - bigdecimal (3.1.9) - concurrent-ruby (1.3.4) - connection_pool (2.4.1) + base64 (0.3.0) + benchmark (0.5.0) + bigdecimal (4.1.2) + concurrent-ruby (1.3.6) + connection_pool (2.5.5) daemons (1.4.1) - date (3.4.1) - drb (2.2.1) - et-orbi (1.2.11) + date (3.5.1) + drb (2.2.3) + et-orbi (1.4.0) tzinfo - fugit (1.11.1) - et-orbi (~> 1, >= 1.2.11) + fugit (1.12.1) + et-orbi (~> 1.4) raabro (~> 1.4) gruff (0.24.0) histogram rmagick (>= 5.3) histogram (0.2.4.1) - i18n (1.14.6) + i18n (1.14.8) concurrent-ruby (~> 1.0) - logger (1.6.4) - mail (2.8.1) + logger (1.7.0) + mail (2.9.0) + logger mini_mime (>= 0.1.1) net-imap net-pop net-smtp mini_mime (1.1.5) - mini_portile2 (2.8.8) - minitest (5.25.4) + minitest (5.26.1) + mqtt (0.7.0) + logger mutex_m (0.3.0) - mysql2 (0.5.6) - net-http (0.6.0) - uri - net-imap (0.4.18) + mysql2 (0.5.7) + bigdecimal + net-http (0.9.1) + uri (>= 0.11.1) + net-imap (0.4.24) date net-protocol net-pop (0.1.2) net-protocol net-protocol (0.2.2) timeout - net-smtp (0.5.0) + net-smtp (0.5.1) net-protocol - nokogiri (1.15.7) - mini_portile2 (~> 2.8.2) + nokogiri (1.15.7-aarch64-linux) racc (~> 1.4) numo-narray (0.9.2.1) observer (0.1.2) - pkg-config (1.5.8) + pkg-config (1.6.5) raabro (1.4.0) racc (1.8.1) rmagick (5.5.0) @@ -75,12 +77,12 @@ GEM rufus-scheduler (3.9.2) fugit (~> 1.1, >= 1.11.1) securerandom (0.3.2) - serialport (1.3.2) + serialport (1.4.0) state_pattern (2.0.2) - timeout (0.4.3) + timeout (0.6.1) tzinfo (2.0.6) concurrent-ruby (~> 1.0) - uri (1.0.2) + uri (1.1.1) PLATFORMS ruby @@ -91,6 +93,7 @@ DEPENDENCIES gruff i18n mail + mqtt mysql2 net-http nokogiri diff --git a/app/helpers/InSyncState.rb b/app/helpers/InSyncState.rb index c5fbd7a..7d9a164 100644 --- a/app/helpers/InSyncState.rb +++ b/app/helpers/InSyncState.rb @@ -94,15 +94,11 @@ class InSyncState < StatePattern::State reading.save end - # Write to EmonHub - begin - TCPSocket.open("printserver",5050){|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 + # Post current_kw_consumed and current_kw_produced to MQTT + MqttClient.publish("home/energy/current_kw_consumed", reading.current_kw_consumed.to_s) + MqttClient.publish("home/energy/current_kw_produced", reading.current_kw_produced.to_s) + + # Result return reading end diff --git a/app/helpers/MqttClient.rb b/app/helpers/MqttClient.rb new file mode 100644 index 0000000..9c5cf00 --- /dev/null +++ b/app/helpers/MqttClient.rb @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml index ba1d007..58e9265 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,15 +21,6 @@ services: - .:/usr/src/app depends_on: - db - rstudio: - container_name: rstudio - restart: unless-stopped - build: ./rstudio - environment: - PASSWORD: secret - volumes: - - ./rstudio:/home/rstudio/smartmeter - ports: - - 8787:8787 +