mirror of
https://github.com/hybridgroup/gobot.git
synced 2025-04-29 13:49:14 +08:00
219 lines
6.6 KiB
Go
219 lines
6.6 KiB
Go
package onewire
|
||
|
||
import (
|
||
"fmt"
|
||
"math"
|
||
"time"
|
||
)
|
||
|
||
const (
|
||
ds18b20DefaultResolution = 12
|
||
ds18b20DefaultConversionTime = 750
|
||
)
|
||
|
||
// ds18b20OptionApplier needs to be implemented by each configurable option type
|
||
type ds18b20OptionApplier interface {
|
||
apply(cfg *ds18b20Configuration)
|
||
}
|
||
|
||
// ds18b20Configuration contains all changeable attributes of the driver.
|
||
type ds18b20Configuration struct {
|
||
scaleUnit func(int) float32
|
||
resolution uint8
|
||
conversionTime uint16
|
||
}
|
||
|
||
// ds18b20UnitscalerOption is the type for applying another unit scaler to the configuration
|
||
type ds18b20UnitscalerOption struct {
|
||
unitscaler func(int) float32
|
||
}
|
||
|
||
type ds18b20ResolutionOption uint8
|
||
|
||
type ds18b20ConversionTimeOption uint16
|
||
|
||
// DS18B20Driver is a driver for the DS18B20 1-wire temperature sensor.
|
||
type DS18B20Driver struct {
|
||
*driver
|
||
ds18b20Cfg *ds18b20Configuration
|
||
}
|
||
|
||
// NewDS18B20Driver creates a new Gobot Driver for DS18B20 one wire temperature sensor.
|
||
//
|
||
// Params:
|
||
//
|
||
// a *Adaptor - the Adaptor to use with this Driver.
|
||
// serial number int - the serial number of the device, without the family code
|
||
//
|
||
// Optional params:
|
||
//
|
||
// onewire.WithFahrenheit()
|
||
// onewire.WithResolution(byte)
|
||
func NewDS18B20Driver(a Connector, serialNumber uint64, opts ...interface{}) *DS18B20Driver {
|
||
d := &DS18B20Driver{
|
||
driver: newDriver(a, "DS18B20", 0x28, serialNumber),
|
||
ds18b20Cfg: &ds18b20Configuration{
|
||
scaleUnit: func(input int) float32 { return float32(input) / 1000 }, // 1000:1 in °C
|
||
resolution: ds18b20DefaultResolution,
|
||
},
|
||
}
|
||
d.afterStart = d.initialize
|
||
d.beforeHalt = d.shutdown
|
||
for _, opt := range opts {
|
||
switch o := opt.(type) {
|
||
case optionApplier:
|
||
o.apply(d.driverCfg)
|
||
case ds18b20OptionApplier:
|
||
o.apply(d.ds18b20Cfg)
|
||
default:
|
||
panic(fmt.Sprintf("'%s' can not be applied on '%s'", opt, d.driverCfg.name))
|
||
}
|
||
}
|
||
return d
|
||
}
|
||
|
||
// WithFahrenheit substitute the default °C scaler by a scaler for °F
|
||
func WithFahrenheit() ds18b20OptionApplier {
|
||
// (1°C × 9/5) + 32 = 33,8°F
|
||
unitscaler := func(input int) float32 { return float32(input)/1000*9.0/5.0 + 32.0 }
|
||
return ds18b20UnitscalerOption{unitscaler: unitscaler}
|
||
}
|
||
|
||
// WithResolution substitute the default 12 bit resolution by the given one (9, 10, 11). The device will adjust
|
||
// the conversion time automatically. Each smaller resolution will decrease the conversion time by a factor of 2.
|
||
// Note: some devices are fixed in 12 bit mode only and do not support this feature (I/O error or just drop setting).
|
||
// WithConversionTime() is most likely supported.
|
||
func WithResolution(resolution uint8) ds18b20OptionApplier {
|
||
return ds18b20ResolutionOption(resolution)
|
||
}
|
||
|
||
// WithConversionTime substitute the default 750 ms by the given one (93, 187, 375, 750).
|
||
// Note: Devices will not adjust the resolution automatically. Some devices accept conversion time values different
|
||
// from common specification. E.g. 10...1000, which leads to real conversion time of conversionTime+50ms. This needs
|
||
// to be tested for your device and measured for your needs, e.g. by DebugConversionTime(0, 500, 5, true).
|
||
func WithConversionTime(conversionTime uint16) ds18b20OptionApplier {
|
||
return ds18b20ConversionTimeOption(conversionTime)
|
||
}
|
||
|
||
// Temperature returns the current temperature, in celsius degrees, if the default unit scaler is used.
|
||
func (d *DS18B20Driver) Temperature() (float32, error) {
|
||
d.mutex.Lock()
|
||
defer d.mutex.Unlock()
|
||
|
||
val, err := d.connection.ReadInteger("temperature")
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
|
||
return d.ds18b20Cfg.scaleUnit(val), nil
|
||
}
|
||
|
||
// Resolution returns the current resolution in bits (9, 10, 11, 12)
|
||
func (d *DS18B20Driver) Resolution() (uint8, error) {
|
||
d.mutex.Lock()
|
||
defer d.mutex.Unlock()
|
||
|
||
val, err := d.connection.ReadInteger("resolution")
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
|
||
if val < 9 || val > 12 {
|
||
return 0, fmt.Errorf("the read value '%d' is out of range (9, 10, 11, 12)", val)
|
||
}
|
||
|
||
return uint8(val), nil
|
||
}
|
||
|
||
// IsExternalPowered returns whether the device is external or parasitic powered
|
||
func (d *DS18B20Driver) IsExternalPowered() (bool, error) {
|
||
d.mutex.Lock()
|
||
defer d.mutex.Unlock()
|
||
|
||
val, err := d.connection.ReadInteger("ext_power")
|
||
if err != nil {
|
||
return false, err
|
||
}
|
||
|
||
return val > 0, nil
|
||
}
|
||
|
||
// ConversionTime returns the conversion time in ms
|
||
func (d *DS18B20Driver) ConversionTime() (uint16, error) {
|
||
d.mutex.Lock()
|
||
defer d.mutex.Unlock()
|
||
|
||
val, err := d.connection.ReadInteger("conv_time")
|
||
if err != nil {
|
||
return 0, err
|
||
}
|
||
|
||
if val < 0 || val > math.MaxUint16 {
|
||
return 0, fmt.Errorf("the read value '%d' is out of range (uint16)", val)
|
||
}
|
||
|
||
return uint16(val), nil
|
||
}
|
||
|
||
// DebugConversionTime try to set the conversion time and compare with real time to read temperature.
|
||
func (d *DS18B20Driver) DebugConversionTime(start, end uint16, stepwide uint16, skipInvalid bool) {
|
||
r, _ := d.Resolution()
|
||
fmt.Printf("\n---- Conversion time check for '%s'@%dbit %d..%d +%d ----\n",
|
||
d.connection.ID(), r, start, end, stepwide)
|
||
fmt.Println("|r1(err)\t|w(err)\t\t|r2(err)\t|T(err)\t\t|real\t\t|diff\t\t|")
|
||
fmt.Println("--------------------------------------------------------------------------------")
|
||
for ct := start; ct < end; ct += stepwide {
|
||
r1, e1 := d.ConversionTime()
|
||
ew := d.connection.WriteInteger("conv_time", int(ct))
|
||
r2, e2 := d.ConversionTime()
|
||
time.Sleep(100 * time.Millisecond) // relax the system
|
||
start := time.Now()
|
||
temp, err := d.Temperature()
|
||
dur := time.Since(start)
|
||
valid := ct == r2
|
||
if valid || !skipInvalid {
|
||
diff := dur - time.Duration(r2)*time.Millisecond
|
||
fmt.Printf("|%d(%t)\t|%d(%t)\t|%d(%t)\t|%v(%t)\t|%s\t|%s\t|\n",
|
||
r1, e1 != nil, ct, ew != nil, r2, e2 != nil, temp, err != nil, dur, diff)
|
||
}
|
||
}
|
||
}
|
||
|
||
func (d *DS18B20Driver) initialize() error {
|
||
if d.ds18b20Cfg.resolution != ds18b20DefaultResolution {
|
||
if err := d.connection.WriteInteger("resolution", int(d.ds18b20Cfg.resolution)); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
if d.ds18b20Cfg.conversionTime != ds18b20DefaultConversionTime {
|
||
return d.connection.WriteInteger("conv_time", int(d.ds18b20Cfg.conversionTime))
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (d *DS18B20Driver) shutdown() error {
|
||
if d.ds18b20Cfg.resolution != ds18b20DefaultResolution {
|
||
return d.connection.WriteInteger("resolution", ds18b20DefaultResolution)
|
||
}
|
||
|
||
if d.ds18b20Cfg.conversionTime != ds18b20DefaultConversionTime {
|
||
return d.connection.WriteInteger("conv_time", int(ds18b20DefaultConversionTime))
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (o ds18b20UnitscalerOption) apply(cfg *ds18b20Configuration) {
|
||
cfg.scaleUnit = o.unitscaler
|
||
}
|
||
|
||
func (o ds18b20ResolutionOption) apply(cfg *ds18b20Configuration) {
|
||
cfg.resolution = uint8(o)
|
||
}
|
||
|
||
func (o ds18b20ConversionTimeOption) apply(cfg *ds18b20Configuration) {
|
||
cfg.conversionTime = uint16(o)
|
||
}
|