1
0
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:
Thomas Kohler 2024-12-05 18:55:12 +01:00 committed by GitHub
commit 50b129b10f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 776 additions and 19 deletions

View File

@ -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

View 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)
}

View 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()
}

View 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)
}

View 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)
}
}

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

View File

@ -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

View File

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

View File

@ -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
View 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.

View File

@ -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

View File

@ -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())
}

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

View File

@ -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)