Add support for requesting input data on demand
Additional docs added in doc/MQ.md
This commit is contained in:
parent
cd00265522
commit
a8380d24db
2
Gemfile
2
Gemfile
|
@ -6,7 +6,7 @@ ruby '>= 2.3.0'
|
|||
gem 'zeitwerk'
|
||||
|
||||
gem 'inifile'
|
||||
gem 'lxp-packet', '~> 0.6.0'
|
||||
gem 'lxp-packet', '~> 0.7.0'
|
||||
|
||||
gem 'roda'
|
||||
|
||||
|
|
20
doc/MQ.md
20
doc/MQ.md
|
@ -12,7 +12,7 @@ You can use mqtts for secure connections and add username/password; see the URL
|
|||
|
||||
## Getting Inverter Status
|
||||
|
||||
As mentioned in the README, the inverter sends power data when it feels like it. This is every 2 minutes. There's no way to change this or ask for it on demand.
|
||||
As mentioned in the README, the inverter sends power data (also called inputs) when it feels like it. This is every 2 minutes.
|
||||
|
||||
It's sent by the inverter as 3 packets in sequence, around a second apart. When we receive each one, it is published under the keys `octolux/inputs/1`, `octolux/inputs/2`, and `octolux/inputs/3`. Each one always has the same set of data, and it should look something like this:
|
||||
|
||||
|
@ -33,6 +33,16 @@ Documenting all these is beyond the scope of this document, but broadly speaking
|
|||
* `t_` are temperatures in celsius; internally, and both radiators on the back of the inverter
|
||||
* not really worked out what all the `bat_status_` are yet as they're usually mostly 0
|
||||
|
||||
|
||||
You can also request this information to be sent immediately with `octolux/cmd/read_input`, with a payload of 1, 2 or 3, depending on which set of inputs you want:
|
||||
|
||||
```
|
||||
$ mosquitto_pub -t octolux/cmd/read_input -m 1
|
||||
```
|
||||
|
||||
This will prompt a further MQ message of `octolux/inputs/1` (as above), and additionally `octolux/result/read_input` will be sent with `OK` when it's complete.
|
||||
|
||||
|
||||
## Controlling the Inverter
|
||||
|
||||
`server.rb` will subscribe to a few topics that can be used for inverter control.
|
||||
|
@ -83,3 +93,11 @@ This says the inverter has told us that register 65 now contains the value 50. I
|
|||
So, clearly for now this requires your client code to know that register 65 is what will change in response to `discharge_pct`. For this reason, the `result` topics are probably more useful for now.
|
||||
|
||||
However, you could use these topics to record or graph every time a register changes, regardless of *how* it was changed, since these will be published even if MQ wasn't used to action the change (eg, via LuxPower's portal or app).
|
||||
|
||||
Finally, if you know the register you want and want it on-demand, you can send `octolux/cmd/read_hold` with an integer message:
|
||||
|
||||
```
|
||||
$ mosquitto_pub -t octolux/cmd/read_hold -m 65
|
||||
```
|
||||
|
||||
This will result in `octolux/hold/65` being sent (as above) and additionally `octolux/result/read_hold` will be sent (with `OK`) once complete.
|
||||
|
|
|
@ -18,6 +18,24 @@ class LuxController
|
|||
@socket = nil
|
||||
end
|
||||
|
||||
def read_input(num)
|
||||
LOGGER.debug "read_input(#{num})"
|
||||
|
||||
type = case num
|
||||
when 1 then LXP::Packet::ReadInput1
|
||||
when 2 then LXP::Packet::ReadInput2
|
||||
when 3 then LXP::Packet::ReadInput3
|
||||
end
|
||||
|
||||
pkt = packet(type: type)
|
||||
socket.write(pkt)
|
||||
read_reply(pkt)
|
||||
end
|
||||
|
||||
def read_hold(register)
|
||||
read_register(register)
|
||||
end
|
||||
|
||||
def charge(enable)
|
||||
LOGGER.debug "charge(#{enable})"
|
||||
update_register(21, LXP::Packet::RegisterBits::AC_CHARGE_ENABLE, enable)
|
||||
|
@ -77,10 +95,7 @@ class LuxController
|
|||
LOGGER.debug "read_register(#{register})"
|
||||
pkt = packet(type: LXP::Packet::ReadHold, register: register)
|
||||
socket.write(pkt)
|
||||
unless (r = socket.read_reply(pkt))
|
||||
LOGGER.fatal 'invalid/no reply from inverter'
|
||||
raise SocketError
|
||||
end
|
||||
r = read_reply(pkt)
|
||||
|
||||
LOGGER.debug "read_register(#{register}) => #{r.value}"
|
||||
|
||||
|
@ -94,10 +109,7 @@ class LuxController
|
|||
pkt.value = val
|
||||
|
||||
socket.write(pkt)
|
||||
unless (r = socket.read_reply(pkt))
|
||||
LOGGER.fatal 'invalid/no reply from inverter'
|
||||
raise SocketError
|
||||
end
|
||||
r = read_reply(pkt)
|
||||
|
||||
LOGGER.debug "set_register(#{register}) => #{r.value}"
|
||||
|
||||
|
@ -108,11 +120,20 @@ class LuxController
|
|||
@socket ||= LuxSocket.new(host: @host, port: @port)
|
||||
end
|
||||
|
||||
def packet(type:, register:)
|
||||
def packet(type:, register: nil)
|
||||
type.new.tap do |pkt|
|
||||
pkt.register = register
|
||||
pkt.register = register if register
|
||||
pkt.datalog_serial = @datalog
|
||||
pkt.inverter_serial = @serial
|
||||
end
|
||||
end
|
||||
|
||||
def read_reply(pkt)
|
||||
unless (r = socket.read_reply(pkt))
|
||||
LOGGER.fatal 'invalid/no reply from inverter'
|
||||
raise SocketError
|
||||
end
|
||||
|
||||
r
|
||||
end
|
||||
end
|
||||
|
|
88
lib/mq.rb
88
lib/mq.rb
|
@ -5,35 +5,13 @@ require 'mqtt/sub_handler'
|
|||
class MQ
|
||||
class << self
|
||||
def run
|
||||
sub.subscribe_to 'octolux/cmd/ac_charge' do |data|
|
||||
LOGGER.info "MQ cmd/ac_charge => #{data}"
|
||||
r = lux_controller.charge(bool(data))
|
||||
lux_controller.close
|
||||
sub.publish_to('octolux/result/ac_charge', r ? 'OK' : 'FAIL')
|
||||
end
|
||||
sub.subscribe_to 'octolux/cmd/read_hold', &method(:read_hold_cb)
|
||||
sub.subscribe_to 'octolux/cmd/read_input', &method(:read_input_cb)
|
||||
|
||||
sub.subscribe_to 'octolux/cmd/forced_discharge' do |data|
|
||||
LOGGER.info "MQ cmd/forced_discharge => #{data}"
|
||||
r = lux_controller.discharge(bool(data))
|
||||
lux_controller.close
|
||||
sub.publish_to('octolux/result/forced_discharge', r ? 'OK' : 'FAIL')
|
||||
end
|
||||
|
||||
sub.subscribe_to 'octolux/cmd/charge_pct' do |data|
|
||||
LOGGER.info "MQ cmd/charge_pct => #{data}"
|
||||
r = (lux_controller.charge_pct = data.to_i)
|
||||
lux_controller.close
|
||||
sub.publish_to('octolux/result/charge_pct',
|
||||
r == data.to_i ? 'OK' : 'FAIL')
|
||||
end
|
||||
|
||||
sub.subscribe_to 'octolux/cmd/discharge_pct' do |data|
|
||||
LOGGER.info "MQ cmd/discharge_pct => #{data}"
|
||||
r = (lux_controller.discharge_pct = data.to_i)
|
||||
lux_controller.close
|
||||
sub.publish_to('octolux/result/discharge_pct',
|
||||
r == data.to_i ? 'OK' : 'FAIL')
|
||||
end
|
||||
sub.subscribe_to 'octolux/cmd/ac_charge', &method(:ac_charge_cb)
|
||||
sub.subscribe_to 'octolux/cmd/forced_discharge', &method(:forced_discharge_cb)
|
||||
sub.subscribe_to 'octolux/cmd/charge_pct', &method(:charge_pct_cb)
|
||||
sub.subscribe_to 'octolux/cmd/discharge_pct', &method(:discharge_pct_cb)
|
||||
|
||||
Thread.stop # sleep forever
|
||||
end
|
||||
|
@ -44,6 +22,60 @@ class MQ
|
|||
|
||||
private
|
||||
|
||||
def read_hold_cb(data, *)
|
||||
LOGGER.info "MQ cmd/read_hold => #{data}"
|
||||
lux_controller.read_hold(data.to_i)
|
||||
lux_controller.close
|
||||
sub.publish_to('octolux/result/read_hold', 'OK')
|
||||
rescue LuxController::SocketError
|
||||
sub.publish_to('octolux/result/read_hold', 'FAIL')
|
||||
end
|
||||
|
||||
def read_input_cb(data, *)
|
||||
LOGGER.info "MQ cmd/read_input => #{data}"
|
||||
lux_controller.read_input(data.to_i)
|
||||
lux_controller.close
|
||||
sub.publish_to('octolux/result/read_input', 'OK')
|
||||
rescue LuxController::SocketError
|
||||
sub.publish_to('octolux/result/read_input', 'FAIL')
|
||||
end
|
||||
|
||||
def ac_charge_cb(data, *)
|
||||
LOGGER.info "MQ cmd/ac_charge => #{data}"
|
||||
r = lux_controller.charge(bool(data))
|
||||
lux_controller.close
|
||||
sub.publish_to('octolux/result/ac_charge', r ? 'OK' : 'FAIL')
|
||||
rescue LuxController::SocketError
|
||||
sub.publish_to('octolux/result/ac_charge', 'FAIL')
|
||||
end
|
||||
|
||||
def forced_discharge_cb(data, *)
|
||||
LOGGER.info "MQ cmd/forced_discharge => #{data}"
|
||||
r = lux_controller.discharge(bool(data))
|
||||
lux_controller.close
|
||||
sub.publish_to('octolux/result/forced_discharge', r ? 'OK' : 'FAIL')
|
||||
rescue LuxController::SocketError
|
||||
sub.publish_to('octolux/result/forced_discharge', 'FAIL')
|
||||
end
|
||||
|
||||
def charge_pct_cb(data, *)
|
||||
LOGGER.info "MQ cmd/charge_pct => #{data}"
|
||||
r = (lux_controller.charge_pct = data.to_i)
|
||||
lux_controller.close
|
||||
sub.publish_to('octolux/result/charge_pct', r == data.to_i ? 'OK' : 'FAIL')
|
||||
rescue LuxController::SocketError
|
||||
sub.publish_to('octolux/result/charge_pct', 'FAIL')
|
||||
end
|
||||
|
||||
def discharge_pct_cb(data, *)
|
||||
LOGGER.info "MQ cmd/discharge_pct => #{data}"
|
||||
r = (lux_controller.discharge_pct = data.to_i)
|
||||
lux_controller.close
|
||||
sub.publish_to('octolux/result/discharge_pct', r == data.to_i ? 'OK' : 'FAIL')
|
||||
rescue LuxController::SocketError
|
||||
sub.publish_to('octolux/result/discharge_pct', 'FAIL')
|
||||
end
|
||||
|
||||
def uri
|
||||
CONFIG['mqtt']['uri']
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue