#14, #15 ppm access fault handling, report modbus exceptions, handle ppm faults in PLC/RTU code

This commit is contained in:
Mikayla Fischler 2022-04-17 21:12:25 -04:00
parent 945b761fc2
commit 2a21d7d0be
5 changed files with 227 additions and 68 deletions

View File

@ -1,4 +1,5 @@
-- #REQUIRES comms.lua -- #REQUIRES comms.lua
-- #REQUIRES ppm.lua
-- Internal Safety System -- Internal Safety System
-- identifies dangerous states and SCRAMs reactor if warranted -- identifies dangerous states and SCRAMs reactor if warranted
@ -19,7 +20,7 @@ function iss_init(reactor)
-- check for critical damage -- check for critical damage
local damage_critical = function () local damage_critical = function ()
local damage_percent = self.reactor.getDamagePercent() local damage_percent = self.reactor.getDamagePercent()
if damage_percent == nil then if damage_percent == ppm.ACCESS_FAULT then
-- lost the peripheral or terminated, handled later -- lost the peripheral or terminated, handled later
log._error("ISS: failed to check reactor damage") log._error("ISS: failed to check reactor damage")
return false return false
@ -31,7 +32,7 @@ function iss_init(reactor)
-- check for heated coolant backup -- check for heated coolant backup
local excess_heated_coolant = function () local excess_heated_coolant = function ()
local hc_needed = self.reactor.getHeatedCoolantNeeded() local hc_needed = self.reactor.getHeatedCoolantNeeded()
if hc_needed == nil then if hc_needed == ppm.ACCESS_FAULT then
-- lost the peripheral or terminated, handled later -- lost the peripheral or terminated, handled later
log._error("ISS: failed to check reactor heated coolant level") log._error("ISS: failed to check reactor heated coolant level")
return false return false
@ -43,7 +44,7 @@ function iss_init(reactor)
-- check for excess waste -- check for excess waste
local excess_waste = function () local excess_waste = function ()
local w_needed = self.reactor.getWasteNeeded() local w_needed = self.reactor.getWasteNeeded()
if w_needed == nil then if w_needed == ppm.ACCESS_FAULT then
-- lost the peripheral or terminated, handled later -- lost the peripheral or terminated, handled later
log._error("ISS: failed to check reactor waste level") log._error("ISS: failed to check reactor waste level")
return false return false
@ -56,7 +57,7 @@ function iss_init(reactor)
local high_temp = function () local high_temp = function ()
-- mekanism: MAX_DAMAGE_TEMPERATURE = 1_200 -- mekanism: MAX_DAMAGE_TEMPERATURE = 1_200
local temp = self.reactor.getTemperature() local temp = self.reactor.getTemperature()
if temp == nil then if temp == ppm.ACCESS_FAULT then
-- lost the peripheral or terminated, handled later -- lost the peripheral or terminated, handled later
log._error("ISS: failed to check reactor temperature") log._error("ISS: failed to check reactor temperature")
return false return false
@ -68,7 +69,7 @@ function iss_init(reactor)
-- check if there is no fuel -- check if there is no fuel
local insufficient_fuel = function () local insufficient_fuel = function ()
local fuel = self.reactor.getFuel() local fuel = self.reactor.getFuel()
if fuel == nil then if fuel == ppm.ACCESS_FAULT then
-- lost the peripheral or terminated, handled later -- lost the peripheral or terminated, handled later
log._error("ISS: failed to check reactor fuel level") log._error("ISS: failed to check reactor fuel level")
return false return false
@ -80,7 +81,7 @@ function iss_init(reactor)
-- check if there is no coolant -- check if there is no coolant
local no_coolant = function () local no_coolant = function ()
local coolant_filled = self.reactor.getCoolantFilledPercentage() local coolant_filled = self.reactor.getCoolantFilledPercentage()
if coolant_filled == nil then if coolant_filled == ppm.ACCESS_FAULT then
-- lost the peripheral or terminated, handled later -- lost the peripheral or terminated, handled later
log._error("ISS: failed to check reactor coolant level") log._error("ISS: failed to check reactor coolant level")
return false return false
@ -126,7 +127,9 @@ function iss_init(reactor)
log._warning("ISS: reactor SCRAM") log._warning("ISS: reactor SCRAM")
self.tripped = true self.tripped = true
self.trip_cause = status self.trip_cause = status
self.reactor.scram() if self.reactor.scram() == ppm.ACCESS_FAULT then
log._error("ISS: failed reactor SCRAM")
end
end end
local first_trip = not was_tripped and self.tripped local first_trip = not was_tripped and self.tripped
@ -293,6 +296,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss)
-- variable reactor status information, excluding heating rate -- variable reactor status information, excluding heating rate
local _reactor_status = function () local _reactor_status = function ()
ppm.clear_fault()
return { return {
status = self.reactor.getStatus(), status = self.reactor.getStatus(),
burn_rate = self.reactor.getBurnRate(), burn_rate = self.reactor.getBurnRate(),
@ -316,17 +320,19 @@ function comms_init(id, modem, local_port, server_port, reactor, iss)
hcool_amnt = self.reactor.getHeatedCoolant()['amount'], hcool_amnt = self.reactor.getHeatedCoolant()['amount'],
hcool_need = self.reactor.getHeatedCoolantNeeded(), hcool_need = self.reactor.getHeatedCoolantNeeded(),
hcool_fill = self.reactor.getHeatedCoolantFilledPercentage() hcool_fill = self.reactor.getHeatedCoolantFilledPercentage()
} }, ppm.faulted()
end end
local _update_status_cache = function () local _update_status_cache = function ()
local status = _reactor_status() local status, faulted = _reactor_status()
local changed = false local changed = false
for key, value in pairs(status) do if not faulted then
if value ~= self.status_cache[key] then for key, value in pairs(status) do
changed = true if value ~= self.status_cache[key] then
break changed = true
break
end
end end
end end
@ -362,6 +368,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss)
-- send structure properties (these should not change) -- send structure properties (these should not change)
-- (server will cache these) -- (server will cache these)
local _send_struct = function () local _send_struct = function ()
ppm.clear_fault()
local mek_data = { local mek_data = {
heat_cap = self.reactor.getHeatCapacity(), heat_cap = self.reactor.getHeatCapacity(),
fuel_asm = self.reactor.getFuelAssemblies(), fuel_asm = self.reactor.getFuelAssemblies(),
@ -373,13 +380,17 @@ function comms_init(id, modem, local_port, server_port, reactor, iss)
max_burn = self.reactor.getMaxBurnRate() max_burn = self.reactor.getMaxBurnRate()
} }
local struct_packet = { if not faulted then
id = self.id, local struct_packet = {
type = RPLC_TYPES.MEK_STRUCT, id = self.id,
mek_data = mek_data type = RPLC_TYPES.MEK_STRUCT,
} mek_data = mek_data
}
_send(struct_packet) _send(struct_packet)
else
log._error("failed to send structure: PPM fault")
end
end end
local _send_iss_status = function () local _send_iss_status = function ()
@ -407,6 +418,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss)
-- reconnect a newly connected reactor -- reconnect a newly connected reactor
local reconnect_reactor = function (reactor) local reconnect_reactor = function (reactor)
self.reactor = reactor self.reactor = reactor
_update_status_cache()
end end
-- parse an RPLC packet -- parse an RPLC packet
@ -486,25 +498,25 @@ function comms_init(id, modem, local_port, server_port, reactor, iss)
-- disable the reactor -- disable the reactor
self.scrammed = true self.scrammed = true
plc_state.scram = true plc_state.scram = true
_send_ack(packet.type, self.reactor.scram()) _send_ack(packet.type, self.reactor.scram() == ppm.ACCESS_OK)
elseif packet.type == RPLC_TYPES.MEK_ENABLE then elseif packet.type == RPLC_TYPES.MEK_ENABLE then
-- enable the reactor -- enable the reactor
self.scrammed = false self.scrammed = false
plc_state.scram = false plc_state.scram = false
_send_ack(packet.type, self.reactor.activate()) _send_ack(packet.type, self.reactor.activate() == ppm.ACCESS_OK)
elseif packet.type == RPLC_TYPES.MEK_BURN_RATE then elseif packet.type == RPLC_TYPES.MEK_BURN_RATE then
-- set the burn rate -- set the burn rate
local burn_rate = packet.data[1] local burn_rate = packet.data[1]
local max_burn_rate = self.reactor.getMaxBurnRate() local max_burn_rate = self.reactor.getMaxBurnRate()
local success = false local success = false
if max_burn_rate ~= nil then if max_burn_rate ~= ppm.ACCESS_FAULT then
if burn_rate > 0 and burn_rate <= max_burn_rate then if burn_rate > 0 and burn_rate <= max_burn_rate then
success = self.reactor.setBurnRate(burn_rate) success = self.reactor.setBurnRate(burn_rate)
end end
end end
_send_ack(packet.type, success) _send_ack(packet.type, success == ppm.ACCESS_OK)
elseif packet.type == RPLC_TYPES.ISS_GET then elseif packet.type == RPLC_TYPES.ISS_GET then
-- get the ISS status -- get the ISS status
_send_iss_status(iss.status()) _send_iss_status(iss.status())
@ -540,9 +552,10 @@ function comms_init(id, modem, local_port, server_port, reactor, iss)
self.linked = link_ack == RPLC_LINKING.ALLOW self.linked = link_ack == RPLC_LINKING.ALLOW
else else
log._("discarding non-link packet before linked") log._debug("discarding non-link packet before linked")
end end
elseif packet.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then elseif packet.scada_frame.protocol() == PROTOCOLS.SCADA_MGMT then
-- todo
end end
end end
end end
@ -559,7 +572,8 @@ function comms_init(id, modem, local_port, server_port, reactor, iss)
-- send live status information -- send live status information
-- overridden : if ISS force disabled reactor -- overridden : if ISS force disabled reactor
local send_status = function (overridden) -- degraded : if PLC status is degraded
local send_status = function (overridden, degraded)
local mek_data = nil local mek_data = nil
if _update_status_cache() then if _update_status_cache() then
@ -572,6 +586,7 @@ function comms_init(id, modem, local_port, server_port, reactor, iss)
timestamp = os.time(), timestamp = os.time(),
control_state = not self.scrammed, control_state = not self.scrammed,
overridden = overridden, overridden = overridden,
degraded = degraded,
heating_rate = self.reactor.getHeatingRate(), heating_rate = self.reactor.getHeatingRate(),
mek_data = mek_data mek_data = mek_data
} }

View File

@ -10,7 +10,7 @@ os.loadAPI("scada-common/comms.lua")
os.loadAPI("config.lua") os.loadAPI("config.lua")
os.loadAPI("plc.lua") os.loadAPI("plc.lua")
local R_PLC_VERSION = "alpha-v0.1.4" local R_PLC_VERSION = "alpha-v0.1.5"
local print = util.print local print = util.print
local println = util.println local println = util.println
@ -243,7 +243,7 @@ while true do
if plc_comms.is_linked() then if plc_comms.is_linked() then
if ticks_to_update <= 0 then if ticks_to_update <= 0 then
plc_comms.send_status(iss_tripped) plc_comms.send_status(iss_tripped, plc_state.degraded)
ticks_to_update = UPDATE_TICKS ticks_to_update = UPDATE_TICKS
end end
else else
@ -275,7 +275,7 @@ while true do
-- safe exit -- safe exit
if plc_state.init_ok then if plc_state.init_ok then
plc_state.scram = true plc_state.scram = true
if reactor.scram() then if reactor.scram() ~= ppm.ACCESS_FAULT then
println_ts("reactor disabled") println_ts("reactor disabled")
else else
-- send an alarm: plc_comms.send_alarm(ALARMS.PLC_LOST_CONTROL) ? -- send an alarm: plc_comms.send_alarm(ALARMS.PLC_LOST_CONTROL) ?

View File

@ -1,5 +1,6 @@
-- #REQUIRES comms.lua -- #REQUIRES comms.lua
-- #REQUIRES modbus.lua -- #REQUIRES modbus.lua
-- #REQUIRES ppm.lua
function rtu_init() function rtu_init()
local self = { local self = {
@ -10,68 +11,91 @@ function rtu_init()
io_count_cache = { 0, 0, 0, 0 } io_count_cache = { 0, 0, 0, 0 }
} }
local __count_io = function () local _count_io = function ()
self.io_count_cache = { #self.discrete_inputs, #self.coils, #self.input_regs, #self.holding_regs } self.io_count_cache = { #self.discrete_inputs, #self.coils, #self.input_regs, #self.holding_regs }
end end
-- return : IO count table
local io_count = function () local io_count = function ()
return self.io_count_cache[0], self.io_count_cache[1], self.io_count_cache[2], self.io_count_cache[3] return self.io_count_cache[0], self.io_count_cache[1], self.io_count_cache[2], self.io_count_cache[3]
end end
-- discrete inputs: single bit read-only -- discrete inputs: single bit read-only
-- return : count of discrete inputs
local connect_di = function (f) local connect_di = function (f)
table.insert(self.discrete_inputs, f) table.insert(self.discrete_inputs, f)
__count_io() _count_io()
return #self.discrete_inputs return #self.discrete_inputs
end end
-- return : value, access fault
local read_di = function (di_addr) local read_di = function (di_addr)
return self.discrete_inputs[di_addr]() ppm.clear_fault()
local value = self.discrete_inputs[di_addr]()
return value, ppm.is_faulted()
end end
-- coils: single bit read-write -- coils: single bit read-write
-- return : count of coils
local connect_coil = function (f_read, f_write) local connect_coil = function (f_read, f_write)
table.insert(self.coils, { read = f_read, write = f_write }) table.insert(self.coils, { read = f_read, write = f_write })
__count_io() _count_io()
return #self.coils return #self.coils
end end
-- return : value, access fault
local read_coil = function (coil_addr) local read_coil = function (coil_addr)
return self.coils[coil_addr].read() ppm.clear_fault()
local value = self.coils[coil_addr].read()
return value, ppm.is_faulted()
end end
-- return : access fault
local write_coil = function (coil_addr, value) local write_coil = function (coil_addr, value)
ppm.clear_fault()
self.coils[coil_addr].write(value) self.coils[coil_addr].write(value)
return ppm.is_faulted()
end end
-- input registers: multi-bit read-only -- input registers: multi-bit read-only
-- return : count of input registers
local connect_input_reg = function (f) local connect_input_reg = function (f)
table.insert(self.input_regs, f) table.insert(self.input_regs, f)
__count_io() _count_io()
return #self.input_regs return #self.input_regs
end end
-- return : value, access fault
local read_input_reg = function (reg_addr) local read_input_reg = function (reg_addr)
return self.coils[reg_addr]() ppm.clear_fault()
local value = self.coils[reg_addr]()
return value, ppm.is_faulted()
end end
-- holding registers: multi-bit read-write -- holding registers: multi-bit read-write
-- return : count of holding registers
local connect_holding_reg = function (f_read, f_write) local connect_holding_reg = function (f_read, f_write)
table.insert(self.holding_regs, { read = f_read, write = f_write }) table.insert(self.holding_regs, { read = f_read, write = f_write })
__count_io() _count_io()
return #self.holding_regs return #self.holding_regs
end end
-- return : value, access fault
local read_holding_reg = function (reg_addr) local read_holding_reg = function (reg_addr)
return self.coils[reg_addr].read() ppm.clear_fault()
local value = self.coils[reg_addr].read()
return value, ppm.is_faulted()
end end
-- return : access fault
local write_holding_reg = function (reg_addr, value) local write_holding_reg = function (reg_addr, value)
ppm.clear_fault()
self.coils[reg_addr].write(value) self.coils[reg_addr].write(value)
return ppm.is_faulted()
end end
return { return {

View File

@ -11,6 +11,20 @@ local MODBUS_FCODE = {
ERROR_FLAG = 0x80 ERROR_FLAG = 0x80
} }
-- modbus exception codes
local MODBUS_EXCODE = {
ILLEGAL_FUNCTION = 0x01,
ILLEGAL_DATA_ADDR = 0x02,
ILLEGAL_DATA_VALUE = 0x03,
SERVER_DEVICE_FAIL = 0x04,
ACKNOWLEDGE = 0x05,
SERVER_DEVICE_BUSY = 0x06,
NEG_ACKNOWLEDGE = 0x07,
MEMORY_PARITY_ERROR = 0x08,
GATEWAY_PATH_UNAVAILABLE = 0x0A,
GATEWAY_TARGET_TIMEOUT = 0x0B
}
-- new modbus comms handler object -- new modbus comms handler object
function modbus_init(rtu_dev) function modbus_init(rtu_dev)
local self = { local self = {
@ -19,13 +33,22 @@ function modbus_init(rtu_dev)
local _1_read_coils = function (c_addr_start, count) local _1_read_coils = function (c_addr_start, count)
local readings = {} local readings = {}
local access_fault = false
local _, coils, _, _ = self.rtu.io_count() local _, coils, _, _ = self.rtu.io_count()
local return_ok = (c_addr_start + count) <= coils local return_ok = (c_addr_start + count) <= coils
if return_ok then if return_ok then
for i = 0, (count - 1) do for i = 0, (count - 1) do
readings[i] = self.rtu.read_coil(c_addr_start + i) readings[i], access_fault = self.rtu.read_coil(c_addr_start + i)
if access_fault then
return_ok = false
readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL
break
end
end end
else
readings = MODBUS_EXCODE.ILLEGAL_DATA_ADDR
end end
return return_ok, readings return return_ok, readings
@ -33,13 +56,22 @@ function modbus_init(rtu_dev)
local _2_read_discrete_inputs = function (di_addr_start, count) local _2_read_discrete_inputs = function (di_addr_start, count)
local readings = {} local readings = {}
local access_fault = false
local discrete_inputs, _, _, _ = self.rtu.io_count() local discrete_inputs, _, _, _ = self.rtu.io_count()
local return_ok = (di_addr_start + count) <= discrete_inputs local return_ok = (di_addr_start + count) <= discrete_inputs
if return_ok then if return_ok then
for i = 0, (count - 1) do for i = 0, (count - 1) do
readings[i] = self.rtu.read_di(di_addr_start + i) readings[i], access_fault = self.rtu.read_di(di_addr_start + i)
if access_fault then
return_ok = false
readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL
break
end
end end
else
readings = MODBUS_EXCODE.ILLEGAL_DATA_ADDR
end end
return return_ok, readings return return_ok, readings
@ -47,13 +79,22 @@ function modbus_init(rtu_dev)
local _3_read_multiple_holding_registers = function (hr_addr_start, count) local _3_read_multiple_holding_registers = function (hr_addr_start, count)
local readings = {} local readings = {}
local access_fault = false
local _, _, _, hold_regs = self.rtu.io_count() local _, _, _, hold_regs = self.rtu.io_count()
local return_ok = (hr_addr_start + count) <= hold_regs local return_ok = (hr_addr_start + count) <= hold_regs
if return_ok then if return_ok then
for i = 0, (count - 1) do for i = 0, (count - 1) do
readings[i] = self.rtu.read_holding_reg(hr_addr_start + i) readings[i], access_fault = self.rtu.read_holding_reg(hr_addr_start + i)
if access_fault then
return_ok = false
readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL
break
end
end end
else
readings = MODBUS_EXCODE.ILLEGAL_DATA_ADDR
end end
return return_ok, readings return return_ok, readings
@ -61,93 +102,132 @@ function modbus_init(rtu_dev)
local _4_read_input_registers = function (ir_addr_start, count) local _4_read_input_registers = function (ir_addr_start, count)
local readings = {} local readings = {}
local access_fault = false
local _, _, input_regs, _ = self.rtu.io_count() local _, _, input_regs, _ = self.rtu.io_count()
local return_ok = (ir_addr_start + count) <= input_regs local return_ok = (ir_addr_start + count) <= input_regs
if return_ok then if return_ok then
for i = 0, (count - 1) do for i = 0, (count - 1) do
readings[i] = self.rtu.read_input_reg(ir_addr_start + i) readings[i], access_fault = self.rtu.read_input_reg(ir_addr_start + i)
if access_fault then
return_ok = false
readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL
break
end
end end
else
readings = MODBUS_EXCODE.ILLEGAL_DATA_ADDR
end end
return return_ok, readings return return_ok, readings
end end
local _5_write_single_coil = function (c_addr, value) local _5_write_single_coil = function (c_addr, value)
local response = nil
local _, coils, _, _ = self.rtu.io_count() local _, coils, _, _ = self.rtu.io_count()
local return_ok = c_addr <= coils local return_ok = c_addr <= coils
if return_ok then if return_ok then
self.rtu.write_coil(c_addr, value) local access_fault = self.rtu.write_coil(c_addr, value)
if access_fault then
return_ok = false
readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL
end
else
response = MODBUS_EXCODE.ILLEGAL_DATA_ADDR
end end
return return_ok return return_ok, response
end end
local _6_write_single_holding_register = function (hr_addr, value) local _6_write_single_holding_register = function (hr_addr, value)
local response = nil
local _, _, _, hold_regs = self.rtu.io_count() local _, _, _, hold_regs = self.rtu.io_count()
local return_ok = hr_addr <= hold_regs local return_ok = hr_addr <= hold_regs
if return_ok then if return_ok then
self.rtu.write_holding_reg(hr_addr, value) local access_fault = self.rtu.write_holding_reg(hr_addr, value)
if access_fault then
return_ok = false
readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL
end
end end
return return_ok return return_ok
end end
local _15_write_multiple_coils = function (c_addr_start, values) local _15_write_multiple_coils = function (c_addr_start, values)
local response = nil
local _, coils, _, _ = self.rtu.io_count() local _, coils, _, _ = self.rtu.io_count()
local count = #values local count = #values
local return_ok = (c_addr_start + count) <= coils local return_ok = (c_addr_start + count) <= coils
if return_ok then if return_ok then
for i = 0, (count - 1) do for i = 0, (count - 1) do
self.rtu.write_coil(c_addr_start + i, values[i + 1]) local access_fault = self.rtu.write_coil(c_addr_start + i, values[i + 1])
if access_fault then
return_ok = false
readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL
break
end
end end
end end
return return_ok return return_ok, response
end end
local _16_write_multiple_holding_registers = function (hr_addr_start, values) local _16_write_multiple_holding_registers = function (hr_addr_start, values)
local response = nil
local _, _, _, hold_regs = self.rtu.io_count() local _, _, _, hold_regs = self.rtu.io_count()
local count = #values local count = #values
local return_ok = (hr_addr_start + count) <= hold_regs local return_ok = (hr_addr_start + count) <= hold_regs
if return_ok then if return_ok then
for i = 0, (count - 1) do for i = 0, (count - 1) do
self.rtu.write_coil(hr_addr_start + i, values[i + 1]) local access_fault = self.rtu.write_coil(hr_addr_start + i, values[i + 1])
if access_fault then
return_ok = false
readings = MODBUS_EXCODE.SERVER_DEVICE_FAIL
break
end
end end
end end
return return_ok return return_ok, response
end end
local handle_packet = function (packet) local handle_packet = function (packet)
local return_code = true local return_code = true
local readings = nil local response = nil
local reply = packet
if #packet.data == 2 then if #packet.data == 2 then
-- handle by function code -- handle by function code
if packet.func_code == MODBUS_FCODE.READ_COILS then if packet.func_code == MODBUS_FCODE.READ_COILS then
return_code, readings = _1_read_coils(packet.data[1], packet.data[2]) return_code, response = _1_read_coils(packet.data[1], packet.data[2])
elseif packet.func_code == MODBUS_FCODE.READ_DISCRETE_INPUTS then elseif packet.func_code == MODBUS_FCODE.READ_DISCRETE_INPUTS then
return_code, readings = _2_read_discrete_inputs(packet.data[1], packet.data[2]) return_code, response = _2_read_discrete_inputs(packet.data[1], packet.data[2])
elseif packet.func_code == MODBUS_FCODE.READ_MUL_HOLD_REGS then elseif packet.func_code == MODBUS_FCODE.READ_MUL_HOLD_REGS then
return_code, readings = _3_read_multiple_holding_registers(packet.data[1], packet.data[2]) return_code, response = _3_read_multiple_holding_registers(packet.data[1], packet.data[2])
elseif packet.func_code == MODBUS_FCODE.READ_INPUT_REGISTERS then elseif packet.func_code == MODBUS_FCODE.READ_INPUT_REGISTERS then
return_code, readings = _4_read_input_registers(packet.data[1], packet.data[2]) return_code, response = _4_read_input_registers(packet.data[1], packet.data[2])
elseif packet.func_code == MODBUS_FCODE.WRITE_SINGLE_COIL then elseif packet.func_code == MODBUS_FCODE.WRITE_SINGLE_COIL then
return_code = _5_write_single_coil(packet.data[1], packet.data[2]) return_code, response = _5_write_single_coil(packet.data[1], packet.data[2])
elseif packet.func_code == MODBUS_FCODE.WRITE_SINGLE_HOLD_REG then elseif packet.func_code == MODBUS_FCODE.WRITE_SINGLE_HOLD_REG then
return_code = _6_write_single_holding_register(packet.data[1], packet.data[2]) return_code, response = _6_write_single_holding_register(packet.data[1], packet.data[2])
elseif packet.func_code == MODBUS_FCODE.WRITE_MUL_COILS then elseif packet.func_code == MODBUS_FCODE.WRITE_MUL_COILS then
return_code = _15_write_multiple_coils(packet.data[1], packet.data[2]) return_code, response = _15_write_multiple_coils(packet.data[1], packet.data[2])
elseif packet.func_code == MODBUS_FCODE.WRITE_MUL_HOLD_REGS then elseif packet.func_code == MODBUS_FCODE.WRITE_MUL_HOLD_REGS then
return_code = _16_write_multiple_holding_registers(packet.data[1], packet.data[2]) return_code, response = _16_write_multiple_holding_registers(packet.data[1], packet.data[2])
else else
-- unknown function -- unknown function
return_code = false return_code = false
response = MODBUS_EXCODE.ILLEGAL_FUNCTION
end end
else else
-- invalid length -- invalid length
@ -155,19 +235,28 @@ function modbus_init(rtu_dev)
end end
if return_code then if return_code then
-- response (default is to echo back) -- default is to echo back
response = packet if type(response) == "table" then
if readings ~= nil then reply.length = #response
response.length = #readings reply.data = response
response.data = readings
end end
else else
-- echo back with error flag -- echo back with error flag
response = packet reply.func_code = bit.bor(packet.func_code, MODBUS_FCODE.ERROR_FLAG)
response.func_code = bit.bor(packet.func_code, ERROR_FLAG)
if type(response) == "nil" then
reply.length = 0
reply.data = {}
elseif type(response) == "number" then
reply.length = 1
reply.data = { response }
elseif type(response) == "table" then
reply.length = #response
reply.data = response
end
end end
return return_code, response return return_code, reply
end end
return { return {

View File

@ -4,12 +4,17 @@
-- Protected Peripheral Manager -- Protected Peripheral Manager
-- --
ACCESS_OK = true
ACCESS_FAULT = nil
---------------------------- ----------------------------
-- PRIVATE DATA/FUNCTIONS -- -- PRIVATE DATA/FUNCTIONS --
---------------------------- ----------------------------
local self = { local self = {
mounts = {}, mounts = {},
auto_cf = false,
faulted = false,
mute = false mute = false
} }
@ -21,18 +26,22 @@ local peri_init = function (device)
local status, result = pcall(func, ...) local status, result = pcall(func, ...)
if status then if status then
-- auto fault clear
if self.auto_cf then self.faulted = false end
-- assume nil is only for functions with no return, so return status -- assume nil is only for functions with no return, so return status
if result == nil then if result == nil then
return true return ACCESS_OK
else else
return result return result
end end
else else
-- function failed -- function failed
self.faulted = true
if not mute then if not mute then
log._error("PPM: protected " .. key .. "() -> " .. result) log._error("PPM: protected " .. key .. "() -> " .. result)
end end
return nil return ACCESS_FAULT
end end
end end
end end
@ -54,6 +63,28 @@ function enable_reporting()
self.mute = false self.mute = false
end end
-- FAULT MEMORY --
-- enable automatically clearing fault flag
function enable_afc()
self.auto_cf = true
end
-- disable automatically clearing fault flag
function disable_afc()
self.auto_cf = false
end
-- check fault flag
function is_faulted()
return self.faulted
end
-- clear fault flag
function clear_fault()
self.faulted = false
end
-- MOUNTING -- -- MOUNTING --
-- mount all available peripherals (clears mounts first) -- mount all available peripherals (clears mounts first)