#!/usr/bin/env ruby # frozen_string_literal: true require_relative '../lib/pzem016' require 'optimist' require 'ruby-progressbar' progname = File.basename($PROGRAM_NAME) commands = %w[read readconfig setconfig resetenergy scanbus] global_opts = Optimist.options do banner "PZEM-016 power measurement module utility.\n\nSyntax: #{progname} [global options] [options]\n\nCommands: #{commands}\n\nGlobal options:" opt :port, 'Serial port', type: :string, default: '/dev/ttyUSB0' opt :speed, 'Port speed', type: :integer, default: 9600 opt :timeout, 'Retry timeout', type: :integer, default: 1 opt :retries, 'Number of times to retry ModBus command', default: 1 opt :name, 'Device name', type: :string, default: 'PZEM-016 Device' opt :debug, 'Extra noise', type: :boolean stop_on commands end cmd = ARGV.empty? ? '(not given)' : ARGV.shift.downcase.to_sym cmd_opts = case cmd when :read, :readconfig # parse delete options Optimist.options do banner "Read #{cmd == :read ? "power" : "config"} values from specified device" opt :sladdr, 'Slave address', type: :integer, default: 1 opt :json, 'Output JSON', type: :boolean, default: false end when :setconfig opts = Optimist.options do banner "Set device address and/or alarm threshold: #{progname} setconfig " opt :sladdr, 'Slave address', type: :integer, default: 1 end if ARGV.size == 2 setting = ARGV.shift.downcase.to_sym Optimist.die "Specify one of 'addr' or 'alarm'; you gave '#{setting}'" unless %i[addr alarm].include?(setting) value = ARGV.shift.to_i else Optimist.die "syntax #{progname} setconfig " end opts when :resetenergy Optimist.options do banner 'Reset (clear) the energy counter (KWH) on the specified device' opt :sladdr, 'Slave address', type: :integer, default: 1 end when :scanbus Optimist.options do banner 'Scan ModBus for nodes/addrs' opt :read, 'Try to read values when node is found', type: :boolean end else puts "Valid commands: #{commands}" Optimist.die "unknown command: #{cmd}" end if global_opts[:debug] puts "Global options: #{global_opts.inspect}" puts "Subcommand: #{cmd.inspect}" puts "Subcommand options: #{cmd_opts.inspect}" puts "Remaining arguments: #{ARGV.inspect}" end bus = ModBus::RTUClient.connect(global_opts[:port], global_opts[:speed]) begin case cmd when :read bus.with_slave(cmd_opts[:sladdr]).tap do |slave| slave.read_retry_timeout = global_opts[:timeout] slave.read_retries = global_opts[:retries] input = Pzem016::InputRegisters.new(global_opts[:name], slave) input.read if cmd_opts[:json] puts input.to_json else input.report end end when :readconfig bus.with_slave(cmd_opts[:sladdr]).tap do |slave| slave.read_retry_timeout = global_opts[:timeout] slave.read_retries = global_opts[:retries] holding = Pzem016::HoldingRegisters.new(global_opts[:name], slave) holding.read if cmd_opts[:json] puts holding.to_json else holding.report end end when :setconfig slave = bus.with_slave(cmd_opts[:sladdr]) slave.read_retry_timeout = global_opts[:timeout] slave.read_retries = global_opts[:retries] device = Pzem016::HoldingRegisters.new(global_opts[:name], slave) case setting when :addr device.set_address(value) puts "Address of slave at '#{cmd_opts[:sladdr]}' has been changed to '#{value}'" when :alarm device.set_alarm(value) puts "Alarm threshold value for slave at #{cmd_opts[:sladdr]} has been set to #{value} watts" end when :resetenergy puts "Slave at addr #{cmd_opts[:sladdr]} energy counter reset: #{Pzem016::HoldingRegisters.new(global_opts[:name], bus.with_slave(cmd_opts[:sladdr])).reset_energy}" when :scanbus begin (1..247).each do |address| bus.with_slave(address) do |slave| slave.read_retry_timeout = global_opts[:timeout] slave.read_retries = global_opts[:retries] begin value = slave.read_holding_registers(0, 3) puts "\nDevice found at address #{value[2]}\n" if cmd_opts[:read] input = Pzem016::InputRegisters.new(global_opts[:name], slave) input.read input.report puts end rescue StandardError print '.' end end end puts "\n" rescue Interrupt puts "\nscan interrupted\n" end else puts "Command required, one of #{commands}" end rescue ModBus::Errors::ModBusTimeout puts 'ModBus timed out - are you sure you specified the correct --sladdr?' end