aio(thermalzone): add driver for read a thermalzone from system (#1040)
This commit is contained in:
parent
d39848e368
commit
d139c0ac7e
|
@ -298,12 +298,15 @@ Support for many devices that use Analog Input/Output (AIO) have
|
|||
a shared set of drivers provided using the `gobot/drivers/aio` package:
|
||||
|
||||
- [AIO](https://en.wikipedia.org/wiki/Analog-to-digital_converter) <=> [Drivers](https://github.com/hybridgroup/gobot/tree/master/drivers/aio)
|
||||
- Analog Actuator
|
||||
- Analog Sensor
|
||||
- Grove Light Sensor
|
||||
- Grove Piezo Vibration Sensor
|
||||
- Grove Rotary Dial
|
||||
- Grove Sound Sensor
|
||||
- Grove Temperature Sensor
|
||||
- Temperature Sensor (supports linear and NTC thermistor in normal and inverse mode)
|
||||
- Thermal Zone Temperature Sensor
|
||||
|
||||
Support for devices that use Inter-Integrated Circuit (I2C) have a shared set of
|
||||
drivers provided using the `gobot/drivers/i2c` package:
|
||||
|
|
|
@ -12,12 +12,12 @@ Please refer to the main [README.md](https://github.com/hybridgroup/gobot/blob/r
|
|||
|
||||
Gobot has a extensible system for connecting to hardware devices. The following AIO devices are currently supported:
|
||||
|
||||
- Analog Sensor
|
||||
- Analog Actuator
|
||||
- Analog Sensor
|
||||
- Grove Light Sensor
|
||||
- Grove Piezo Vibration Sensor
|
||||
- Grove Rotary Dial
|
||||
- Grove Sound Sensor
|
||||
- Grove Temperature Sensor
|
||||
- Temperature Sensor (supports linear and NTC thermistor in normal and inverse mode)
|
||||
|
||||
More drivers are coming soon...
|
||||
- Thermal Zone Temperature Sensor
|
||||
|
|
|
@ -26,7 +26,7 @@ type sensorScaleOption struct {
|
|||
scaler func(input int) (value float64)
|
||||
}
|
||||
|
||||
// AnalogSensorDriver represents an Analog Sensor
|
||||
// AnalogSensorDriver represents an analog sensor
|
||||
type AnalogSensorDriver struct {
|
||||
*driver
|
||||
sensorCfg *sensorConfiguration
|
||||
|
@ -35,6 +35,7 @@ type AnalogSensorDriver struct {
|
|||
gobot.Eventer
|
||||
lastRawValue int
|
||||
lastValue float64
|
||||
analogRead func() (int, float64, error)
|
||||
}
|
||||
|
||||
// NewAnalogSensorDriver returns a new driver for analog sensors, given an AnalogReader and pin.
|
||||
|
@ -60,6 +61,7 @@ func NewAnalogSensorDriver(a AnalogReader, pin string, opts ...interface{}) *Ana
|
|||
}
|
||||
d.afterStart = d.initialize
|
||||
d.beforeHalt = d.shutdown
|
||||
d.analogRead = d.analogSensorRead
|
||||
|
||||
for _, opt := range opts {
|
||||
switch o := opt.(type) {
|
||||
|
@ -168,6 +170,7 @@ func (a *AnalogSensorDriver) initialize() error {
|
|||
go func() {
|
||||
timer := time.NewTimer(a.sensorCfg.readInterval)
|
||||
timer.Stop()
|
||||
|
||||
for {
|
||||
// please note, that this ensures the first read is done immediately, but has drawbacks, see notes above
|
||||
rawValue, value, err := a.analogRead()
|
||||
|
@ -183,6 +186,7 @@ func (a *AnalogSensorDriver) initialize() error {
|
|||
oldValue = value
|
||||
}
|
||||
}
|
||||
|
||||
timer.Reset(a.sensorCfg.readInterval) // ensure that after each read is a wait, independent of duration of read
|
||||
select {
|
||||
case <-timer.C:
|
||||
|
@ -205,8 +209,9 @@ func (a *AnalogSensorDriver) shutdown() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// analogRead performs an reading from the sensor and sets the internal attributes and returns the raw and scaled value
|
||||
func (a *AnalogSensorDriver) analogRead() (int, float64, error) {
|
||||
// analogSensorRead performs an reading from the sensor, sets the internal attributes and returns
|
||||
// the raw and scaled value
|
||||
func (a *AnalogSensorDriver) analogSensorRead() (int, float64, error) {
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
|
||||
|
|
|
@ -128,7 +128,7 @@ func TestTemperatureSensorDriver_LinearScaler(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTemperatureSensorPublishesTemperatureInCelsius(t *testing.T) {
|
||||
func TestTemperatureSensorWithSensorCyclicRead_PublishesTemperatureInCelsius(t *testing.T) {
|
||||
// arrange
|
||||
sem := make(chan bool)
|
||||
a := newAioTestAdaptor()
|
||||
|
@ -155,7 +155,7 @@ func TestTemperatureSensorPublishesTemperatureInCelsius(t *testing.T) {
|
|||
assert.InDelta(t, 31.61532462352477, d.Value(), 0.0)
|
||||
}
|
||||
|
||||
func TestTemperatureSensorWithSensorCyclicReadPublishesError(t *testing.T) {
|
||||
func TestTemperatureSensorWithSensorCyclicRead_PublishesError(t *testing.T) {
|
||||
// arrange
|
||||
sem := make(chan bool)
|
||||
a := newAioTestAdaptor()
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
package aio
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gobot.io/x/gobot/v2"
|
||||
)
|
||||
|
||||
// thermalZoneOptionApplier needs to be implemented by each configurable option type
|
||||
type thermalZoneOptionApplier interface {
|
||||
apply(cfg *thermalZoneConfiguration)
|
||||
}
|
||||
|
||||
// thermalZoneConfiguration contains all changeable attributes of the driver.
|
||||
type thermalZoneConfiguration struct {
|
||||
scaleUnit func(float64) float64
|
||||
}
|
||||
|
||||
// thermalZoneUnitscalerOption is the type for applying another unit scaler to the configuration
|
||||
type thermalZoneUnitscalerOption struct {
|
||||
unitscaler func(float64) float64
|
||||
}
|
||||
|
||||
// ThermalZoneDriver represents an driver for reading the system thermal zone temperature
|
||||
type ThermalZoneDriver struct {
|
||||
*AnalogSensorDriver
|
||||
thermalZoneCfg *thermalZoneConfiguration
|
||||
}
|
||||
|
||||
// NewThermalZoneDriver is a driver for reading the system thermal zone temperature, given an AnalogReader and zone id.
|
||||
//
|
||||
// Supported options: see also [aio.NewAnalogSensorDriver]
|
||||
//
|
||||
// "WithFahrenheit()"
|
||||
//
|
||||
// Adds the following API Commands: see [aio.NewAnalogSensorDriver]
|
||||
func NewThermalZoneDriver(a AnalogReader, zoneID string, opts ...interface{}) *ThermalZoneDriver {
|
||||
degreeScaler := func(input int) float64 { return float64(input) / 1000 }
|
||||
d := ThermalZoneDriver{
|
||||
AnalogSensorDriver: NewAnalogSensorDriver(a, zoneID, WithSensorScaler(degreeScaler)),
|
||||
thermalZoneCfg: &thermalZoneConfiguration{
|
||||
scaleUnit: func(input float64) float64 { return input }, // 1:1 in °C
|
||||
},
|
||||
}
|
||||
d.driverCfg.name = gobot.DefaultName("ThermalZone")
|
||||
d.analogRead = d.thermalZoneRead
|
||||
|
||||
for _, opt := range opts {
|
||||
switch o := opt.(type) {
|
||||
case optionApplier:
|
||||
o.apply(d.driverCfg)
|
||||
case sensorOptionApplier:
|
||||
o.apply(d.sensorCfg)
|
||||
case thermalZoneOptionApplier:
|
||||
o.apply(d.thermalZoneCfg)
|
||||
default:
|
||||
panic(fmt.Sprintf("'%s' can not be applied on '%s'", opt, d.driverCfg.name))
|
||||
}
|
||||
}
|
||||
|
||||
return &d
|
||||
}
|
||||
|
||||
// WithFahrenheit substitute the default 1:1 °C scaler by a scaler for °F
|
||||
func WithFahrenheit() thermalZoneOptionApplier {
|
||||
// (1°C × 9/5) + 32 = 33,8°F
|
||||
unitscaler := func(input float64) float64 { return input*9.0/5.0 + 32.0 }
|
||||
return thermalZoneUnitscalerOption{unitscaler: unitscaler}
|
||||
}
|
||||
|
||||
// thermalZoneRead overrides and extends the analogSensorRead() function to add the unit scaler
|
||||
func (d *ThermalZoneDriver) thermalZoneRead() (int, float64, error) {
|
||||
if _, _, err := d.analogSensorRead(); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
// apply unit scaler on value
|
||||
d.lastValue = d.thermalZoneCfg.scaleUnit(d.lastValue)
|
||||
return d.lastRawValue, d.lastValue, nil
|
||||
}
|
||||
|
||||
func (o thermalZoneUnitscalerOption) apply(cfg *thermalZoneConfiguration) {
|
||||
cfg.scaleUnit = o.unitscaler
|
||||
}
|
|
@ -0,0 +1,91 @@
|
|||
package aio
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewThermalZoneDriver(t *testing.T) {
|
||||
// arrange
|
||||
const pin = "thermal_zone0"
|
||||
a := newAioTestAdaptor()
|
||||
// act
|
||||
d := NewThermalZoneDriver(a, pin)
|
||||
// assert: driver attributes
|
||||
assert.IsType(t, &ThermalZoneDriver{}, d)
|
||||
assert.NotNil(t, d.driverCfg)
|
||||
assert.True(t, strings.HasPrefix(d.Name(), "ThermalZone"))
|
||||
assert.Equal(t, a, d.Connection())
|
||||
require.NoError(t, d.afterStart())
|
||||
require.NoError(t, d.beforeHalt())
|
||||
assert.NotNil(t, d.Commander)
|
||||
assert.NotNil(t, d.mutex)
|
||||
// assert: sensor attributes
|
||||
assert.Equal(t, pin, d.Pin())
|
||||
assert.InDelta(t, 0.0, d.lastValue, 0, 0)
|
||||
assert.Equal(t, 0, d.lastRawValue)
|
||||
assert.Nil(t, d.halt) // will be created on initialize, if cyclic reading is on
|
||||
assert.NotNil(t, d.Eventer)
|
||||
require.NotNil(t, d.sensorCfg)
|
||||
assert.Equal(t, time.Duration(0), d.sensorCfg.readInterval)
|
||||
assert.NotNil(t, d.sensorCfg.scale)
|
||||
// assert: thermal zone attributes
|
||||
require.NotNil(t, d.thermalZoneCfg)
|
||||
require.NotNil(t, d.thermalZoneCfg.scaleUnit)
|
||||
assert.InDelta(t, 1.0, d.thermalZoneCfg.scaleUnit(1), 0.0)
|
||||
}
|
||||
|
||||
func TestNewThermalZoneDriver_options(t *testing.T) {
|
||||
// This is a general test, that options are applied in constructor by using the common WithName() option, least one
|
||||
// option of this driver and one of another driver (which should lead to panic). Further tests for options can also
|
||||
// be done by call of "WithOption(val).apply(cfg)".
|
||||
// arrange
|
||||
const (
|
||||
myName = "outlet temperature"
|
||||
cycReadDur = 10 * time.Millisecond
|
||||
)
|
||||
panicFunc := func() {
|
||||
NewThermalZoneDriver(newAioTestAdaptor(), "1", WithName("crazy"),
|
||||
WithActuatorScaler(func(float64) int { return 0 }))
|
||||
}
|
||||
// act
|
||||
d := NewThermalZoneDriver(newAioTestAdaptor(), "1",
|
||||
WithName(myName),
|
||||
WithSensorCyclicRead(cycReadDur),
|
||||
WithFahrenheit())
|
||||
// assert
|
||||
assert.Equal(t, cycReadDur, d.sensorCfg.readInterval)
|
||||
assert.InDelta(t, 33.8, d.thermalZoneCfg.scaleUnit(1), 0.0) // (1°C × 9/5) + 32 = 33,8°F
|
||||
assert.Equal(t, myName, d.Name())
|
||||
assert.PanicsWithValue(t, "'scaler option for analog actuators' can not be applied on 'crazy'", panicFunc)
|
||||
}
|
||||
|
||||
func TestThermalZoneWithSensorCyclicRead_PublishesTemperatureInFahrenheit(t *testing.T) {
|
||||
// arrange
|
||||
sem := make(chan bool)
|
||||
a := newAioTestAdaptor()
|
||||
d := NewThermalZoneDriver(a, "1", WithSensorCyclicRead(10*time.Millisecond), WithFahrenheit())
|
||||
a.analogReadFunc = func() (int, error) {
|
||||
return -100000, nil // -100.000 °C
|
||||
}
|
||||
// act: start cyclic reading
|
||||
require.NoError(t, d.Start())
|
||||
// assert
|
||||
_ = d.Once(d.Event(Value), func(data interface{}) {
|
||||
//nolint:forcetypeassert // ok here
|
||||
assert.InDelta(t, -148.0, data.(float64), 0.0)
|
||||
sem <- true
|
||||
})
|
||||
|
||||
select {
|
||||
case <-sem:
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Errorf(" Temperature Sensor Event \"Data\" was not published")
|
||||
}
|
||||
|
||||
assert.InDelta(t, -148.0, d.Value(), 0.0)
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
//go:build example
|
||||
// +build example
|
||||
|
||||
//
|
||||
// Do not build by default.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"gobot.io/x/gobot/v2"
|
||||
"gobot.io/x/gobot/v2/drivers/aio"
|
||||
"gobot.io/x/gobot/v2/platforms/raspi"
|
||||
)
|
||||
|
||||
// Wiring: no wiring needed
|
||||
func main() {
|
||||
adaptor := raspi.NewAdaptor()
|
||||
therm0C := aio.NewThermalZoneDriver(adaptor, "thermal_zone0")
|
||||
therm0F := aio.NewThermalZoneDriver(adaptor, "thermal_zone0", aio.WithFahrenheit())
|
||||
|
||||
work := func() {
|
||||
gobot.Every(500*time.Millisecond, func() {
|
||||
t0C, err := therm0C.Read()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
t0F, err := therm0F.Read()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Zone 0: %2.3f °C, %2.3f °F\n", t0C, t0F)
|
||||
})
|
||||
}
|
||||
|
||||
robot := gobot.NewRobot("thermalBot",
|
||||
[]gobot.Connection{adaptor},
|
||||
[]gobot.Device{therm0C, therm0F},
|
||||
work,
|
||||
)
|
||||
|
||||
if err := robot.Start(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
//go:build example
|
||||
// +build example
|
||||
|
||||
//
|
||||
// Do not build by default.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"gobot.io/x/gobot/v2"
|
||||
"gobot.io/x/gobot/v2/drivers/aio"
|
||||
"gobot.io/x/gobot/v2/platforms/tinkerboard"
|
||||
)
|
||||
|
||||
// Wiring: no wiring needed
|
||||
func main() {
|
||||
adaptor := tinkerboard.NewAdaptor()
|
||||
therm0 := aio.NewThermalZoneDriver(adaptor, "thermal_zone0")
|
||||
therm1 := aio.NewThermalZoneDriver(adaptor, "thermal_zone1", aio.WithFahrenheit())
|
||||
|
||||
work := func() {
|
||||
gobot.Every(500*time.Millisecond, func() {
|
||||
t0, err := therm0.Read()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
t1, err := therm1.Read()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Zone 0: %2.3f °C, Zone 1: %2.3f °F\n", t0, t1)
|
||||
})
|
||||
}
|
||||
|
||||
robot := gobot.NewRobot("thermalBot",
|
||||
[]gobot.Connection{adaptor},
|
||||
[]gobot.Device{therm0, therm1},
|
||||
work,
|
||||
)
|
||||
|
||||
if err := robot.Start(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -33,6 +33,13 @@ type gpioPinDefinition struct {
|
|||
cdev cdevPin
|
||||
}
|
||||
|
||||
type analogPinDefinition struct {
|
||||
path string
|
||||
r bool // readable
|
||||
w bool // writable
|
||||
bufLen uint16
|
||||
}
|
||||
|
||||
type pwmPinDefinition struct {
|
||||
channel int
|
||||
dir string
|
||||
|
@ -46,6 +53,7 @@ type Adaptor struct {
|
|||
gpioPinMap map[string]gpioPinDefinition
|
||||
pwmPinMap map[string]pwmPinDefinition
|
||||
mutex sync.Mutex
|
||||
*adaptors.AnalogPinsAdaptor
|
||||
*adaptors.DigitalPinsAdaptor
|
||||
*adaptors.PWMPinsAdaptor
|
||||
*adaptors.I2cBusAdaptor
|
||||
|
@ -56,13 +64,13 @@ type Adaptor struct {
|
|||
//
|
||||
// Optional parameters:
|
||||
//
|
||||
// adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs (still used by default)
|
||||
// adaptors.WithSpiGpioAccess(sclk, nss, mosi, miso): use GPIO's instead of /dev/spidev#.#
|
||||
// adaptors.WithGpiosActiveLow(pin's): invert the pin behavior
|
||||
// adaptors.WithGpiosPullUp/Down(pin's): sets the internal pull resistor
|
||||
// adaptors.WithGpiosOpenDrain/Source(pin's): sets the output behavior
|
||||
// adaptors.WithGpioDebounce(pin, period): sets the input debouncer
|
||||
// adaptors.WithGpioEventOnFallingEdge/RaisingEdge/BothEdges(pin, handler): activate edge detection
|
||||
// adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs (still used by default)
|
||||
// adaptors.WithSpiGpioAccess(sclk, nss, mosi, miso): use GPIO's instead of /dev/spidev#.#
|
||||
// adaptors.WithGpiosActiveLow(pin's): invert the pin behavior
|
||||
// adaptors.WithGpiosPullUp/Down(pin's): sets the internal pull resistor
|
||||
// adaptors.WithGpiosOpenDrain/Source(pin's): sets the output behavior
|
||||
// adaptors.WithGpioDebounce(pin, period): sets the input debouncer
|
||||
// adaptors.WithGpioEventOnFallingEdge/RaisingEdge/BothEdges(pin, handler): activate edge detection
|
||||
func NewNeoAdaptor(opts ...func(adaptors.Optioner)) *Adaptor {
|
||||
sys := system.NewAccesser(system.WithDigitalPinGpiodAccess())
|
||||
c := &Adaptor{
|
||||
|
@ -71,6 +79,7 @@ func NewNeoAdaptor(opts ...func(adaptors.Optioner)) *Adaptor {
|
|||
gpioPinMap: neoGpioPins,
|
||||
pwmPinMap: neoPwmPins,
|
||||
}
|
||||
c.AnalogPinsAdaptor = adaptors.NewAnalogPinsAdaptor(sys, c.translateAnalogPin)
|
||||
c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateDigitalPin, opts...)
|
||||
c.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, c.translatePWMPin,
|
||||
adaptors.WithPolarityInvertedIdentifier(pwmInvertedIdentifier))
|
||||
|
@ -99,6 +108,10 @@ func (c *Adaptor) Connect() error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := c.AnalogPinsAdaptor.Connect(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.PWMPinsAdaptor.Connect(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -116,6 +129,10 @@ func (c *Adaptor) Finalize() error {
|
|||
err = multierror.Append(err, e)
|
||||
}
|
||||
|
||||
if e := c.AnalogPinsAdaptor.Finalize(); e != nil {
|
||||
err = multierror.Append(err, e)
|
||||
}
|
||||
|
||||
if e := c.I2cBusAdaptor.Finalize(); e != nil {
|
||||
err = multierror.Append(err, e)
|
||||
}
|
||||
|
@ -143,6 +160,24 @@ func (c *Adaptor) validateI2cBusNumber(busNr int) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Adaptor) translateAnalogPin(id string) (string, bool, bool, uint16, error) {
|
||||
pinInfo, ok := analogPinDefinitions[id]
|
||||
if !ok {
|
||||
return "", false, false, 0, fmt.Errorf("'%s' is not a valid id for a analog pin", id)
|
||||
}
|
||||
|
||||
path := pinInfo.path
|
||||
info, err := c.sys.Stat(path)
|
||||
if err != nil {
|
||||
return "", false, false, 0, fmt.Errorf("Error (%v) on access '%s'", err, path)
|
||||
}
|
||||
if info.IsDir() {
|
||||
return "", false, false, 0, fmt.Errorf("The item '%s' is a directory, which is not expected", path)
|
||||
}
|
||||
|
||||
return path, pinInfo.r, pinInfo.w, pinInfo.bufLen, nil
|
||||
}
|
||||
|
||||
func (c *Adaptor) translateDigitalPin(id string) (string, int, error) {
|
||||
pindef, ok := c.gpioPinMap[id]
|
||||
if !ok {
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gobot.io/x/gobot/v2"
|
||||
"gobot.io/x/gobot/v2/drivers/aio"
|
||||
"gobot.io/x/gobot/v2/drivers/gpio"
|
||||
"gobot.io/x/gobot/v2/drivers/i2c"
|
||||
"gobot.io/x/gobot/v2/system"
|
||||
|
@ -58,6 +59,7 @@ var (
|
|||
_ gpio.DigitalWriter = (*Adaptor)(nil)
|
||||
_ gpio.PwmWriter = (*Adaptor)(nil)
|
||||
_ gpio.ServoWriter = (*Adaptor)(nil)
|
||||
_ aio.AnalogReader = (*Adaptor)(nil)
|
||||
_ i2c.Connector = (*Adaptor)(nil)
|
||||
)
|
||||
|
||||
|
@ -99,6 +101,29 @@ func TestDigitalIO(t *testing.T) {
|
|||
require.NoError(t, a.Finalize())
|
||||
}
|
||||
|
||||
func TestAnalog(t *testing.T) {
|
||||
mockPaths := []string{
|
||||
"/sys/class/thermal/thermal_zone0/temp",
|
||||
}
|
||||
|
||||
a, fs := initTestAdaptorWithMockedFilesystem(mockPaths)
|
||||
|
||||
fs.Files["/sys/class/thermal/thermal_zone0/temp"].Contents = "567\n"
|
||||
got, err := a.AnalogRead("thermal_zone0")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 567, got)
|
||||
|
||||
_, err = a.AnalogRead("thermal_zone10")
|
||||
require.ErrorContains(t, err, "'thermal_zone10' is not a valid id for a analog pin")
|
||||
|
||||
fs.WithReadError = true
|
||||
_, err = a.AnalogRead("thermal_zone0")
|
||||
require.ErrorContains(t, err, "read error")
|
||||
fs.WithReadError = false
|
||||
|
||||
require.NoError(t, a.Finalize())
|
||||
}
|
||||
|
||||
func TestInvalidPWMPin(t *testing.T) {
|
||||
a, fs := initTestAdaptorWithMockedFilesystem(pwmMockPaths)
|
||||
preparePwmFs(fs)
|
||||
|
@ -342,6 +367,49 @@ func Test_translateDigitalPin(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_translateAnalogPin(t *testing.T) {
|
||||
mockedPaths := []string{
|
||||
"/sys/class/thermal/thermal_zone0/temp",
|
||||
"/sys/class/thermal/thermal_zone1/temp",
|
||||
}
|
||||
tests := map[string]struct {
|
||||
id string
|
||||
wantPath string
|
||||
wantReadable bool
|
||||
wantBufLen uint16
|
||||
wantErr string
|
||||
}{
|
||||
"translate_thermal_zone0": {
|
||||
id: "thermal_zone0",
|
||||
wantPath: "/sys/class/thermal/thermal_zone0/temp",
|
||||
wantReadable: true,
|
||||
wantBufLen: 7,
|
||||
},
|
||||
"unknown_id": {
|
||||
id: "thermal_zone1",
|
||||
wantErr: "'thermal_zone1' is not a valid id for a analog pin",
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// arrange
|
||||
a, _ := initTestAdaptorWithMockedFilesystem(mockedPaths)
|
||||
// act
|
||||
path, r, w, buf, err := a.translateAnalogPin(tc.id)
|
||||
// assert
|
||||
if tc.wantErr != "" {
|
||||
require.EqualError(t, err, tc.wantErr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, tc.wantPath, path)
|
||||
assert.Equal(t, tc.wantReadable, r)
|
||||
assert.False(t, w)
|
||||
assert.Equal(t, tc.wantBufLen, buf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_translatePWMPin(t *testing.T) {
|
||||
basePaths := []string{"/sys/devices/platform/soc/1c21400.pwm/pwm/"}
|
||||
tests := map[string]struct {
|
||||
|
|
|
@ -23,3 +23,8 @@ var neoPwmPins = map[string]pwmPinDefinition{
|
|||
// UART_RXD0, GPIOA5, PWM
|
||||
"PWM": {dir: "/sys/devices/platform/soc/1c21400.pwm/pwm/", dirRegexp: "pwmchip[0]$", channel: 0},
|
||||
}
|
||||
|
||||
var analogPinDefinitions = map[string]analogPinDefinition{
|
||||
// +/-273.200 °C need >=7 characters to read: +/-273200 millidegree Celsius
|
||||
"thermal_zone0": {path: "/sys/class/thermal/thermal_zone0/temp", r: true, w: false, bufLen: 7},
|
||||
}
|
||||
|
|
|
@ -24,6 +24,13 @@ const (
|
|||
defaultSpiMaxSpeed = 500000
|
||||
)
|
||||
|
||||
type analogPinDefinition struct {
|
||||
path string
|
||||
r bool // readable
|
||||
w bool // writable
|
||||
bufLen uint16
|
||||
}
|
||||
|
||||
// Adaptor is the Gobot Adaptor for the Raspberry Pi
|
||||
type Adaptor struct {
|
||||
name string
|
||||
|
@ -31,6 +38,7 @@ type Adaptor struct {
|
|||
sys *system.Accesser
|
||||
revision string
|
||||
pwmPins map[string]gobot.PWMPinner
|
||||
*adaptors.AnalogPinsAdaptor
|
||||
*adaptors.DigitalPinsAdaptor
|
||||
*adaptors.I2cBusAdaptor
|
||||
*adaptors.SpiBusAdaptor
|
||||
|
@ -41,13 +49,13 @@ type Adaptor struct {
|
|||
//
|
||||
// Optional parameters:
|
||||
//
|
||||
// adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs (still used by default)
|
||||
// adaptors.WithSpiGpioAccess(sclk, nss, mosi, miso): use GPIO's instead of /dev/spidev#.#
|
||||
// adaptors.WithGpiosActiveLow(pin's): invert the pin behavior
|
||||
// adaptors.WithGpiosPullUp/Down(pin's): sets the internal pull resistor
|
||||
// adaptors.WithGpiosOpenDrain/Source(pin's): sets the output behavior
|
||||
// adaptors.WithGpioDebounce(pin, period): sets the input debouncer
|
||||
// adaptors.WithGpioEventOnFallingEdge/RaisingEdge/BothEdges(pin, handler): activate edge detection
|
||||
// adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs (still used by default)
|
||||
// adaptors.WithSpiGpioAccess(sclk, nss, mosi, miso): use GPIO's instead of /dev/spidev#.#
|
||||
// adaptors.WithGpiosActiveLow(pin's): invert the pin behavior
|
||||
// adaptors.WithGpiosPullUp/Down(pin's): sets the internal pull resistor
|
||||
// adaptors.WithGpiosOpenDrain/Source(pin's): sets the output behavior
|
||||
// adaptors.WithGpioDebounce(pin, period): sets the input debouncer
|
||||
// adaptors.WithGpioEventOnFallingEdge/RaisingEdge/BothEdges(pin, handler): activate edge detection
|
||||
func NewAdaptor(opts ...func(adaptors.Optioner)) *Adaptor {
|
||||
sys := system.NewAccesser(system.WithDigitalPinGpiodAccess())
|
||||
c := &Adaptor{
|
||||
|
@ -55,6 +63,7 @@ func NewAdaptor(opts ...func(adaptors.Optioner)) *Adaptor {
|
|||
sys: sys,
|
||||
PiBlasterPeriod: 10000000,
|
||||
}
|
||||
c.AnalogPinsAdaptor = adaptors.NewAnalogPinsAdaptor(sys, c.translateAnalogPin)
|
||||
c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.getPinTranslatorFunction(), opts...)
|
||||
c.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, c.validateI2cBusNumber, 1)
|
||||
c.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, c.validateSpiBusNumber, defaultSpiBusNumber, defaultSpiChipNumber,
|
||||
|
@ -91,6 +100,10 @@ func (c *Adaptor) Connect() error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := c.AnalogPinsAdaptor.Connect(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.pwmPins = make(map[string]gobot.PWMPinner)
|
||||
return c.DigitalPinsAdaptor.Connect()
|
||||
}
|
||||
|
@ -111,6 +124,10 @@ func (c *Adaptor) Finalize() error {
|
|||
}
|
||||
c.pwmPins = nil
|
||||
|
||||
if e := c.AnalogPinsAdaptor.Finalize(); e != nil {
|
||||
err = multierror.Append(err, e)
|
||||
}
|
||||
|
||||
if e := c.I2cBusAdaptor.Finalize(); e != nil {
|
||||
err = multierror.Append(err, e)
|
||||
}
|
||||
|
@ -184,6 +201,24 @@ func (c *Adaptor) validateI2cBusNumber(busNr int) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Adaptor) translateAnalogPin(id string) (string, bool, bool, uint16, error) {
|
||||
pinInfo, ok := analogPinDefinitions[id]
|
||||
if !ok {
|
||||
return "", false, false, 0, fmt.Errorf("'%s' is not a valid id for a analog pin", id)
|
||||
}
|
||||
|
||||
path := pinInfo.path
|
||||
info, err := c.sys.Stat(path)
|
||||
if err != nil {
|
||||
return "", false, false, 0, fmt.Errorf("Error (%v) on access '%s'", err, path)
|
||||
}
|
||||
if info.IsDir() {
|
||||
return "", false, false, 0, fmt.Errorf("The item '%s' is a directory, which is not expected", path)
|
||||
}
|
||||
|
||||
return path, pinInfo.r, pinInfo.w, pinInfo.bufLen, nil
|
||||
}
|
||||
|
||||
func (c *Adaptor) getPinTranslatorFunction() func(string) (string, int, error) {
|
||||
return func(pin string) (string, int, error) {
|
||||
var line int
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gobot.io/x/gobot/v2"
|
||||
"gobot.io/x/gobot/v2/drivers/aio"
|
||||
"gobot.io/x/gobot/v2/drivers/gpio"
|
||||
"gobot.io/x/gobot/v2/drivers/i2c"
|
||||
"gobot.io/x/gobot/v2/drivers/spi"
|
||||
|
@ -27,6 +28,7 @@ var (
|
|||
_ gpio.DigitalWriter = (*Adaptor)(nil)
|
||||
_ gpio.PwmWriter = (*Adaptor)(nil)
|
||||
_ gpio.ServoWriter = (*Adaptor)(nil)
|
||||
_ aio.AnalogReader = (*Adaptor)(nil)
|
||||
_ i2c.Connector = (*Adaptor)(nil)
|
||||
_ spi.Connector = (*Adaptor)(nil)
|
||||
)
|
||||
|
@ -108,6 +110,29 @@ func TestFinalize(t *testing.T) {
|
|||
require.NoError(t, a.Finalize())
|
||||
}
|
||||
|
||||
func TestAnalog(t *testing.T) {
|
||||
mockPaths := []string{
|
||||
"/sys/class/thermal/thermal_zone0/temp",
|
||||
}
|
||||
|
||||
a, fs := initTestAdaptorWithMockedFilesystem(mockPaths)
|
||||
|
||||
fs.Files["/sys/class/thermal/thermal_zone0/temp"].Contents = "567\n"
|
||||
got, err := a.AnalogRead("thermal_zone0")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 567, got)
|
||||
|
||||
_, err = a.AnalogRead("thermal_zone10")
|
||||
require.ErrorContains(t, err, "'thermal_zone10' is not a valid id for a analog pin")
|
||||
|
||||
fs.WithReadError = true
|
||||
_, err = a.AnalogRead("thermal_zone0")
|
||||
require.ErrorContains(t, err, "read error")
|
||||
fs.WithReadError = false
|
||||
|
||||
require.NoError(t, a.Finalize())
|
||||
}
|
||||
|
||||
func TestDigitalPWM(t *testing.T) {
|
||||
mockedPaths := []string{"/dev/pi-blaster"}
|
||||
a, fs := initTestAdaptorWithMockedFilesystem(mockedPaths)
|
||||
|
@ -339,3 +364,46 @@ func Test_validateI2cBusNumber(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_translateAnalogPin(t *testing.T) {
|
||||
mockedPaths := []string{
|
||||
"/sys/class/thermal/thermal_zone0/temp",
|
||||
"/sys/class/thermal/thermal_zone1/temp",
|
||||
}
|
||||
tests := map[string]struct {
|
||||
id string
|
||||
wantPath string
|
||||
wantReadable bool
|
||||
wantBufLen uint16
|
||||
wantErr string
|
||||
}{
|
||||
"translate_thermal_zone0": {
|
||||
id: "thermal_zone0",
|
||||
wantPath: "/sys/class/thermal/thermal_zone0/temp",
|
||||
wantReadable: true,
|
||||
wantBufLen: 7,
|
||||
},
|
||||
"unknown_id": {
|
||||
id: "thermal_zone1",
|
||||
wantErr: "'thermal_zone1' is not a valid id for a analog pin",
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// arrange
|
||||
a, _ := initTestAdaptorWithMockedFilesystem(mockedPaths)
|
||||
// act
|
||||
path, r, w, buf, err := a.translateAnalogPin(tc.id)
|
||||
// assert
|
||||
if tc.wantErr != "" {
|
||||
require.EqualError(t, err, tc.wantErr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, tc.wantPath, path)
|
||||
assert.Equal(t, tc.wantReadable, r)
|
||||
assert.False(t, w)
|
||||
assert.Equal(t, tc.wantBufLen, buf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,3 +86,8 @@ var pins = map[string]map[string]int{
|
|||
"3": 21,
|
||||
},
|
||||
}
|
||||
|
||||
var analogPinDefinitions = map[string]analogPinDefinition{
|
||||
// +/-273.200 °C need >=7 characters to read: +/-273200 millidegree Celsius
|
||||
"thermal_zone0": {path: "/sys/class/thermal/thermal_zone0/temp", r: true, w: false, bufLen: 7},
|
||||
}
|
||||
|
|
|
@ -23,10 +23,27 @@ const (
|
|||
defaultSpiMaxSpeed = 500000
|
||||
)
|
||||
|
||||
type cdevPin struct {
|
||||
chip uint8
|
||||
line uint8
|
||||
}
|
||||
|
||||
type gpioPinDefinition struct {
|
||||
sysfs int
|
||||
cdev cdevPin
|
||||
}
|
||||
|
||||
type analogPinDefinition struct {
|
||||
path string
|
||||
r bool // readable
|
||||
w bool // writable
|
||||
bufLen uint16
|
||||
}
|
||||
|
||||
type pwmPinDefinition struct {
|
||||
channel int
|
||||
dir string
|
||||
dirRegexp string
|
||||
channel int
|
||||
}
|
||||
|
||||
// Adaptor represents a Gobot Adaptor for the ASUS Tinker Board
|
||||
|
@ -34,6 +51,7 @@ type Adaptor struct {
|
|||
name string
|
||||
sys *system.Accesser
|
||||
mutex sync.Mutex
|
||||
*adaptors.AnalogPinsAdaptor
|
||||
*adaptors.DigitalPinsAdaptor
|
||||
*adaptors.PWMPinsAdaptor
|
||||
*adaptors.I2cBusAdaptor
|
||||
|
@ -44,10 +62,10 @@ type Adaptor struct {
|
|||
//
|
||||
// Optional parameters:
|
||||
//
|
||||
// adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs (still used by default)
|
||||
// adaptors.WithSpiGpioAccess(sclk, nss, mosi, miso): use GPIO's instead of /dev/spidev#.#
|
||||
// adaptors.WithGpiosActiveLow(pin's): invert the pin behavior
|
||||
// adaptors.WithGpiosPullUp/Down(pin's): sets the internal pull resistor
|
||||
// adaptors.WithGpiodAccess(): use character device gpiod driver instead of sysfs (still used by default)
|
||||
// adaptors.WithSpiGpioAccess(sclk, nss, mosi, miso): use GPIO's instead of /dev/spidev#.#
|
||||
// adaptors.WithGpiosActiveLow(pin's): invert the pin behavior
|
||||
// adaptors.WithGpiosPullUp/Down(pin's): sets the internal pull resistor
|
||||
//
|
||||
// note from RK3288 datasheet: "The pull direction (pullup or pulldown) for all of GPIOs are software-programmable", but
|
||||
// the latter is not working for any pin (armbian 22.08.7)
|
||||
|
@ -57,6 +75,7 @@ func NewAdaptor(opts ...func(adaptors.Optioner)) *Adaptor {
|
|||
name: gobot.DefaultName("Tinker Board"),
|
||||
sys: sys,
|
||||
}
|
||||
c.AnalogPinsAdaptor = adaptors.NewAnalogPinsAdaptor(sys, c.translateAnalogPin)
|
||||
c.DigitalPinsAdaptor = adaptors.NewDigitalPinsAdaptor(sys, c.translateDigitalPin, opts...)
|
||||
c.PWMPinsAdaptor = adaptors.NewPWMPinsAdaptor(sys, c.translatePWMPin,
|
||||
adaptors.WithPolarityInvertedIdentifier(pwmInvertedIdentifier))
|
||||
|
@ -85,6 +104,10 @@ func (c *Adaptor) Connect() error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err := c.AnalogPinsAdaptor.Connect(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.PWMPinsAdaptor.Connect(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -102,6 +125,10 @@ func (c *Adaptor) Finalize() error {
|
|||
err = multierror.Append(err, e)
|
||||
}
|
||||
|
||||
if e := c.AnalogPinsAdaptor.Finalize(); e != nil {
|
||||
err = multierror.Append(err, e)
|
||||
}
|
||||
|
||||
if e := c.I2cBusAdaptor.Finalize(); e != nil {
|
||||
err = multierror.Append(err, e)
|
||||
}
|
||||
|
@ -130,6 +157,24 @@ func (c *Adaptor) validateI2cBusNumber(busNr int) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (c *Adaptor) translateAnalogPin(id string) (string, bool, bool, uint16, error) {
|
||||
pinInfo, ok := analogPinDefinitions[id]
|
||||
if !ok {
|
||||
return "", false, false, 0, fmt.Errorf("'%s' is not a valid id for a analog pin", id)
|
||||
}
|
||||
|
||||
path := pinInfo.path
|
||||
info, err := c.sys.Stat(path)
|
||||
if err != nil {
|
||||
return "", false, false, 0, fmt.Errorf("Error (%v) on access '%s'", err, path)
|
||||
}
|
||||
if info.IsDir() {
|
||||
return "", false, false, 0, fmt.Errorf("The item '%s' is a directory, which is not expected", path)
|
||||
}
|
||||
|
||||
return path, pinInfo.r, pinInfo.w, pinInfo.bufLen, nil
|
||||
}
|
||||
|
||||
func (c *Adaptor) translateDigitalPin(id string) (string, int, error) {
|
||||
pindef, ok := gpioPinDefinitions[id]
|
||||
if !ok {
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
|
||||
"gobot.io/x/gobot/v2"
|
||||
"gobot.io/x/gobot/v2/drivers/aio"
|
||||
"gobot.io/x/gobot/v2/drivers/gpio"
|
||||
"gobot.io/x/gobot/v2/drivers/i2c"
|
||||
"gobot.io/x/gobot/v2/system"
|
||||
|
@ -58,6 +59,7 @@ var (
|
|||
_ gpio.DigitalWriter = (*Adaptor)(nil)
|
||||
_ gpio.PwmWriter = (*Adaptor)(nil)
|
||||
_ gpio.ServoWriter = (*Adaptor)(nil)
|
||||
_ aio.AnalogReader = (*Adaptor)(nil)
|
||||
_ i2c.Connector = (*Adaptor)(nil)
|
||||
)
|
||||
|
||||
|
@ -99,6 +101,29 @@ func TestDigitalIO(t *testing.T) {
|
|||
require.NoError(t, a.Finalize())
|
||||
}
|
||||
|
||||
func TestAnalog(t *testing.T) {
|
||||
mockPaths := []string{
|
||||
"/sys/class/thermal/thermal_zone0/temp",
|
||||
}
|
||||
|
||||
a, fs := initTestAdaptorWithMockedFilesystem(mockPaths)
|
||||
|
||||
fs.Files["/sys/class/thermal/thermal_zone0/temp"].Contents = "567\n"
|
||||
got, err := a.AnalogRead("thermal_zone0")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 567, got)
|
||||
|
||||
_, err = a.AnalogRead("thermal_zone10")
|
||||
require.ErrorContains(t, err, "'thermal_zone10' is not a valid id for a analog pin")
|
||||
|
||||
fs.WithReadError = true
|
||||
_, err = a.AnalogRead("thermal_zone0")
|
||||
require.ErrorContains(t, err, "read error")
|
||||
fs.WithReadError = false
|
||||
|
||||
require.NoError(t, a.Finalize())
|
||||
}
|
||||
|
||||
func TestInvalidPWMPin(t *testing.T) {
|
||||
a, fs := initTestAdaptorWithMockedFilesystem(pwmMockPaths)
|
||||
preparePwmFs(fs)
|
||||
|
@ -355,6 +380,55 @@ func Test_translateDigitalPin(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func Test_translateAnalogPin(t *testing.T) {
|
||||
mockedPaths := []string{
|
||||
"/sys/class/thermal/thermal_zone0/temp",
|
||||
"/sys/class/thermal/thermal_zone1/temp",
|
||||
}
|
||||
tests := map[string]struct {
|
||||
id string
|
||||
wantPath string
|
||||
wantReadable bool
|
||||
wantBufLen uint16
|
||||
wantErr string
|
||||
}{
|
||||
"translate_thermal_zone0": {
|
||||
id: "thermal_zone0",
|
||||
wantPath: "/sys/class/thermal/thermal_zone0/temp",
|
||||
wantReadable: true,
|
||||
wantBufLen: 7,
|
||||
},
|
||||
"translate_thermal_zone1": {
|
||||
id: "thermal_zone1",
|
||||
wantPath: "/sys/class/thermal/thermal_zone1/temp",
|
||||
wantReadable: true,
|
||||
wantBufLen: 7,
|
||||
},
|
||||
"unknown_id": {
|
||||
id: "99",
|
||||
wantErr: "'99' is not a valid id for a analog pin",
|
||||
},
|
||||
}
|
||||
for name, tc := range tests {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
// arrange
|
||||
a, _ := initTestAdaptorWithMockedFilesystem(mockedPaths)
|
||||
// act
|
||||
path, r, w, buf, err := a.translateAnalogPin(tc.id)
|
||||
// assert
|
||||
if tc.wantErr != "" {
|
||||
require.EqualError(t, err, tc.wantErr)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
assert.Equal(t, tc.wantPath, path)
|
||||
assert.Equal(t, tc.wantReadable, r)
|
||||
assert.False(t, w)
|
||||
assert.Equal(t, tc.wantBufLen, buf)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_translatePWMPin(t *testing.T) {
|
||||
basePaths := []string{
|
||||
"/sys/devices/platform/ff680020.pwm/pwm/",
|
||||
|
|
|
@ -1,15 +1,5 @@
|
|||
package tinkerboard
|
||||
|
||||
type cdevPin struct {
|
||||
chip uint8
|
||||
line uint8
|
||||
}
|
||||
|
||||
type gpioPinDefinition struct {
|
||||
sysfs int
|
||||
cdev cdevPin
|
||||
}
|
||||
|
||||
// notes for character device
|
||||
// pins: A=0+Nr, B=8+Nr, C=16+Nr
|
||||
// tested: armbian Linux, OK: work as input and output, IN: work only as input
|
||||
|
@ -50,3 +40,9 @@ var pwmPinDefinitions = map[string]pwmPinDefinition{
|
|||
// GPIO7_C7_UART2TX_PWM3
|
||||
"32": {dir: "/sys/devices/platform/ff680030.pwm/pwm/", dirRegexp: "pwmchip[0|1|2|3]$", channel: 0},
|
||||
}
|
||||
|
||||
var analogPinDefinitions = map[string]analogPinDefinition{
|
||||
// +/-273.200 °C need >=7 characters to read: +/-273200 millidegree Celsius
|
||||
"thermal_zone0": {path: "/sys/class/thermal/thermal_zone0/temp", r: true, w: false, bufLen: 7},
|
||||
"thermal_zone1": {path: "/sys/class/thermal/thermal_zone1/temp", r: true, w: false, bufLen: 7},
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue