Skip to content

Commit

Permalink
onewire(ds18b20): introduce 1-wire device access by sysfs and temp dr…
Browse files Browse the repository at this point in the history
…iver
  • Loading branch information
gen2thomas committed Nov 5, 2024
1 parent b5f5ac6 commit 94cb412
Show file tree
Hide file tree
Showing 13 changed files with 648 additions and 19 deletions.
24 changes: 24 additions & 0 deletions adaptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,18 @@ type SpiSystemDevicer interface {
Close() error
}

// OneWireSystemDevicer is the interface to a 1-wire device at system level.
type OneWireSystemDevicer interface {
// ReadData reads byte data from the device
ReadData(command string, data []byte) error
// WriteData writes byte data to the device
WriteData(command string, data []byte) error
// ReadInteger reads an integer value from the device
ReadInteger(command string) (int, error)
// Close the 1-wire connection.
Close() error
}

// BusOperations are functions provided by a bus device, e.g. SPI, i2c.
type BusOperations interface {
// ReadByteData reads a byte from the given register of bus device.
Expand Down Expand Up @@ -213,6 +225,18 @@ type SpiOperations interface {
Close() error
}

// OneWireOperations are the wrappers around the actual functions used by the 1-wire device interface
type OneWireOperations interface {
// ReadData reads from the device
ReadData(command string, data []byte) error
// WriteData writes to the device
WriteData(command string, data []byte) error
// ReadInteger reads an integer value from the device
ReadInteger(command string) (int, error)
// Close the connection.
Close() error
}

// Adaptor is the interface that describes an adaptor in gobot
type Adaptor interface {
// Name returns the label for the Adaptor
Expand Down
141 changes: 141 additions & 0 deletions drivers/onewire/ds18b20_driver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package onewire

import (
"fmt"
)

// 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
}

// ds18b20UnitscalerOption is the type for applying another unit scaler to the configuration
type ds18b20UnitscalerOption struct {
unitscaler func(int) float32
}

type ds18b20ResolutionOption uint8

// 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 RGB LEDs.
//
// Params:
//
// a *Adaptor - the Adaptor to use with this Driver.
// count int - how many LEDs are in the array controlled by this driver.
// bright - the default brightness to apply for all LEDs (must be between 0 and 31).
//
// Optional params:
//
// onewire.WithBusNumber(int): bus to use with this driver, instead of the default bus 1.
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
},
}
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)
// Please also note, that each smaller resolution will decrease the conversion time by a factor of 2
func WithResolution(resolution byte) ds18b20OptionApplier {
return ds18b20ResolutionOption(resolution)
}

// 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 (93, 187, 375, 750)
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 < 93 || val > 750 {
return 0, fmt.Errorf("the read value '%d' is out of range (93, 187, 375, 750)", val)
}

return uint16(val), nil
}

func (o ds18b20UnitscalerOption) apply(cfg *ds18b20Configuration) {
cfg.scaleUnit = o.unitscaler
}

func (o ds18b20ResolutionOption) apply(cfg *ds18b20Configuration) {
cfg.resolution = uint8(o)
}
51 changes: 51 additions & 0 deletions drivers/onewire/onewire_connection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package onewire

import (
"sync"

"gobot.io/x/gobot/v2"
)

// onewireConnection is the common implementation of the 1-wire bus interface.
type onewireConnection struct {
onewireSystem gobot.OneWireSystemDevicer
mutex sync.Mutex
}

// NewConnection uses the given 1-wire system device and provides it as gobot.OneWireOperations
// and Implements gobot.BusOperations.
func NewConnection(onewireSystem gobot.OneWireSystemDevicer) *onewireConnection {
return &onewireConnection{onewireSystem: onewireSystem}
}

// Close connection to underlying SPI device. Implements functions of onewire.Connection respectively
// gobot.OneWireOperations.
func (c *onewireConnection) Close() error {
c.mutex.Lock()
defer c.mutex.Unlock()

return c.onewireSystem.Close()
}

// ReadData reads the data according the command, e.g. from the specified file on sysfs bus.
func (c *onewireConnection) ReadData(command string, data []byte) error {
c.mutex.Lock()
defer c.mutex.Unlock()

return c.onewireSystem.ReadData(command, data)
}

// WriteData writes the data according the command, e.g. to the specified file on sysfs bus.
func (c *onewireConnection) WriteData(command string, data []byte) error {
c.mutex.Lock()
defer c.mutex.Unlock()

return c.onewireSystem.WriteData(command, data)
}

func (c *onewireConnection) ReadInteger(command string) (int, error) {
c.mutex.Lock()
defer c.mutex.Unlock()

return c.onewireSystem.ReadInteger(command)
}
131 changes: 131 additions & 0 deletions drivers/onewire/onewire_driver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package onewire

import (
"fmt"
"log"
"sync"

"gobot.io/x/gobot/v2"
)

// Connector lets adaptors provide the drivers to get access to the 1-wire devices on platforms.
type Connector interface {
// GetOneWireConnection returns a connection to a 1-wire device with family code and serial number.
GetOneWireConnection(familyCode byte, serialNumber uint64) (Connection, error)
}

// Connection is a connection to a 1-wire device with family code and serial number on a specific bus, provided by
// an adaptor, usually just by calling the onewire package's GetOneWireConnection() function.
type Connection gobot.OneWireOperations

// optionApplier needs to be implemented by each configurable option type
type optionApplier interface {
apply(cfg *configuration)
}

// configuration contains all changeable attributes of the driver.
type configuration struct {
name string
familyCode byte
serialNumber uint64
}

// nameOption is the type for applying another name to the configuration
type nameOption string

// Driver implements the interface gobot.Driver.
type driver struct {
driverCfg *configuration
connector Connector
connection Connection
afterStart func() error
beforeHalt func() error
gobot.Commander
mutex *sync.Mutex // mutex often needed to ensure that write-read sequences are not interrupted
}

// newDriver creates a new generic and basic 1-wire gobot driver.
//
// Supported options:
//
// "WithName"
func newDriver(a Connector, name string, familyCode byte, serialNumber uint64, opts ...interface{}) *driver {
d := &driver{
driverCfg: &configuration{name: gobot.DefaultName(name), familyCode: familyCode, serialNumber: serialNumber},
connector: a,
afterStart: func() error { return nil },
beforeHalt: func() error { return nil },
Commander: gobot.NewCommander(),
mutex: &sync.Mutex{},
}

for _, opt := range opts {
switch o := opt.(type) {
case optionApplier:
o.apply(d.driverCfg)
default:
panic(fmt.Sprintf("'%s' can not be applied on '%s'", opt, d.driverCfg.name))
}
}

return d
}

// WithName is used to replace the default name of the driver.
func WithName(name string) optionApplier {
return nameOption(name)
}

// Name returns the name of the device.
func (d *driver) Name() string {
return d.driverCfg.name
}

// SetName sets the name of the device.
func (d *driver) SetName(name string) {
d.driverCfg.name = name
}

// Connection returns the connection of the device.
func (d *driver) Connection() gobot.Connection {
if conn, ok := d.connection.(gobot.Connection); ok {
return conn
}

log.Printf("%s has no gobot connection\n", d.driverCfg.name)
return nil
}

// Start initializes the device.
func (d *driver) Start() error {
d.mutex.Lock()
defer d.mutex.Unlock()

var err error
d.connection, err = d.connector.GetOneWireConnection(d.driverCfg.familyCode, d.driverCfg.serialNumber)
if err != nil {
return err
}

return d.afterStart()
}

// Halt halts the device.
func (d *driver) Halt() error {
d.mutex.Lock()
defer d.mutex.Unlock()

// currently there is nothing to do here for the driver, the connection is cached on adaptor side
// and will be closed on adaptor Finalize()

return d.beforeHalt()
}

func (o nameOption) String() string {
return "name option for 1-wire drivers"
}

// apply change the name in the configuration.
func (o nameOption) apply(c *configuration) {
c.name = string(o)
}
Loading

0 comments on commit 94cb412

Please sign in to comment.