-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
aio(thermalzone): add driver for read a thermalzone from system
- Loading branch information
1 parent
d39848e
commit 3febcf1
Showing
8 changed files
with
357 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
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.NotNil(t, d.halt) | ||
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 | ||
} | ||
_ = d.Once(d.Event(Value), func(data interface{}) { | ||
//nolint:forcetypeassert // ok here | ||
assert.InDelta(t, -148.0, data.(float64), 0.0) | ||
sem <- true | ||
}) | ||
require.NoError(t, d.Start()) | ||
|
||
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.