summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Pointner <equinox@spreadspace.org>2021-09-19 20:20:41 +0200
committerChristian Pointner <equinox@spreadspace.org>2021-09-19 20:20:41 +0200
commit72ce00fc5b382eef8ce7b5596230bdef369e4cfd (patch)
treee8d4000874bd9e76dd273724bd8990a8cdde9a07
parentimproved sensors initialization (diff)
add sensors to lua prometheus exporter
-rwxr-xr-xfiles/common/openwrt/sensors-read.lua15
-rw-r--r--files/common/openwrt/sensors.module_lua227
-rw-r--r--files/common/openwrt/sensors_promethues-node-exporter.lua26
-rw-r--r--inventory/group_vars/chaos-at-home-sensors/vars.yml5
-rw-r--r--inventory/host_vars/ch-sensors1.yml12
5 files changed, 258 insertions, 27 deletions
diff --git a/files/common/openwrt/sensors-read.lua b/files/common/openwrt/sensors-read.lua
new file mode 100755
index 00000000..e441b9ea
--- /dev/null
+++ b/files/common/openwrt/sensors-read.lua
@@ -0,0 +1,15 @@
+#!/usr/bin/lua
+
+local sensors = require "sensors"
+
+local config, err = sensors.read_config('/etc/sensors.json')
+if not config then error(err) end
+
+local readings, err = sensors.read(config)
+if not readings then error(err) end
+for name, values in pairs(readings) do
+ print(name .. ":")
+ for t, v in pairs(values) do
+ print(" * " .. t .. " = " .. v)
+ end
+end
diff --git a/files/common/openwrt/sensors.module_lua b/files/common/openwrt/sensors.module_lua
index f6c3e38c..0767733d 100644
--- a/files/common/openwrt/sensors.module_lua
+++ b/files/common/openwrt/sensors.module_lua
@@ -1,23 +1,24 @@
local base = _G
-local io = require("io")
-local string = require("string")
local cjson = require "cjson.safe"
local glob = require "posix.glob"
+local io = require("io")
+local string = require("string")
local time = require "posix.time"
local unistd = require "posix.unistd"
local _M = {}
local _internal_ = {}
_internal_.setup = {}
+_internal_.read = {}
--- ############# i2c ###########
+-- ############# utils ###########
function _internal_.write_to_file(path, data)
-- base.print(string.format("writing '%s' to '%s'", data, path))
local f, err = io.open(path, "w")
if not f then return nil, err end
- ret, err = f:write(data)
+ local ret, err = f:write(data)
f:close()
return ret, err
end
@@ -26,7 +27,7 @@ function _internal_.read_from_file(path)
local f, err = io.open(path, "r")
if not f then return nil, err end
- data, err = f:read("*a")
+ local data, err = f:read("*a")
f:close()
return data, err
end
@@ -38,7 +39,7 @@ function _M.i2c_bus_path(bus)
end
function _M.i2c_device_name(bus, address)
- return string.format("%d-%04X", bus, address)
+ return string.format("%d-%04x", bus, address)
end
function _M.i2c_device_path(bus, address)
@@ -46,11 +47,11 @@ function _M.i2c_device_path(bus, address)
end
function _M.i2c_add_device(bus, address, name)
- return _internal_.write_to_file(_M.i2c_bus_path(bus) .. "/new_device", string.format("%s 0x%02X", name, address))
+ return _internal_.write_to_file(_M.i2c_bus_path(bus) .. "/new_device", string.format("%s 0x%02x", name, address))
end
function _M.i2c_delete_device(bus, address)
- return _internal_.write_to_file(_M.i2c_bus_path(bus) .. "/delete_device", string.format("0x%02X", address))
+ return _internal_.write_to_file(_M.i2c_bus_path(bus) .. "/delete_device", string.format("0x%02x", address))
end
function _M.i2c_get_mux_channels(parent, address)
@@ -72,23 +73,24 @@ function _M.i2c_get_mux_channels(parent, address)
local bus = string.match(bus_path, '/i2c%-(%d+)$')
if not bus then return nil, "unable to parse bus number from path: " .. bus_path end
- channels[tonumber(channel)] = tonumber(bus)
+ channels[base.tonumber(channel)] = base.tonumber(bus)
end
return channels
end
+
function _M.i2c_add_devices_recursive(bus, devices)
local num_devices = 0
local device
for _, device in base.ipairs(devices) do
- -- base.print(string.format("i2c_add_device(%d, 0x%02X, %s)", bus, device.address, device.driver))
- _M.i2c_add_device(bus, device.address, device.driver)
+ _M.i2c_add_device(bus, device.address, device.type)
num_devices = num_devices + 1
- local setup_function = _internal_.setup[device.driver]
+ local setup_function = _internal_.setup[device.type]
if setup_function ~= nil then
time.nanosleep({tv_sec = 0, tv_nsec = 100000000})
- setup_function(bus, device.address)
+ local ret, err = setup_function(bus, device.address)
+ if not ret then return nil, err end
end
if device.channels then
@@ -108,21 +110,87 @@ function _M.i2c_add_devices_recursive(bus, devices)
return num_devices
end
+function _M.i2c_read_sensors_recursive(bus, devices)
+ local readings = {}
+ local device
+ for _, device in base.ipairs(devices) do
+ local name = device.name ~= nil and device.name or device.type .. "_" .. _M.i2c_device_name(bus, device.address)
+ local read_function = _internal_.read[device.type]
+ if read_function ~= nil then
+ local values, kind = read_function(bus, device.address)
+ if values ~= nil then
+ readings[name] = values
+ readings[name]['_kind_'] = kind
+ end
+ end
+
+ if device.channels then
+ local mux_channels, err = _M.i2c_get_mux_channels(bus, device.address)
+ if not mux_channels then return nil, error end
+ local channel
+ for _, channel in base.ipairs(device.channels) do
+ if mux_channels[channel.number] then
+ local tmp, err = _M.i2c_read_sensors_recursive(mux_channels[channel.number], channel.devices)
+ if tmp ~= nil then for k,v in base.pairs(tmp) do readings[k] = v end end
+ end
+ end
+ end
+ end
+
+ return readings
+end
+
--- ############# II0 ###########
+
+-- ############# 1-wire ###########
+
+function _M.w1_device_path(address)
+ return string.format("/sys/bus/w1/devices/" .. address)
+end
+
+function _M.w1_device_family_and_serial(address)
+ local family, serial = string.match(address, '^(%d+)%-(%d+)$')
+ if not family then return nil, "unable to extract device family and serial from 1-wire address " .. address end
+ return family, serial
+end
+
+function _M.w1_read_sensors(devices)
+ local readings = {}
+ local device
+ for _, device in base.ipairs(devices) do
+ local name = device.name ~= nil and device.name or device.address
+ local family, _ = _M.w1_device_family_and_serial(device.address)
+ if family ~= nil then
+ local read_function = _internal_.read["w1-" .. family]
+ if read_function ~= nil then
+ local values, kind = read_function(device.address)
+ if values ~= nil then
+ readings[name] = values
+ readings[name]['_kind_'] = kind
+ end
+ end
+ end
+ end
+
+ return readings
+end
+
+
+-- ############# iio ###########
function _M.iio_device_path_from_i2c_device(bus, address)
local device_paths, glob_result = glob.glob(_M.i2c_device_path(bus, address) .. "/iio:device*", 0)
if not device_paths then
- if glob_result == glob.GLOB_NOMATCH then return nil, "couldn't find IIO device handle for i2c device " .. _M.i2c_device_name(bus, address) end
+ if glob_result == glob.GLOB_NOMATCH then return nil, "couldn't find iio device handle for i2c device " .. _M.i2c_device_name(bus, address) end
if glob_result == glob.GLOB_ABORTED then return nil, "glob(): aborted" end
if glob_result == glob.GLOB_NOSPACE then return nil, "glob(): no space" end
return nil, "glob(): unknown error"
end
- if #device_paths ~= 1 then return nil, "found more than one IIO device for i2c device " .. _M.i2c_device_name(bus, address) end
+ if #device_paths ~= 1 then return nil, "found more than one iio device for i2c device " .. _M.i2c_device_name(bus, address) end
return device_paths[1]
end
+
function _M.iio_setup_bmp280(bus, address)
local iio_device, err = _M.iio_device_path_from_i2c_device(bus, address)
if not iio_device then return nil, err end
@@ -133,6 +201,27 @@ function _M.iio_setup_bmp280(bus, address)
end
_internal_.setup['bmp280'] = _M.iio_setup_bmp280
+function _M.iio_read_bmp280(bus, address)
+ local iio_device, err = _M.iio_device_path_from_i2c_device(bus, address)
+ if not iio_device then return end
+
+ local values = {}
+ local tmp, err = _internal_.read_from_file(iio_device .. '/in_pressure_input')
+ if tmp ~= nil then
+ local val = base.tonumber(tmp)
+ if val ~= nil then values['pressure'] = val/100 end
+ end
+ tmp, err = _internal_.read_from_file(iio_device .. '/in_temp_input')
+ if tmp ~= nil then
+ local val = base.tonumber(tmp)
+ if val ~= nil then values['temperature'] = val/1000 end
+ end
+
+ return values, 'bmp280'
+end
+_internal_.read['bmp280'] = _M.iio_read_bmp280
+
+
function _M.iio_setup_bme280(bus, address)
local iio_device, err = _M.iio_device_path_from_i2c_device(bus, address)
if not iio_device then return nil, err end
@@ -145,8 +234,91 @@ function _M.iio_setup_bme280(bus, address)
end
_internal_.setup['bme280'] = _M.iio_setup_bme280
+function _M.iio_read_bme280(bus, address)
+ local iio_device, err = _M.iio_device_path_from_i2c_device(bus, address)
+ if not iio_device then return end
+
+ local values = {}
+ local tmp, err = _internal_.read_from_file(iio_device .. '/in_humidityrelative_input')
+ if tmp ~= nil then
+ local val = base.tonumber(tmp)
+ if val ~= nil then values['humidity'] = val/1000 end
+ end
+ local tmp, err = _internal_.read_from_file(iio_device .. '/in_pressure_input')
+ if tmp ~= nil then
+ local val = base.tonumber(tmp)
+ if val ~= nil then values['pressure'] = val/100 end
+ end
+ tmp, err = _internal_.read_from_file(iio_device .. '/in_temp_input')
+ if tmp ~= nil then
+ local val = base.tonumber(tmp)
+ if val ~= nil then values['temperature'] = val/1000 end
+ end
+
+ return values, 'bme280'
+end
+_internal_.read['bme280'] = _M.iio_read_bme280
+
+
+function _M.iio_read_am2315(bus, address)
+ local iio_device, err = _M.iio_device_path_from_i2c_device(bus, address)
+ if not iio_device then return end
+
+ local values = {}
+ local tmp, err = _internal_.read_from_file(iio_device .. '/in_humidityrelative_raw')
+ if tmp ~= nil then
+ local val = base.tonumber(tmp)
+ if val ~= nil then values['humidity'] = val/10 end
+ end
+ time.nanosleep({tv_sec = 0, tv_nsec = 100000000})
+ tmp, err = _internal_.read_from_file(iio_device .. '/in_temp_raw')
+ if tmp ~= nil then
+ local val = base.tonumber(tmp)
+ if val ~= nil then values['temperature'] = val/10 end
+ end
+
+ return values, 'am2315'
+end
+_internal_.read['am2315'] = _M.iio_read_am2315
--- ############# config ###########
+
+
+-- ############# hwmon ###########
+
+function _M.hwmon_device_path_from_w1_device(address)
+ local device_paths, glob_result = glob.glob(_M.w1_device_path(address) .. "/hwmon/hwmon*", 0)
+ if not device_paths then
+ if glob_result == glob.GLOB_NOMATCH then return nil, "couldn't find hwmon device handle for 1-wire device " .. address end
+ if glob_result == glob.GLOB_ABORTED then return nil, "glob(): aborted" end
+ if glob_result == glob.GLOB_NOSPACE then return nil, "glob(): no space" end
+ return nil, "glob(): unknown error"
+ end
+ if #device_paths ~= 1 then return nil, "found more than one hwmon device for 1-wire device " .. address end
+ return device_paths[1]
+end
+
+function _M.hwmon_read_w1_therm(address)
+ local hwmon_device, err = _M.hwmon_device_path_from_w1_device(address)
+ if not hwmon_device then return end
+
+ local values = {}
+ local tmp, err = _internal_.read_from_file(hwmon_device .. '/temp1_input')
+ if tmp ~= nil then
+ local val = base.tonumber(tmp)
+ if val ~= nil then values['temperature'] = val/1000 end
+ end
+
+ return values
+end
+_internal_.read['w1-10'] = function(address) return _M.hwmon_read_w1_therm(address), "ds18s20" end
+_internal_.read['w1-22'] = function(address) return _M.hwmon_read_w1_therm(address), "ds1822" end
+_internal_.read['w1-28'] = function(address) return _M.hwmon_read_w1_therm(address), "ds18b20" end
+_internal_.read['w1-3B'] = function(address) return _M.hwmon_read_w1_therm(address), "ds1825" end
+_internal_.read['w1-42'] = function(address) return _M.hwmon_read_w1_therm(address), "ds28ea00" end
+
+
+
+-- ############# high-level interface ###########
function _M.read_config(path)
sensors_json, err = _internal_.read_from_file(path)
@@ -159,12 +331,31 @@ function _M.setup(config)
local num_devices = 0
if config.i2c then
local i2c_bus
- for _, i2c_bus in ipairs(config.i2c) do
+ for _, i2c_bus in base.ipairs(config.i2c) do
local tmp, err = _M.i2c_add_devices_recursive(i2c_bus.number, i2c_bus.devices)
if not tmp then return nil, err end
num_devices = num_devices + tmp
end
end
+ -- for now the only supported 1-wire master device is i2c based and will be initialized above
+ -- also sensors on 1-wire busses are discvored automatically and for now no supported sensor needs any setup
+end
+
+function _M.read(config)
+ local readings = {}
+ if config.i2c then
+ local i2c_bus
+ for _, i2c_bus in base.ipairs(config.i2c) do
+ local tmp, err = _M.i2c_read_sensors_recursive(i2c_bus.number, i2c_bus.devices)
+ if tmp ~= nil then for k,v in base.pairs(tmp) do readings[k] = v end end
+ end
+ end
+ if config.w1 then
+ local tmp, err = _M.w1_read_sensors(config.w1)
+ if tmp ~= nil then for k,v in base.pairs(tmp) do readings[k] = v end end
+ end
+
+ return readings
end
diff --git a/files/common/openwrt/sensors_promethues-node-exporter.lua b/files/common/openwrt/sensors_promethues-node-exporter.lua
new file mode 100644
index 00000000..9c814659
--- /dev/null
+++ b/files/common/openwrt/sensors_promethues-node-exporter.lua
@@ -0,0 +1,26 @@
+local sensors = require "sensors"
+
+local config, _ = sensors.read_config('/etc/sensors.json')
+sensors.setup(config)
+local units = {
+ temperature = "celsius",
+ humidity = "percent",
+ pressure = "bar",
+}
+
+local function scrape()
+ local readings, err = sensors.read(config)
+ if not readings then return end
+
+ for name, values in pairs(readings) do
+ labels = { name = name, kind = values._kind_ }
+ for t, v in pairs(values) do
+ local unit = units[t]
+ if unit ~= nil then
+ metric("sensors_" .. t .. "_" .. unit, "gauge", labels, v)
+ end
+ end
+ end
+end
+
+return { scrape = scrape }
diff --git a/inventory/group_vars/chaos-at-home-sensors/vars.yml b/inventory/group_vars/chaos-at-home-sensors/vars.yml
index 7f4a93eb..169373a8 100644
--- a/inventory/group_vars/chaos-at-home-sensors/vars.yml
+++ b/inventory/group_vars/chaos-at-home-sensors/vars.yml
@@ -80,9 +80,8 @@ openwrt_mixin:
/usr/lib/lua/sensors.lua:
file: "{{ global_files_dir }}/common/openwrt/sensors.module_lua"
- /usr/libexec/sensors-init.lua:
- file: "{{ global_files_dir }}/common/openwrt/sensors-init.lua"
- mode: "0755"
+ /usr/lib/lua/prometheus-collectors/sensors.lua:
+ file: "{{ global_files_dir }}/common/openwrt/sensors_promethues-node-exporter.lua"
openwrt_uci:
diff --git a/inventory/host_vars/ch-sensors1.yml b/inventory/host_vars/ch-sensors1.yml
index 7efd03e4..d602fff7 100644
--- a/inventory/host_vars/ch-sensors1.yml
+++ b/inventory/host_vars/ch-sensors1.yml
@@ -6,27 +6,27 @@ sensornode_sensors:
- number: 0
devices:
- address: 0x70
- driver: pca9548
+ type: pca9548
channels:
- number: 0
devices:
- name: foo
address: 0x76
- driver: bme280
+ type: bme280
- number: 1
devices:
- name: bar
address: 0x77
- driver: bmp280
+ type: bmp280
- number: 2
devices:
- name: baz
address: 0x5c
- driver: am2315
+ type: am2315
- number: 7
devices:
- address: 0x18
- driver: ds2482
- 1wire:
+ type: ds2482
+ w1:
- name: hugo
address: 28-012113511280