Skip to content

Commit

Permalink
fix(InfraSensing): customize for ENV-THUM
Browse files Browse the repository at this point in the history
  • Loading branch information
anataty committed Dec 15, 2023
1 parent 7d2488f commit 8a9ef0e
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 194 deletions.
5 changes: 3 additions & 2 deletions analog_io_modules/infrasensing_env-thum/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ This [Enapter Device Blueprint](https://go.enapter.com/marketplace-readme) integ
- Create [Enapter Virtual UCM](https://go.enapter.com/handbook-vucm).
- [Upload](https://go.enapter.com/developers-upload-blueprint) this blueprint to Enapter Virtual UCM.
- Use the `Configure` command in the Enapter mobile or Web app to set up the device communication parameters:
- IP address (use either static IP or DHCP reservation);
- Modbus Unit ID
- IP address (default - `192.168.11.160`)
- Modbus Unit ID (default - `1`)

## References

- [Temperature & Humidity Sensor product page](https://go.enapter.com/infrasensing-env-thum)
- [Infrasensing SensorGateway: How it works video](https://go.enapter.com/infrasensing-sensorgateway-video)
- [Modbus TCP manual](https://go.enapter.com/infrasensing-modbus-manual)
154 changes: 76 additions & 78 deletions analog_io_modules/infrasensing_env-thum/firmware.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,19 @@ MODEL = 'ENV-THUM'
-- Configuration variables must be also defined
-- in `write_configuration` command arguments in manifest.yml
IP_ADDRESS_CONFIG = 'ip_address'
MODBUS_PORT_CONFIG = 'modbus_port'
UNIT_ID_CONFIG = 'modbus_unit_id'

-- Initiate device firmware. Called at the end of the file.
function main()
scheduler.add(30000, send_properties)
scheduler.add(1000, send_telemetry)

config.init({
[IP_ADDRESS_CONFIG] = { type = 'string', required = true },
[UNIT_ID_CONFIG] = { type = 'number', required = true },
[IP_ADDRESS_CONFIG] = { type = 'string', required = true, default = '192.168.11.160' },
[UNIT_ID_CONFIG] = { type = 'number', required = true, default = 1 },
[MODBUS_PORT_CONFIG] = { type = 'number', required = true, default = 502 },
})

enapter.register_command_handler('set_threshold', command_set_threshold)
end

function send_properties()
local properties = {}

properties.vendor = VENDOR
properties.model = MODEL

enapter.send_properties(properties)
scheduler.add(1000, send_telemetry)
scheduler.add(30000, send_properties)
end

function send_telemetry()
Expand All @@ -45,44 +36,19 @@ function send_telemetry()
local alerts = {}
local status = 'ok'

for i = 0, 2 do
local data = device:read_inputs(30200 + i * 32, 2)
for i = 0, 3 do
local data = device:read_inputs(200 + i * 32, 2)
if data then
telemetry['value' .. i] = toFloat32(data)
else
status = 'no_data'
end

local data = device:read_input_status(10201 + i * 32, 1)
if data then
telemetry['alarm_down' .. i] = data[1]
table.insert(alerts, 'alarm_down' .. i)
status = 'down'
end

local data = device:read_input_status(10202 + i * 32, 1)
if data then
telemetry['alarm_warn' .. i] = data[1]
table.insert(alerts, 'alarm_warn' .. i)
status = 'warning'
end

local data = device:read_holdings(40200 + i * 32, 2)
if data then
telemetry['thr_high_down' .. i] = toFloat32(data)
end

local data = device:read_holdings(40202 + i * 32, 2)
if data then
telemetry['thr_high_warn' .. i] = toFloat32(data)
end

local data = device:read_holdings(40204 + i * 32, 2)
if data then
telemetry['thr_low_high' .. i] = toFloat32(data)
end

local data = device:read_holdings(40206 + i * 32, 2)
local data = device:read_inputs(201 + i * 32, 1)
if data then
telemetry['thr_low_warn' .. i] = toFloat32(data)
telemetry['type' .. i] = get_type(data[1])
else
status = 'no_data'
end
end

Expand All @@ -91,6 +57,58 @@ function send_telemetry()
enapter.send_telemetry(telemetry)
end

function send_properties()
local properties = {}

properties.vendor = VENDOR
properties.model = MODEL

enapter.send_properties(properties)
end

function get_type(val)
local types = {
[1] = 'Temperature',
[2] = 'Humidity',
[3] = 'Airflow',
[4] = 'Shock',
[5] = 'Dust',
[7] = 'Sound Pressure',
[8] = 'Power Failure Sensor',
[9] = 'Leak',
[10] = 'CO',
[11] = 'Air Pressure',
[12] = 'Security',
[13] = 'Dewpoint',
[14] = 'Fuel Level (Fuel Level Sensor)',
[15] = 'Flow Rate (Fuel Level Sensor)',
[16] = 'Resistance',
[17] = 'TVOC',
[18] = 'CO2',
[19] = 'Motion (EXP4HUB)',
[20] = 'O2',
[21] = 'Light',
[22] = 'CO',
[23] = 'HF',
[24] = 'Volt (AC/DC Power Sensor)',
[25] = 'Amp (AC/DC Power Sensor)',
[26] = 'Watt (AC/DC Power Sensor)',
[27] = 'Watthour (AC/DC Power Sensor)',
[28] = 'Ping',
[29] = 'H2',
[30] = 'Voltage Status',
[31] = 'THD',
[32] = 'Frequency',
[33] = 'Location and Distance',
[34] = 'Tilt (Inclination)',
[35] = 'Particle(PM)',
[36] = 'Radon',
[37] = 'kW (AC Meter3 Power)',
[38] = 'kWh (AC Meter 3 energy)',
}
return types[val]
end

-- Holds global device connection
local device

Expand All @@ -104,46 +122,26 @@ function connect_device()
enapter.log('cannot read config: ' .. tostring(err), 'error')
return nil, 'cannot_read_config'
else
local address, unit_id = values[IP_ADDRESS_CONFIG], values[UNIT_ID_CONFIG]
if not address or not unit_id then
local address, unit_id, port = values[IP_ADDRESS_CONFIG], values[UNIT_ID_CONFIG], values[MODBUS_PORT_CONFIG]
if not address or not unit_id or not port then
return nil, 'not_configured'
else
-- Declare global variable to reuse connection between function calls
device = SensorGwModbusTcp.new(address, tonumber(unit_id))
device = SensorGwModbusTcp.new(address .. ':' .. port, tonumber(unit_id))
device:connect()
return device, nil
end
end
end

function command_set_threshold(ctx, args)
local thresholds = {
['High Down'] = 0,
['High Warn'] = 2,
['Low Down'] = 4,
['Low Warn'] = 6,
}

if device then
local err = device:write_holdings(args.node * 32 + 40200 + thresholds[args.threshold], args.value)
if err == 0 then
return 'Reset command is sent'
else
ctx.error('Command failed, Modbus TCP error: ' .. tostring(err))
end
else
ctx.error('Device connection is not configured')
end
end

function toFloat32(data)
local raw_str = string.pack('BBBB', data[1] >> 8, data[1] & 0xff, data[2] >> 8, data[2] & 0xff)
return string.unpack('>f', raw_str)
end

-------------------------------------------
-- InfraSensing SensorGateway ModbusTCP API
-------------------------------------------
-----------------------------------------------
-- InfraSensing SensorGateway Modbus TCP API --
-----------------------------------------------

SensorGwModbusTcp = {}

Expand All @@ -167,7 +165,7 @@ function SensorGwModbusTcp:read_inputs(address, number)

local registers, err = self.modbus:read_inputs(self.unit_id, address, number, 1000)
if err and err ~= 0 then
enapter.log('Register ' .. tostring(address) .. ' read error: ' .. err, 'error')
enapter.log('Read input register ' .. tostring(address) .. ' read error: ' .. err, 'error')
if err == 1 then
-- Sometimes timeout happens and it may break underlying Modbus client,
-- this is a temporary workaround which manually reconnects.
Expand All @@ -185,7 +183,7 @@ function SensorGwModbusTcp:read_input_status(address, number)

local registers, err = self.modbus:read_discrete_inputs(self.unit_id, address, number, 1000)
if err and err ~= 0 then
enapter.log('Register ' .. tostring(address) .. ' read error: ' .. err, 'error')
enapter.log('Read input status register ' .. tostring(address) .. ' read error: ' .. err, 'error')
if err == 1 then
-- Sometimes timeout happens and it may break underlying Modbus client,
-- this is a temporary workaround which manually reconnects.
Expand All @@ -203,7 +201,7 @@ function SensorGwModbusTcp:read_holdings(address, number)

local registers, err = self.modbus:read_inputs(self.unit_id, address, number, 1000)
if err and err ~= 0 then
enapter.log('Register ' .. tostring(address) .. ' read error: ' .. err, 'error')
enapter.log('Read holding register ' .. tostring(address) .. ' read error: ' .. err, 'error')
if err == 1 then
-- Sometimes timeout happens and it may break underlying Modbus client,
-- this is a temporary workaround which manually reconnects.
Expand All @@ -221,7 +219,7 @@ function SensorGwModbusTcp:write_holdings(address, number)

local err = self.modbus:write_holding(self.unit_id, address, number, 1000)
if err ~= 0 then
enapter.log('Register ' .. tostring(address) .. ' write error: ' .. err, 'error')
enapter.log('Writ holding register ' .. tostring(address) .. ' write error: ' .. err, 'error')
if err == 1 then
-- Sometimes timeout happens and it may break underlying Modbus client,
-- this is a temporary workaround which manually reconnects.
Expand Down
Loading

0 comments on commit 8a9ef0e

Please sign in to comment.