From 72ce00fc5b382eef8ce7b5596230bdef369e4cfd Mon Sep 17 00:00:00 2001 From: Christian Pointner Date: Sun, 19 Sep 2021 20:20:41 +0200 Subject: add sensors to lua prometheus exporter --- files/common/openwrt/sensors-read.lua | 15 ++ files/common/openwrt/sensors.module_lua | 227 +++++++++++++++++++-- .../openwrt/sensors_promethues-node-exporter.lua | 26 +++ .../group_vars/chaos-at-home-sensors/vars.yml | 5 +- inventory/host_vars/ch-sensors1.yml | 12 +- 5 files changed, 258 insertions(+), 27 deletions(-) create mode 100755 files/common/openwrt/sensors-read.lua create mode 100644 files/common/openwrt/sensors_promethues-node-exporter.lua 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 -- cgit v1.2.3