local base = _G local io = require("io") local string = require("string") local cjson = require "cjson.safe" local glob = require "posix.glob" local time = require "posix.time" local unistd = require "posix.unistd" local _M = {} local _internal_ = {} _internal_.setup = {} -- ############# i2c ########### 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) f:close() return ret, err end 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") f:close() return data, err end -- ############# i2c ########### function _M.i2c_bus_path(bus) return string.format("/sys/bus/i2c/devices/i2c-%d", bus) end function _M.i2c_device_name(bus, address) return string.format("%d-%04X", bus, address) end function _M.i2c_device_path(bus, address) return string.format("/sys/bus/i2c/devices/" .. _M.i2c_device_name(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)) 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)) end function _M.i2c_get_mux_channels(parent, address) local channel_paths, glob_result = glob.glob(_M.i2c_device_path(parent, address) .. "/channel-*", 0) if not channel_paths then if glob_result == glob.GLOB_NOMATCH then return {} 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 local channels = {} for _, channel_path in base.pairs(channel_paths) do local channel = string.match(channel_path, '/channel%-(%d+)$') if not channel then return nil, "unable to parse channel number from path: " .. channel_path end local bus_path, err = unistd.readlink(channel_path) if not bus_path then return nil, err end 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) 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) num_devices = num_devices + 1 local setup_function = _internal_.setup[device.driver] if setup_function ~= nil then time.nanosleep({tv_sec = 0, tv_nsec = 100000000}) setup_function(bus, device.address) end if device.channels then time.nanosleep({tv_sec = 0, tv_nsec = 100000000}) 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 not mux_channels[channel.number] then return nil, string.format("i2c-mux %s has no channel %d", _M.i2c_device_name(bus, device.address), channel.number) end local tmp, err = _M.i2c_add_devices_recursive(mux_channels[channel.number], channel.devices) if not tmp then return nil, err end num_devices = num_devices + tmp end end end return num_devices end -- ############# II0 ########### 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_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 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 local ret, err = _internal_.write_to_file(iio_device .. '/in_pressure_oversampling_ratio', '1') if not ret then return nil, err end return _internal_.write_to_file(iio_device .. '/in_temp_oversampling_ratio', '1') end _internal_.setup['bmp280'] = _M.iio_setup_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 local ret, err = _internal_.write_to_file(iio_device .. '/in_humidityrelative_oversampling_ratio', '1') if not ret then return nil, err end local ret, err = _internal_.write_to_file(iio_device .. '/in_pressure_oversampling_ratio', '1') if not ret then return nil, err end return _internal_.write_to_file(iio_device .. '/in_temp_oversampling_ratio', '1') end _internal_.setup['bme280'] = _M.iio_setup_bme280 -- ############# config ########### function _M.read_config(path) sensors_json, err = _internal_.read_from_file(path) if not sensors_json then return nil, err end return cjson.decode(sensors_json) end function _M.setup(config) local num_devices = 0 if config.i2c then local i2c_bus for _, i2c_bus in 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 end -- ################################ return _M