Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(InfraSensing): customize for ENV-THUM #278

Merged
merged 1 commit into from
Jan 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions analog_io_modules/infrasensing_env-thum/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# InfraSensing Temperature & Humidity sensor ENV-THUM

This [Enapter Device Blueprint](https://go.enapter.com/marketplace-readme) integrates **InfraSensing ENV-THUM** - RH sensor created for monitoring temperature and humidity levels indoor - via [Modbus TCP](https://go.enapter.com/developers-modbustcp) implemented on [Enapter Virtual UCM](https://go.enapter.com/handbook-vucm).
This [Enapter Device Blueprint](https://go.enapter.com/marketplace-readme) integrates two **InfraSensing ENV-THUM** RH sensors connected to [InfraSensing SensorGateway](https://go.enapter.com/infrasensing-sensorgateway) using LAN port. They communicate with [Enapter Virtual UCM](https://go.enapter.com/handbook-vucm) via [Modbus TCP](https://go.enapter.com/developers-modbustcp).

## Connect to Enapter

Expand All @@ -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)
186 changes: 99 additions & 87 deletions analog_io_modules/infrasensing_env-thum/firmware.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,21 @@ 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'
NODE_CONFIG = 'node'

-- 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 },
[NODE_CONFIG] = { type = 'number', required = true, default = 7 },
})

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 @@ -41,54 +34,92 @@ function send_telemetry()
return
end

local telemetry = {}
local alerts = {}
local status = 'ok'

for i = 0, 2 do
local data = device:read_inputs(30200 + i * 32, 2)
if data then
telemetry['value' .. i] = toFloat32(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'
local node, err = config.read(NODE_CONFIG)
if err then
enapter.log('cannot read config: ' .. tostring(err), 'error')
enapter.send_telemetry({ status = 'error', alerts = { 'cannot_read_config' } })
else
local telemetry = {}
local alerts = {}
local status = 'ok'

for i = 0, node do
if i == 1 then
goto continue
end

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_inputs(201 + i * 32, 1)
if data then
telemetry['type' .. i] = get_type(data[1])
else
status = 'no_data'
end
::continue::
end

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

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

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

local data = device:read_holdings(40206 + i * 32, 2)
if data then
telemetry['thr_low_warn' .. i] = toFloat32(data)
end
end
enapter.send_properties(properties)
end

telemetry.alerts = alerts
telemetry.status = status
enapter.send_telemetry(telemetry)
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
Expand All @@ -104,46 +135,27 @@ 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 .. ':' .. math.floor(port), tonumber(unit_id))
enapter.log(address .. ':' .. math.floor(port))
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 +179,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 +197,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 +215,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 +233,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
Loading