Master/ Slave handling

This commit is contained in:
AndyWhittaker 2020-10-20 15:51:24 +01:00
parent 89aaaf8f4a
commit 5821bd5bbd
14 changed files with 214 additions and 78 deletions

View File

@ -0,0 +1,7 @@
{
"folders": [
{
"path": "."
}
]
}

View File

@ -16,7 +16,9 @@ After the datalogger reboots (this takes only a couple of seconds and does not a
## Enabling AC Charge
You may need to tweak a setting to enable AC Charging. This is easiest done on LuxPower's web portal, and the phone apps will let you do it too.
You may need to tweak a setting to enable AC Charging. This is easiest done on LuxPower's web portal, and the phone apps will let you do it too especially if you can connect locally to the inverter's dongle rather than connect via China (can be a bit slow with numerous timeouts).
The trick with these apps is to select your inverter from the dropdown listbox above and then press "Read". If not connected locally, this can take 2 minutes to work. If you think it hasn't worked, just click "Read" again but be patient. After inputting you new values, press the "Set" button. Again, this can take at least a minute to work - you should see a "Success" message pop-up, otherwise "Timed Out" will appear; if this happens, just press the "Set" button once again.
Check that `AC Charge Start Time 1` and `AC Charge End Time 1` are `00:00` and `23:59` respectively, as seen in this screenshot:
@ -24,4 +26,4 @@ Check that `AC Charge Start Time 1` and `AC Charge End Time 1` are `00:00` and `
This means that AC Charging can be used between 00:00 and 23:59 (ie, any time of day).
Time 2 and Time 3 can be left as they are, unless you specifically want more specific time ranges setting.
Time 2 and Time 3 can be left as they are, unless you specifically want more specific time ranges setting.

View File

@ -2,7 +2,7 @@
`server.rb` can be configured to connect to an MQTT server. For this, add a section to your `config.ini` like so;
```ini
``` ini
[mqtt]
# see https://github.com/mqtt/mqtt.github.io/wiki/URI-Scheme for URI help
uri = mqtt://server:1883
@ -14,25 +14,24 @@ You can use mqtts for secure connections and add username/password; see the URL
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:
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/masterinputs/1`, `octolux/masterinputs/2`, and `octolux/masterinputs/3`. Note that the Slave inverter will send iunder the keys `octolux/slaveinputs/1`, `octolux/slaveinputs/2`, and `octolux/slaveinputs/3`. Each one always has the same set of data, and it should look something like this:
```
$ mosquitto_sub -t octolux/inputs/+ -v
octolux/inputs/1 {"status":32,"v_bat":49.4,"soc":53,"p_pv":550,"p_charge":114,"p_discharge":0,"v_acr":247.3,"f_ac":49.96,"p_inv":0,"p_rec":116,"v_eps":247.3,"f_eps":49.96,"p_to_grid":0,"p_to_user":0,"e_pv_day":0.7,"e_inv_day":2.0,"e_rec_day":1.7,"e_chg_day":1.9,"e_dischg_day":2.4,"e_eps_day":0.0,"e_to_grid_day":0.0,"e_to_user_day":3.9,"v_bus_1":379.9,"v_bus_2":300.5}
octolux/inputs/2 {"e_pv_all":1675.6,"e_inv_all":943.9,"e_rec_all":1099.3,"e_chg_all":1251.2,"e_dischg_all":1151.6,"e_eps_all":0.0,"e_to_grid_all":124.0,"e_to_user_all":1115.8,"t_inner":43,"t_rad_1":30,"t_rad_2":30}
octolux/inputs/3 {"max_chg_curr":105.0,"max_dischg_curr":150.0,"charge_volt_ref":53.2,"dischg_cut_volt":40.0,"bat_status_0":0,"bat_status_1":0,"bat_status_2":0,"bat_status_3":0,"bat_status_4":0,"bat_status_5":192,"bat_status_6":0,"bat_status_7":0,"bat_status_8":0,"bat_status_9":0,"bat_status_inv":3}
octolux/masterinputs/1 {"status":32,"v_bat":49.4,"soc":53,"p_pv":550,"p_charge":114,"p_discharge":0,"v_acr":247.3,"f_ac":49.96,"p_inv":0,"p_rec":116,"v_eps":247.3,"f_eps":49.96,"p_to_grid":0,"p_to_user":0,"e_pv_day":0.7,"e_inv_day":2.0,"e_rec_day":1.7,"e_chg_day":1.9,"e_dischg_day":2.4,"e_eps_day":0.0,"e_to_grid_day":0.0,"e_to_user_day":3.9,"v_bus_1":379.9,"v_bus_2":300.5}
octolux/masterinputs/2 {"e_pv_all":1675.6,"e_inv_all":943.9,"e_rec_all":1099.3,"e_chg_all":1251.2,"e_dischg_all":1151.6,"e_eps_all":0.0,"e_to_grid_all":124.0,"e_to_user_all":1115.8,"t_inner":43,"t_rad_1":30,"t_rad_2":30}
octolux/masterinputs/3 {"max_chg_curr":105.0,"max_dischg_curr":150.0,"charge_volt_ref":53.2,"dischg_cut_volt":40.0,"bat_status_0":0,"bat_status_1":0,"bat_status_2":0,"bat_status_3":0,"bat_status_4":0,"bat_status_5":192,"bat_status_6":0,"bat_status_7":0,"bat_status_8":0,"bat_status_9":0,"bat_status_inv":3}
```
Documenting all these is beyond the scope of this document, but broadly speaking:
* `status` is 0 when idle, 16 when discharging (`p_dischg > 0`), 32 when charging (`p_charge > 0`)
* `v_bat is battery voltage, `soc` is state-of-charge in %. `v_bus` are internal bus voltages
* those prefixed with `p_` are intantaneous power in watts
* `e_` are energy accumulators in `kWh` (and they're present for both today and all-time)
* `f_` are mains Hz
* `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
* `status` is 0 when idle, 16 when discharging (`p_dischg > 0`), 32 when charging (`p_charge > 0`)
* `v_bat is battery voltage, ` soc`is state-of-charge in %.`v\_bus\` are internal bus voltages
* those prefixed with `p_` are intantaneous power in watts
* `e_` are energy accumulators in `kWh` (and they're present for both today and all-time)
* `f_` are mains Hz
* `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:
@ -40,8 +39,14 @@ You can also request this information to be sent immediately with `octolux/cmd/r
$ 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.
This will prompt a further MQ message of `octolux/masterinputs/1` (as above), and additionally `octolux/result/read_input` will be sent with `OK` when it's complete.
Note for parallel inverters you need to do some post-processing. The SOC values need to be averaged to get the agrigated battery SOC. All the other values need to be summed together.
To match the Lux monitoring web-app you need to do slightly more.
`House Consumption is a sum of p_inv, p_to_user and p_pv of both inverters.`
`p_to_user is the direct import from the grid.`
## Controlling the Inverter
@ -49,11 +54,10 @@ This will prompt a further MQ message of `octolux/inputs/1` (as above), and addi
In the following, "boolean" can be any of the following to mean true: `1`, `t`, `true`, `y`, `yes`, `on`. Anything else is interpreted as false.
* `octolux/cmd/ac_charge` - send this a boolean to enable or disable AC charging. This is taking energy from the grid to charge; this does not need to be on to charge from solar.
* `octolux/cmd/forced_discharge` - send this a boolean to enable or disable forced discharging. This is only useful if you get paid for export and the export rate is high. Normally, this should be off; this is *not* related to normal discharging operation.
* `octolux/cmd/discharge_pct` - send this an integer (0-100) to set the discharge rate. Normally this is 100% to enable normal discharge. If you have another cheap electricity source and want the inverter to stop supplying electricity, setting this to 0 will do that.
* `octolux/cmd/charge_pct` - send this an integer (0-100) to set the charge rate. This probably isn't terribly useful, but if you want to limit AC charging to less than the full 3600W, use this to do it.
* `octolux/cmd/ac_charge` \- send this a boolean to enable or disable AC charging\. This is taking energy from the grid to charge; this does not need to be on to charge from solar\.
* `octolux/cmd/forced_discharge` \- send this a boolean to enable or disable forced discharging\. This is only useful if you get paid for export and the export rate is high\. Normally\, this should be off; this is *not* related to normal discharging operation.
* `octolux/cmd/discharge_pct` \- send this an integer \(0\-100\) to set the discharge rate\. Normally this is 100% to enable normal discharge\. If you have another cheap electricity source and want the inverter to stop supplying electricity\, setting this to 0 will do that\.
* `octolux/cmd/charge_pct` \- send this an integer \(0\-100\) to set the charge rate\. This probably isn't terribly useful\, but if you want to limit AC charging to less than the full 3600W\, use this to do it\.
So for example, you could do;
@ -73,7 +77,7 @@ This is quite low-level and may not be terribly useful yet; this is subject to i
The inverter has a bunch of registers that determine current operation. There's a full list in my [lxp-packet](https://github.com/celsworth/lxp-packet/blob/master/doc/LXP_REGISTERS.txt) gem.
So for example, setting discharge percentage is register 65 (DISCHG_POWER_PERCENT_CMD from the register list).
So for example, setting discharge percentage is register 65 (DISCHG\_POWER\_PERCENT\_CMD from the register list).
So if I send:
@ -100,4 +104,4 @@ Finally, if you know the register you want and want it on-demand, you can send `
$ 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.
This will result in `octolux/hold/65` being sent (as above) and additionally `octolux/result/read_hold` will be sent (with `OK`) once complete.

View File

@ -7,7 +7,8 @@ LOGGER.info "Current Octopus Unit Price: #{octopus.price}p"
# $lc is LuxController. This talks directly to the inverter and can do things
# like enabling/disabling AC charge and setting charge power rates.
# LOGGER.info "Charge Power = #{lc.charge_pct}%"
# LOGGER.info "Charge Power = #{lc-mast.charge_pct}%"
# LOGGER.info "Charge Power = #{lc-slave.charge_pct}%"
# $ls is LuxStatus. This is gleaned from the optional server.rb since the
# data in it is only sent by the inverter every 2 minutes.
@ -18,8 +19,13 @@ begin
# if the current price is 5p or lower, enable AC charge
charge = octopus.price <= 5
unless lc.charge(charge)
LOGGER.error 'Failed to update inverter status!'
unless lc-mast.charge(charge)
LOGGER.error 'Failed to update master inverter status :-('
exit 255
end
unless lc-slave.charge(charge)
LOGGER.error 'Failed to update slave inverter status :-('
exit 255
end
rescue StandardError => e

View File

@ -94,7 +94,7 @@ end
LOGGER.info "ac_charge = #{ac_charge} ; discharge = #{discharge} (> #{min_discharge_price}p)"
discharge_pct = discharge ? 100 : 0
r = (lc.discharge_pct = discharge_pct)
exit 255 unless r == discharge_pct
r-mast = (lc-mast.discharge_pct = discharge_pct)
exit 255 unless r-mast == discharge_pct
exit 255 unless lc.charge(ac_charge)
exit 255 unless lc-mast.charge(ac_charge)

View File

@ -19,7 +19,7 @@ class LuxController
end
def read_input(num)
LOGGER.debug "read_input(#{num})"
LOGGER.info "read_input(#{num})"
type = case num
when 1 then LXP::Packet::ReadInput1
@ -76,12 +76,12 @@ class LuxController
# Returns true if the bit was already as requested, or updated.
#
def update_register(register, bit, enable)
LOGGER.debug "update_register(#{register}, #{bit}, #{enable})"
LOGGER.info "update_register(#{register}, #{bit}, #{enable})"
old_val = read_register(register)
enabled = (old_val & bit) == bit
if enable == enabled
LOGGER.debug "update_register(#{register}) => no action required"
LOGGER.info "update_register(#{register}) => no action required"
return true
end
@ -92,18 +92,18 @@ class LuxController
end
def read_register(register)
LOGGER.debug "read_register(#{register})"
LOGGER.info "read_register(#{register})"
pkt = packet(type: LXP::Packet::ReadHold, register: register)
socket.write(pkt)
r = read_reply(pkt)
LOGGER.debug "read_register(#{register}) => #{r.value}"
LOGGER.info "read_register(#{register}) => #{r.value}"
r.value
end
def set_register(register, val)
LOGGER.debug "set_register(#{register}, #{val})"
LOGGER.info "set_register(#{register}, #{val})"
pkt = packet(type: LXP::Packet::WriteSingle, register: register)
pkt.value = val
@ -111,7 +111,7 @@ class LuxController
socket.write(pkt)
r = read_reply(pkt)
LOGGER.debug "set_register(#{register}) => #{r.value}"
LOGGER.info "set_register(#{register}) => #{r.value}"
r.value
end
@ -130,7 +130,7 @@ class LuxController
def read_reply(pkt)
unless (r = socket.read_reply(pkt))
LOGGER.fatal 'invalid/no reply from inverter'
LOGGER.fatal 'Invalid/no reply from inverter'
raise SocketError
end

View File

@ -6,14 +6,31 @@ require 'lxp/packet'
#
class LuxListener
class << self
def run
def run(host:, port:, slave:)
LOGGER.info "LuxListener - host #{host} port #{port} slave #{slave}"
@slave = slave
loop do
socket = LuxSocket.new(host: CONFIG['lxp']['host'], port: CONFIG['lxp']['port'])
listen(socket)
socket = LuxSocket.new(host: host, port: port)
if @slave == 0
LOGGER.info("Created new Master LuxListener")
else
LOGGER.info("Created new Slave LuxListener")
end
listen(socket, slave)
rescue StandardError => e
LOGGER.error "Socket Error: #{e}"
if @slave == 0
LOGGER.error "Socket Master Error: #{e}"
else
LOGGER.error "Socket Slave Error: #{e}"
end
LOGGER.debug e.backtrace.join("\n")
LOGGER.info 'Reconnecting in 5 seconds'
if @slave == 0
LOGGER.info 'Reconnecting to Master in 5 seconds'
else
LOGGER.info 'Reconnecting to Slave in 5 seconds'
end
sleep 5
end
end
@ -30,20 +47,20 @@ class LuxListener
private
def listen(socket)
def listen(socket, slave)
loop do
next unless (pkt = socket.read_packet)
@last_packet = Time.now
process_input(pkt) if pkt.is_a?(LXP::Packet::ReadInput)
process_read_hold(pkt) if pkt.is_a?(LXP::Packet::ReadHold)
process_write_single(pkt) if pkt.is_a?(LXP::Packet::WriteSingle)
process_input(pkt, slave) if pkt.is_a?(LXP::Packet::ReadInput)
process_read_hold(pkt, slave) if pkt.is_a?(LXP::Packet::ReadHold)
process_write_single(pkt, slave) if pkt.is_a?(LXP::Packet::WriteSingle)
end
ensure
socket.close
end
def process_input(pkt)
def process_input(pkt, slave)
inputs.merge!(pkt.to_h)
n = case pkt
@ -52,19 +69,32 @@ class LuxListener
when LXP::Packet::ReadInput3 then 3
end
MQ.publish("octolux/inputs/#{n}", pkt.to_h)
end
def process_read_hold(pkt)
pkt.to_h.each do |register, value|
registers[register] = value
MQ.publish("octolux/hold/#{register}", value)
# Not very neat... but it allows us to see both inverters seperatly.
if slave == 0
MQ.publish("octolux/masterinputs/#{n}", pkt.to_h, slave)
else
MQ.publish("octolux/slaveinputs/#{n}", pkt.to_h, slave)
end
end
def process_write_single(pkt)
registers[pkt.register] = pkt.value
MQ.publish("octolux/hold/#{pkt.register}", pkt.value)
def process_read_hold(pkt, slave)
pkt.to_h.each do |register, value|
registers[register] = value
if slave == 0
MQ.publish("octolux/masterhold/#{register}", value, slave)
else
MQ.publish("octolux/slavehold/#{register}", value, slave)
end
end
end
end
end
def process_write_single(pkt, slave)
registers[pkt.register] = pkt.value
if slave == 0
MQ.publish("octolux/masterhold/#{pkt.register}", pkt.value, slave)
else
MQ.publish("octolux/slavehold/#{pkt.register}", pkt.value, slave)
end
end
end #class << self
end #class LuxListener

View File

@ -16,16 +16,22 @@ class MQ
Thread.stop # sleep forever
end
def publish(topic, message)
def publish(topic, message, slave)
sub.publish_to(topic, message) if uri
@slave = slave
end
private
def read_hold_cb(data, *)
LOGGER.info "MQ cmd/read_hold => #{data}"
lux_controller.read_hold(data.to_i)
lux_controller.close
if @slave == 0
lux_controller.read_hold(data.to_i)
lux_controller.close
else
lux_controllerslave.read_hold(data.to_i)
lux_controllerslave.close
end
sub.publish_to('octolux/result/read_hold', 'OK')
rescue LuxController::SocketError
sub.publish_to('octolux/result/read_hold', 'FAIL')
@ -33,8 +39,13 @@ class MQ
def read_input_cb(data, *)
LOGGER.info "MQ cmd/read_input => #{data}"
lux_controller.read_input(data.to_i)
lux_controller.close
if @slave == 0
lux_controller.read_input(data.to_i)
lux_controller.close
else
lux_controllerslave.read_input(data.to_i)
lux_controllerslave.close
end
sub.publish_to('octolux/result/read_input', 'OK')
rescue LuxController::SocketError
sub.publish_to('octolux/result/read_input', 'FAIL')
@ -42,8 +53,13 @@ class MQ
def ac_charge_cb(data, *)
LOGGER.info "MQ cmd/ac_charge => #{data}"
r = lux_controller.charge(bool(data))
lux_controller.close
if @slave == 0
r = lux_controller.charge(bool(data))
lux_controller.close
else
r = lux_controllerslave.charge(bool(data))
lux_controllerslave.close
end
sub.publish_to('octolux/result/ac_charge', r ? 'OK' : 'FAIL')
rescue LuxController::SocketError
sub.publish_to('octolux/result/ac_charge', 'FAIL')
@ -51,8 +67,13 @@ class MQ
def forced_discharge_cb(data, *)
LOGGER.info "MQ cmd/forced_discharge => #{data}"
r = lux_controller.discharge(bool(data))
lux_controller.close
if @slave == 0
r = lux_controller.discharge(bool(data))
lux_controller.close
else
r = lux_controllerslave.discharge(bool(data))
lux_controllerslave.close
end
sub.publish_to('octolux/result/forced_discharge', r ? 'OK' : 'FAIL')
rescue LuxController::SocketError
sub.publish_to('octolux/result/forced_discharge', 'FAIL')
@ -60,8 +81,13 @@ class MQ
def charge_pct_cb(data, *)
LOGGER.info "MQ cmd/charge_pct => #{data}"
r = (lux_controller.charge_pct = data.to_i)
lux_controller.close
if @slave == 0
r = (lux_controller.charge_pct = data.to_i)
lux_controller.close
else
r = (lux_controllerslave.charge_pct = data.to_i)
lux_controllerslave.close
end
sub.publish_to('octolux/result/charge_pct', r == data.to_i ? 'OK' : 'FAIL')
rescue LuxController::SocketError
sub.publish_to('octolux/result/charge_pct', 'FAIL')
@ -69,8 +95,13 @@ class MQ
def discharge_pct_cb(data, *)
LOGGER.info "MQ cmd/discharge_pct => #{data}"
r = (lux_controller.discharge_pct = data.to_i)
lux_controller.close
if @slave == 0
r = (lux_controller.discharge_pct = data.to_i)
lux_controller.close
else
r = (lux_controllerslave.discharge_pct = data.to_i)
lux_controllerslave.close
end
sub.publish_to('octolux/result/discharge_pct', r == data.to_i ? 'OK' : 'FAIL')
rescue LuxController::SocketError
sub.publish_to('octolux/result/discharge_pct', 'FAIL')
@ -90,7 +121,12 @@ class MQ
port: CONFIG['lxp']['port'],
serial: CONFIG['lxp']['serial'],
datalog: CONFIG['lxp']['datalog'])
end
@lux_controllerslave ||= LuxController.new(host: CONFIG['lxp']['host_slave'],
port: CONFIG['lxp']['port_slave'],
serial: CONFIG['lxp']['serial_slave'],
datalog: CONFIG['lxp']['datalog_slave'])
end
def bool(input)
case input

View File

@ -19,18 +19,38 @@ Thread.new do
loop do
t = Thread.new do
begin
LuxListener.run
LOGGER.info("Creating new Master LuxListener")
LuxListener.run(host: CONFIG['lxp']['host'], port: CONFIG['lxp']['port'], slave: 0)
rescue StandardError => e
LOGGER.error "LuxListener Thread: #{e}"
LOGGER.error "LuxListener Master Thread: #{e}"
LOGGER.debug e.backtrace.join("\n")
end
end
t.join
LOGGER.info 'Restarting LuxListener Thread in 5 seconds'
LOGGER.info 'Restarting Master LuxListener Thread in 5 seconds'
sleep 5
end
end
## start a separate for the slave controller
Thread.new do
loop do
tslave = Thread.new do
begin
LOGGER.info("Creating new Slave LuxListener")
LuxListener.run(host: CONFIG['lxp']['host_slave'], port: CONFIG['lxp']['port_slave'], slave: 1)
rescue StandardError => e
LOGGER.error "LuxListener Slave Thread: #{e}"
LOGGER.debug e.backtrace.join("\n")
end
end
tslave.join
LOGGER.info 'Restarting Slave LuxListener Thread in 5 seconds'
sleep 5
end
end
# MQTT stuff
Rack::Server.start(Host: CONFIG['server']['listen_host'] || CONFIG['server']['host'],
Port: CONFIG['server']['port'],
app: App.freeze.app)

View File

@ -10,6 +10,6 @@ They all end up doing the same job, but over different transport mechanisms. So
* `http_` send an HTTP request to the HTTP API on `server.rb` (TODO, not written yet)
* (no prefix) direct TCP access - communicate directly with the inverter and do not need `server.rb` running at all
So for example, `mq_ac_charge_on.rb` and `ac_charge_on.rb` both enable AC chage, but the former does it by sending an MQ message which `server.rb` receives (and that talks to the inverter), whereas `ac_charge_on.rb` opens a TCP socket to the inverter and does it directly.
So for example, `mq_ac_charge_on.rb` and `ac_charge_on.rb` both enable AC charge, but the former does it by sending an MQ message which `server.rb` receives (and that talks to the inverter), whereas `ac_charge_on.rb` opens a TCP socket to the inverter and does it directly.
Therefore, if `mq_ac_charge_on.rb` doesn't work but `ac_charge_on.rb` does, you can look to the MQ side of your configuration to find the problem.
Therefore, if `mq_ac_charge_on.rb` doesn't work but `ac_charge_on.rb` does, you can look to the MQ side of your configuration to find the problem.

View File

@ -9,3 +9,10 @@ lc = LuxController.new(host: CONFIG['lxp']['host'],
datalog: CONFIG['lxp']['datalog'])
lc.charge(false)
lc_slave = LuxController.new(host: CONFIG['lxp']['host_slave'],
port: CONFIG['lxp']['port_slave'],
serial: CONFIG['lxp']['serial_slave'],
datalog: CONFIG['lxp']['datalog_slave'])
lc_slave.charge(false)

View File

@ -9,3 +9,10 @@ lc = LuxController.new(host: CONFIG['lxp']['host'],
datalog: CONFIG['lxp']['datalog'])
lc.charge(true)
lc_slave = LuxController.new(host: CONFIG['lxp']['host_slave'],
port: CONFIG['lxp']['port_slave'],
serial: CONFIG['lxp']['serial_slave'],
datalog: CONFIG['lxp']['datalog_slave'])
lc_slave.charge(true)

View File

@ -9,3 +9,11 @@ lc = LuxController.new(host: CONFIG['lxp']['host'],
datalog: CONFIG['lxp']['datalog'])
lc.discharge_pct = 0
lc_slave = LuxController.new(host: CONFIG['lxp']['host_slave'],
port: CONFIG['lxp']['port_slave'],
serial: CONFIG['lxp']['serial_slave'],
datalog: CONFIG['lxp']['datalog_slave'])
lc_slave.discharge_pct = 0

View File

@ -9,3 +9,12 @@ lc = LuxController.new(host: CONFIG['lxp']['host'],
datalog: CONFIG['lxp']['datalog'])
lc.discharge_pct = 100
lc_slave = LuxController.new(host: CONFIG['lxp']['host_slave'],
port: CONFIG['lxp']['port_slave'],
serial: CONFIG['lxp']['serial_slave'],
datalog: CONFIG['lxp']['datalog_slave'])
lc_slave.discharge_pct = 100