mirror of
https://github.com/hybridgroup/gobot.git
synced 2025-04-24 13:48:49 +08:00
Merge 501a1e707cbadbb0825dab955a449974afad380a into 53d791a48aca4fe65f12235c032ad4adb3902746
This commit is contained in:
commit
50b129b10f
32
adaptor.go
32
adaptor.go
@ -176,6 +176,22 @@ type SpiSystemDevicer interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
// OneWireSystemDevicer is the interface to a 1-wire device at system level.
|
||||
type OneWireSystemDevicer interface {
|
||||
// ID returns the device id in the form "family code"-"serial number".
|
||||
ID() string
|
||||
// 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)
|
||||
// WriteInteger writes an integer value to the device
|
||||
WriteInteger(command string, val 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.
|
||||
@ -213,6 +229,22 @@ type SpiOperations interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
// OneWireOperations are the wrappers around the actual functions used by the 1-wire device interface
|
||||
type OneWireOperations interface {
|
||||
// ID returns the device id in the form "family code"-"serial number".
|
||||
ID() string
|
||||
// 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)
|
||||
// WriteInteger writes an integer value to the device
|
||||
WriteInteger(command string, val 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
|
||||
|
218
drivers/onewire/ds18b20_driver.go
Normal file
218
drivers/onewire/ds18b20_driver.go
Normal file
@ -0,0 +1,218 @@
|
||||
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)
|
||||
}
|
69
drivers/onewire/onewire_connection.go
Normal file
69
drivers/onewire/onewire_connection.go
Normal file
@ -0,0 +1,69 @@
|
||||
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}
|
||||
}
|
||||
|
||||
// ID returns the device id in the form "family code"-"serial number". Implements gobot.OneWireOperations.
|
||||
func (d *onewireConnection) ID() string {
|
||||
return d.onewireSystem.ID()
|
||||
}
|
||||
|
||||
// ReadData reads the data according the command, e.g. from the specified file on sysfs bus.
|
||||
// Implements gobot.OneWireOperations.
|
||||
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.
|
||||
// Implements gobot.OneWireOperations.
|
||||
func (c *onewireConnection) WriteData(command string, data []byte) error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
return c.onewireSystem.WriteData(command, data)
|
||||
}
|
||||
|
||||
// ReadInteger reads the value according the command, e.g. to the specified file on sysfs bus.
|
||||
// Implements gobot.OneWireOperations.
|
||||
func (c *onewireConnection) ReadInteger(command string) (int, error) {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
return c.onewireSystem.ReadInteger(command)
|
||||
}
|
||||
|
||||
// WriteInteger writes the value according the command, e.g. to the specified file on sysfs bus.
|
||||
// Implements gobot.OneWireOperations.
|
||||
func (c *onewireConnection) WriteInteger(command string, val int) error {
|
||||
c.mutex.Lock()
|
||||
defer c.mutex.Unlock()
|
||||
|
||||
return c.onewireSystem.WriteInteger(command, val)
|
||||
}
|
||||
|
||||
// Close connection to underlying 1-wire 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()
|
||||
}
|
131
drivers/onewire/onewire_driver.go
Normal file
131
drivers/onewire/onewire_driver.go
Normal file
@ -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)
|
||||
}
|
80
examples/tinkerboard_ds18b20.go
Normal file
80
examples/tinkerboard_ds18b20.go
Normal file
@ -0,0 +1,80 @@
|
||||
//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/onewire"
|
||||
"gobot.io/x/gobot/v2/platforms/tinkerboard"
|
||||
)
|
||||
|
||||
// Preparation: see /gobot/system/ONEWIRE.md and /gobot/platforms/tinkerboard/README.md
|
||||
//
|
||||
// Wiring:
|
||||
// PWR Tinkerboard: 1 (+3.3V, VCC), 6, 9, 14, 20 (GND)
|
||||
// 1-wire Tinkerboard: 7 (DQ) - resistor to VCC, ~1.5kOhm ... 5kOhm
|
||||
// DS18B20: 1 (GND), 2 (DQ), 3 (VDD, +3 ... 5.5V) for local power mode
|
||||
func main() {
|
||||
adaptor := tinkerboard.NewAdaptor()
|
||||
// resolution change not supported by all devices
|
||||
temp0 := onewire.NewDS18B20Driver(adaptor, 0x072261452f18, onewire.WithResolution(10))
|
||||
temp1 := onewire.NewDS18B20Driver(adaptor, 0x1465421f64ff, onewire.WithFahrenheit(), onewire.WithConversionTime(500))
|
||||
|
||||
work := func() {
|
||||
time0, err := temp0.ConversionTime()
|
||||
if err != nil {
|
||||
log.Printf("Err CT0: %v\n", err)
|
||||
}
|
||||
res0, err := temp0.Resolution()
|
||||
if err != nil {
|
||||
log.Printf("Err R0: %v\n", err)
|
||||
}
|
||||
log.Printf("Conversion time @%d bit for Temp 0: %d ms\n", res0, time0)
|
||||
|
||||
time1, err := temp1.ConversionTime()
|
||||
if err != nil {
|
||||
log.Printf("Err CT1: %v\n", err)
|
||||
}
|
||||
res1, err := temp1.Resolution()
|
||||
if err != nil {
|
||||
log.Printf("Err R1: %v\n", err)
|
||||
}
|
||||
log.Printf("Conversion time @%d bit for Temp 0: %d ms\n", res1, time1)
|
||||
|
||||
gobot.Every(10*(time.Duration(time0))*time.Millisecond, func() {
|
||||
t0, err := temp0.Temperature()
|
||||
if err != nil {
|
||||
log.Printf("Err Temp 0: %v\n", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Temp 0: %2.1f °C\n", t0)
|
||||
})
|
||||
|
||||
gobot.Every(10*(time.Duration(time1))*time.Millisecond, func() {
|
||||
t1, err := temp1.Temperature()
|
||||
if err != nil {
|
||||
log.Printf("Err Temp 1: %v\n", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Temp 1: %2.3f °F\n", t1)
|
||||
})
|
||||
}
|
||||
|
||||
robot := gobot.NewRobot("onewireBot",
|
||||
[]gobot.Connection{adaptor},
|
||||
[]gobot.Device{temp0, temp1},
|
||||
work,
|
||||
)
|
||||
|
||||
if err := robot.Start(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
77
platforms/adaptors/onewirebusadaptor.go
Normal file
77
platforms/adaptors/onewirebusadaptor.go
Normal file
@ -0,0 +1,77 @@
|
||||
package adaptors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
multierror "github.com/hashicorp/go-multierror"
|
||||
|
||||
"gobot.io/x/gobot/v2/drivers/onewire"
|
||||
"gobot.io/x/gobot/v2/system"
|
||||
)
|
||||
|
||||
// OneWireBusAdaptor is a adaptor for the 1-wire bus, normally used for composition in platforms.
|
||||
// note: currently only one controller is supported by most platforms, but it would be possible to activate more,
|
||||
// see https://forums.raspberrypi.com/viewtopic.php?t=65137
|
||||
type OneWireBusAdaptor struct {
|
||||
sys *system.Accesser
|
||||
mutex sync.Mutex
|
||||
connections map[string]onewire.Connection
|
||||
}
|
||||
|
||||
// NewOneWireBusAdaptor provides the access to 1-wire devices of the board.
|
||||
func NewOneWireBusAdaptor(sys *system.Accesser) *OneWireBusAdaptor {
|
||||
a := &OneWireBusAdaptor{sys: sys}
|
||||
return a
|
||||
}
|
||||
|
||||
// Connect prepares the connection to 1-wire devices.
|
||||
func (a *OneWireBusAdaptor) Connect() error {
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
|
||||
a.connections = make(map[string]onewire.Connection)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Finalize closes all 1-wire connections.
|
||||
func (a *OneWireBusAdaptor) Finalize() error {
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
|
||||
var err error
|
||||
for _, con := range a.connections {
|
||||
if con != nil {
|
||||
if e := con.Close(); e != nil {
|
||||
err = multierror.Append(err, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
a.connections = nil
|
||||
return err
|
||||
}
|
||||
|
||||
// GetOneWireConnection returns a 1-wire connection to a device with the given family code and serial number.
|
||||
func (a *OneWireBusAdaptor) GetOneWireConnection(familyCode byte, serialNumber uint64) (onewire.Connection, error) {
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
|
||||
if a.connections == nil {
|
||||
return nil, fmt.Errorf("not connected")
|
||||
}
|
||||
|
||||
id := fmt.Sprintf("%d_%d", familyCode, serialNumber)
|
||||
|
||||
con := a.connections[id]
|
||||
if con == nil {
|
||||
var err error
|
||||
dev, err := a.sys.NewOneWireDevice(familyCode, serialNumber)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
con = onewire.NewConnection(dev)
|
||||
a.connections[id] = con
|
||||
}
|
||||
|
||||
return con, nil
|
||||
}
|
@ -14,8 +14,11 @@ Tested OS:
|
||||
* [Debian TinkerOS](https://github.com/TinkerBoard/debian_kernel/releases)
|
||||
* [armbian](https://www.armbian.com/tinkerboard/) with Debian or Ubuntu
|
||||
|
||||
> The latest "Tinker Board Debian 10 V3.0.11" is official discontinued. Nevertheless it is well tested with gobot. There
|
||||
> is a known i2c issue with the Kernel 4.4.194 if using block reads. armbian is known to work in this area.
|
||||
> The latest "Tinker Board Debian 10 V3.0.11" is official discontinued. Nevertheless it is tested with gobot. There
|
||||
> is a known i2c issue with the Kernel 4.4.194 if using block reads. armbian is known to work in this area. We recommend
|
||||
> to use "armbian bookworm minimal", because it is used for the latest development steps of gobot.
|
||||
|
||||
## Configuration steps for the OS
|
||||
|
||||
### System access and configuration basics
|
||||
|
||||
@ -26,7 +29,7 @@ Note that these configuration steps must be performed on the Tinker Board itself
|
||||
Board via SSH (option "-4" is used to force IPv4, which is needed for some versions of TinkerOS):
|
||||
|
||||
```sh
|
||||
ssh -4 linaro@192.168.1.xxx
|
||||
ssh -4 <user>@192.168.1.xxx # linaro@192.168.1.xxx
|
||||
```
|
||||
|
||||
### Enabling hardware drivers
|
||||
@ -35,19 +38,20 @@ Not all drivers are enabled by default. You can have a look at the configuration
|
||||
your system:
|
||||
|
||||
```sh
|
||||
cat /boot/config.txt
|
||||
cat /boot/armbianEnv.txt #/boot/config.txt
|
||||
```
|
||||
|
||||
This file can be modified by "vi" or "nano", it is self explanatory:
|
||||
|
||||
```sh
|
||||
sudo vi /boot/config.txt
|
||||
sudo vi /boot/armbianEnv.txt
|
||||
```
|
||||
|
||||
Newer versions of Tinker Board provide an user interface for configuration with:
|
||||
Newer versions of OS provide an user interface for configuration with:
|
||||
|
||||
```sh
|
||||
sudo tinker-config
|
||||
sudo apt install armbian-config
|
||||
sudo armbian-config # tinker-config
|
||||
```
|
||||
|
||||
After configuration was changed, an reboot is necessary.
|
||||
@ -68,17 +72,17 @@ sudo groupadd -f --system gpio
|
||||
|
||||
If you already have a "gpio" group, you can skip to the next step.
|
||||
|
||||
#### Add the "linaro" user to the new "gpio" group
|
||||
#### Add the user to the new "gpio" group (TinkerOS only)
|
||||
|
||||
Add the user "linaro" to be a member of the Linux group named "gpio" by running the following command:
|
||||
Add the user to be a member of the Linux group named "gpio" by running the following command:
|
||||
|
||||
```sh
|
||||
sudo usermod -a -G gpio linaro
|
||||
sudo usermod -a -G gpio <user>
|
||||
```
|
||||
|
||||
If you already have added the "gpio" group, you can skip to the next step.
|
||||
|
||||
#### Add a "udev" rules file for gpio
|
||||
#### Add a "udev" rules file for gpio (TinkerOS only)
|
||||
|
||||
Create a new "udev" rules file for the GPIO on the Tinker Board by running the following command:
|
||||
|
||||
@ -108,17 +112,17 @@ Create a Linux group named "i2c" by running the following command:
|
||||
sudo groupadd -f --system i2c
|
||||
```
|
||||
|
||||
#### Add the "linaro" user to the new "i2c" group
|
||||
#### Add the user to the new "i2c" group
|
||||
|
||||
If you already have added the "i2c" group, you can skip to the next step.
|
||||
|
||||
Add the user "linaro" to be a member of the Linux group named "i2c" by running the following command:
|
||||
Add the user to be a member of the Linux group named "i2c" by running the following command:
|
||||
|
||||
```sh
|
||||
sudo usermod -a -G gpio linaro
|
||||
sudo usermod -a -G gpio <user>
|
||||
```
|
||||
|
||||
#### Add a "udev" rules file for I2C
|
||||
#### Add a "udev" rules file for I2C (TinkerOS only)
|
||||
|
||||
Create a new "udev" rules file for the I2C on the Tinker Board by running the following command:
|
||||
|
||||
@ -159,8 +163,8 @@ Once you have compiled your code, you can upload your program and execute it on
|
||||
using the `scp` and `ssh` commands like this:
|
||||
|
||||
```sh
|
||||
scp tinkerboard_blink linaro@192.168.1.xxx:/home/linaro/
|
||||
ssh -t linaro@192.168.1.xxx "./tinkerboard_blink"
|
||||
scp tinkerboard_blink <user>@192.168.1.xxx:/home/<user>/
|
||||
ssh -t <user>@192.168.1.xxx "./tinkerboard_blink"
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
@ -54,6 +54,7 @@ type Adaptor struct {
|
||||
*adaptors.PWMPinsAdaptor
|
||||
*adaptors.I2cBusAdaptor
|
||||
*adaptors.SpiBusAdaptor
|
||||
*adaptors.OneWireBusAdaptor
|
||||
}
|
||||
|
||||
// NewAdaptor creates a Tinkerboard Adaptor
|
||||
@ -96,6 +97,7 @@ func NewAdaptor(opts ...interface{}) *Adaptor {
|
||||
a.I2cBusAdaptor = adaptors.NewI2cBusAdaptor(sys, a.validateI2cBusNumber, defaultI2cBusNumber)
|
||||
a.SpiBusAdaptor = adaptors.NewSpiBusAdaptor(sys, a.validateSpiBusNumber, defaultSpiBusNumber, defaultSpiChipNumber,
|
||||
defaultSpiMode, defaultSpiBitsNumber, defaultSpiMaxSpeed)
|
||||
a.OneWireBusAdaptor = adaptors.NewOneWireBusAdaptor(sys)
|
||||
return a
|
||||
}
|
||||
|
||||
@ -110,6 +112,10 @@ func (a *Adaptor) Connect() error {
|
||||
a.mutex.Lock()
|
||||
defer a.mutex.Unlock()
|
||||
|
||||
if err := a.OneWireBusAdaptor.Connect(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := a.SpiBusAdaptor.Connect(); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -150,6 +156,10 @@ func (a *Adaptor) Finalize() error {
|
||||
if e := a.SpiBusAdaptor.Finalize(); e != nil {
|
||||
err = multierror.Append(err, e)
|
||||
}
|
||||
|
||||
if e := a.OneWireBusAdaptor.Finalize(); e != nil {
|
||||
err = multierror.Append(err, e)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# GPIOs
|
||||
|
||||
This document describes some basics for developers. This is useful to understand programming in gobot's [digital pin driver](digital_pin.go).
|
||||
This document describes some basics for developers. This is useful to understand programming in gobot's [digital pin driver](./digitalpin_access.go).
|
||||
|
||||
## GPIOs with sysfs
|
||||
|
||||
|
59
system/ONEWIRE.md
Normal file
59
system/ONEWIRE.md
Normal file
@ -0,0 +1,59 @@
|
||||
# 1-wire bus
|
||||
|
||||
This document describes some basics for developers. This is useful to understand programming in gobot's [1-wire driver](./onewiredevice_sysfs.go).
|
||||
|
||||
## 1-wire with sysfs
|
||||
|
||||
If the 1-wire bus is enabled on the board, the bus data can be connected to one pin of the used platform. The enabling
|
||||
activates the Kernel drivers for common devices (family drivers), which are than mapped to the sysfs, see
|
||||
<https://www.kernel.org/doc/Documentation/w1/w1.generic>.
|
||||
|
||||
## Check available 1-wire devices
|
||||
|
||||
Example for Tinkerboard (RK3288) with armbian and 3 connected temperature sensors DS18B20:
|
||||
|
||||
```sh
|
||||
ls -la /sys/bus/w1/devices/
|
||||
insgesamt 0
|
||||
drwxr-xr-x 2 root root 0 29. Okt 08:58 .
|
||||
drwxr-xr-x 4 root root 0 29. Okt 08:58 ..
|
||||
lrwxrwxrwx 1 root root 0 31. Okt 07:55 28-072261452f18 -> ../../../devices/w1_bus_master1/28-072261452f18
|
||||
lrwxrwxrwx 1 root root 0 31. Okt 07:55 28-08225482b0de -> ../../../devices/w1_bus_master1/28-08225482b0de
|
||||
lrwxrwxrwx 1 root root 0 31. Okt 07:55 28-1e40710a6461 -> ../../../devices/w1_bus_master1/28-1e40710a6461
|
||||
lrwxrwxrwx 1 root root 0 29. Okt 08:58 w1_bus_master1 -> ../../../devices/w1_bus_master1
|
||||
```
|
||||
|
||||
Within a device folder different files are available for typical access.
|
||||
|
||||
```sh
|
||||
ls -la /sys/bus/w1/devices/28-072261452f18/
|
||||
insgesamt 0
|
||||
drwxr-xr-x 4 root root 0 29. Okt 08:58 .
|
||||
drwxr-xr-x 6 root root 0 29. Okt 08:58 ..
|
||||
-rw-r--r-- 1 root root 4096 31. Okt 07:57 alarms
|
||||
-rw-r--r-- 1 root root 4096 31. Okt 07:57 conv_time
|
||||
lrwxrwxrwx 1 root root 0 31. Okt 07:57 driver -> ../../../bus/w1/drivers/w1_slave_driver
|
||||
--w------- 1 root root 4096 31. Okt 07:57 eeprom_cmd
|
||||
-r--r--r-- 1 root root 4096 31. Okt 07:57 ext_power
|
||||
-rw-r--r-- 1 root root 4096 31. Okt 07:57 features
|
||||
drwxr-xr-x 3 root root 0 29. Okt 08:58 hwmon
|
||||
-r--r--r-- 1 root root 4096 31. Okt 07:57 id
|
||||
-r--r--r-- 1 root root 4096 31. Okt 07:57 name
|
||||
drwxr-xr-x 2 root root 0 31. Okt 07:13 power
|
||||
-rw-r--r-- 1 root root 4096 31. Okt 11:10 resolution
|
||||
lrwxrwxrwx 1 root root 0 29. Okt 08:58 subsystem -> ../../../bus/w1
|
||||
-r--r--r-- 1 root root 4096 31. Okt 07:57 temperature
|
||||
-rw-r--r-- 1 root root 4096 29. Okt 08:58 uevent
|
||||
-rw-r--r-- 1 root root 4096 31. Okt 07:57 w1_slave
|
||||
```
|
||||
|
||||
This files depends on the family driver.
|
||||
|
||||
## Different access levels and modes
|
||||
|
||||
Currently gobot supports only direct access to the devices in automatic search mode of the controller device. The
|
||||
implementation is similar to the sysfs access of the analog pin driver.
|
||||
|
||||
E.g. if the cyclic device search should be avoided, the access to the controller device is needed, see Kernel
|
||||
documentation. If this will be implemented in the future, have in mind that more than one controller devices are
|
||||
possible. The gobot's 1-wire architecture can be changed then similar to SPI or I2C.
|
@ -45,6 +45,9 @@ func startEdgePolling(
|
||||
for {
|
||||
select {
|
||||
case <-quitChan:
|
||||
if !firstLoopDone {
|
||||
wg.Done()
|
||||
}
|
||||
return
|
||||
default:
|
||||
// note: pure reading takes between 30us and 1ms on rasperry Pi1, typically 50us, with sysfs also 500us
|
||||
|
@ -149,7 +149,10 @@ func (fs *MockFilesystem) stat(name string) (os.FileInfo, error) {
|
||||
log.Println("A")
|
||||
return nil, err
|
||||
}
|
||||
defer os.Remove(tmpFile.Name())
|
||||
defer func() {
|
||||
tmpFile.Close()
|
||||
os.Remove(tmpFile.Name())
|
||||
}()
|
||||
|
||||
return os.Stat(tmpFile.Name())
|
||||
}
|
||||
|
61
system/onewiredevice_sysfs.go
Normal file
61
system/onewiredevice_sysfs.go
Normal file
@ -0,0 +1,61 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"path"
|
||||
)
|
||||
|
||||
type onewireDeviceSysfs struct {
|
||||
id string
|
||||
sysfsPath string
|
||||
sfa *sysfsFileAccess
|
||||
}
|
||||
|
||||
func newOneWireDeviceSysfs(sfa *sysfsFileAccess, id string) *onewireDeviceSysfs {
|
||||
p := &onewireDeviceSysfs{
|
||||
id: id,
|
||||
sysfsPath: path.Join("/sys/bus/w1/devices", id),
|
||||
sfa: sfa,
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// ID returns the device id in the form "family code"-"serial number". Implements gobot.OneWireSystemDevicer.
|
||||
func (o *onewireDeviceSysfs) ID() string {
|
||||
return o.id
|
||||
}
|
||||
|
||||
// ReadData reads from the sysfs path specified by the command. Implements gobot.OneWireSystemDevicer.
|
||||
func (o *onewireDeviceSysfs) ReadData(command string, data []byte) error {
|
||||
p := path.Join(o.sysfsPath, command)
|
||||
buf, err := o.sfa.read(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
copy(data, buf)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteData writes to the path specified by the command. Implements gobot.OneWireSystemDevicer.
|
||||
func (o *onewireDeviceSysfs) WriteData(command string, data []byte) error {
|
||||
p := path.Join(o.sysfsPath, command)
|
||||
return o.sfa.write(p, data)
|
||||
}
|
||||
|
||||
// ReadInteger reads an integer value from the device. Implements gobot.OneWireSystemDevicer.
|
||||
func (o *onewireDeviceSysfs) ReadInteger(command string) (int, error) {
|
||||
p := path.Join(o.sysfsPath, command)
|
||||
return o.sfa.readInteger(p)
|
||||
}
|
||||
|
||||
// WriteInteger writes an integer value to the device. Implements gobot.OneWireSystemDevicer.
|
||||
func (o *onewireDeviceSysfs) WriteInteger(command string, val int) error {
|
||||
p := path.Join(o.sysfsPath, command)
|
||||
return o.sfa.writeInteger(p, val)
|
||||
}
|
||||
|
||||
// Close the 1-wire connection. Implements gobot.OneWireSystemDevicer.
|
||||
func (o *onewireDeviceSysfs) Close() error {
|
||||
// currently nothing to do here - the file descriptors will be closed immediately after read/write
|
||||
return nil
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"unsafe"
|
||||
|
||||
@ -151,6 +152,15 @@ func (a *Accesser) NewSpiDevice(busNum, chipNum, mode, bits int, maxSpeed int64)
|
||||
return a.spiAccess.createDevice(busNum, chipNum, mode, bits, maxSpeed)
|
||||
}
|
||||
|
||||
// NewOneWireDevice returns a new 1-wire device with the given parameters.
|
||||
// note: this is a basic implementation without using the possibilities of bus controller
|
||||
// it depends on automatic device search, see https://www.kernel.org/doc/Documentation/w1/w1.generic
|
||||
func (a *Accesser) NewOneWireDevice(familyCode byte, serialNumber uint64) (gobot.OneWireSystemDevicer, error) {
|
||||
sfa := &sysfsFileAccess{fs: a.fs, readBufLen: 200}
|
||||
deviceID := fmt.Sprintf("%02x-%012x", familyCode, serialNumber)
|
||||
return newOneWireDeviceSysfs(sfa, deviceID), nil
|
||||
}
|
||||
|
||||
// OpenFile opens file of given name from native or the mocked file system
|
||||
func (a *Accesser) OpenFile(name string, flag int, perm os.FileMode) (File, error) {
|
||||
return a.fs.openFile(name, flag, perm)
|
||||
|
Loading…
x
Reference in New Issue
Block a user