Skip to content

Commit 9dfcfc5

Browse files
authored
onewire(ds18b20): introduce 1-wire device access by sysfs and temp driver (hybridgroup#1091)
1 parent 53d791a commit 9dfcfc5

24 files changed

+1640
-23
lines changed

Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,10 @@ examples_fmt_fix:
7070

7171
$(EXAMPLES):
7272
ifeq ($(CHECK),ON)
73-
go vet ./$@
73+
go vet -tags libusb ./$@
7474
else ifeq ($(CHECK),FMT)
7575
gofumpt -l -w ./$@
7676
golangci-lint run ./$@ --fix --build-tags example,libusb --disable forcetypeassert --disable noctx
7777
else
78-
go build -o /tmp/gobot_examples/$@ ./$@
78+
go build -tags libusb -o /tmp/gobot_examples/$@ ./$@
7979
endif

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,12 @@ a shared set of drivers provided using the `gobot/drivers/spi` package:
408408
- MFRC522 RFID Card Reader
409409
- SSD1306 OLED Display Controller
410410

411+
Support for devices that use 1-wire bus with Linux Kernel support (w1-gpio) have
412+
a shared set of drivers provided using the `gobot/drivers/onewire` package:
413+
414+
- [1-wire](https://en.wikipedia.org/wiki/1-Wire) <=> [Drivers](https://github.com/hybridgroup/gobot/blob/release/drivers/onewire)
415+
- DS18B20 Temperature Sensor
416+
411417
## API
412418

413419
Gobot includes a RESTful API to query the status of any robot running within a group, including the connection and

adaptor.go

+32
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,22 @@ type SpiSystemDevicer interface {
176176
Close() error
177177
}
178178

179+
// OneWireSystemDevicer is the interface to a 1-wire device at system level.
180+
type OneWireSystemDevicer interface {
181+
// ID returns the device id in the form "family code"-"serial number".
182+
ID() string
183+
// ReadData reads byte data from the device
184+
ReadData(command string, data []byte) error
185+
// WriteData writes byte data to the device
186+
WriteData(command string, data []byte) error
187+
// ReadInteger reads an integer value from the device
188+
ReadInteger(command string) (int, error)
189+
// WriteInteger writes an integer value to the device
190+
WriteInteger(command string, val int) error
191+
// Close the 1-wire connection.
192+
Close() error
193+
}
194+
179195
// BusOperations are functions provided by a bus device, e.g. SPI, i2c.
180196
type BusOperations interface {
181197
// ReadByteData reads a byte from the given register of bus device.
@@ -213,6 +229,22 @@ type SpiOperations interface {
213229
Close() error
214230
}
215231

232+
// OneWireOperations are the wrappers around the actual functions used by the 1-wire device interface
233+
type OneWireOperations interface {
234+
// ID returns the device id in the form "family code"-"serial number".
235+
ID() string
236+
// ReadData reads from the device
237+
ReadData(command string, data []byte) error
238+
// WriteData writes to the device
239+
WriteData(command string, data []byte) error
240+
// ReadInteger reads an integer value from the device
241+
ReadInteger(command string) (int, error)
242+
// WriteInteger writes an integer value to the device
243+
WriteInteger(command string, val int) error
244+
// Close the connection.
245+
Close() error
246+
}
247+
216248
// Adaptor is the interface that describes an adaptor in gobot
217249
type Adaptor interface {
218250
// Name returns the label for the Adaptor

driver.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ package gobot
44
type Driver interface {
55
// Name returns the label for the Driver
66
Name() string
7-
// SetName sets the label for the Driver.
8-
// Please use options [aio.WithName, ble.WithName, gpio.WithName or serial.WithName] instead.
7+
// SetName sets the label for the Driver (deprecated, use WithName() instead).
8+
// Please use options [aio.WithName, ble.WithName, gpio.WithName, onewire.WithName or serial.WithName] instead.
99
SetName(s string)
1010
// Start initiates the Driver
1111
Start() error

drivers/i2c/helpers_test.go

+8
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ func (t *i2cTestAdaptor) ReadByte() (byte, error) {
6666
return 0, err
6767
}
6868
val := bytes[0]
69+
6970
return val, nil
7071
}
7172

@@ -80,6 +81,7 @@ func (t *i2cTestAdaptor) ReadByteData(reg uint8) (uint8, error) {
8081
return 0, err
8182
}
8283
val := bytes[0]
84+
8385
return val, nil
8486
}
8587

@@ -98,6 +100,7 @@ func (t *i2cTestAdaptor) ReadWordData(reg uint8) (uint16, error) {
98100
return 0, fmt.Errorf("Buffer underrun")
99101
}
100102
low, high := bytes[0], bytes[1]
103+
101104
return (uint16(high) << 8) | uint16(low), nil
102105
}
103106

@@ -107,6 +110,7 @@ func (t *i2cTestAdaptor) ReadBlockData(reg uint8, b []byte) error {
107110
if err := t.writeBytes([]byte{reg}); err != nil {
108111
return err
109112
}
113+
110114
return t.readBytes(b)
111115
}
112116

@@ -153,6 +157,7 @@ func (t *i2cTestAdaptor) WriteBytes(b []byte) error {
153157
if len(b) > 32 {
154158
b = b[:32]
155159
}
160+
156161
return t.writeBytes(b)
157162
}
158163

@@ -162,6 +167,7 @@ func (t *i2cTestAdaptor) GetI2cConnection(address int, bus int) (Connection, err
162167
}
163168
t.bus = bus
164169
t.address = address
170+
165171
return t, nil
166172
}
167173

@@ -194,6 +200,7 @@ func (t *i2cTestAdaptor) readBytes(b []byte) error {
194200
if n != len(b) {
195201
return fmt.Errorf("Read %v bytes from device by i2c helpers, expected %v", n, len(b))
196202
}
203+
197204
return nil
198205
}
199206

@@ -204,5 +211,6 @@ func (t *i2cTestAdaptor) writeBytes(b []byte) error {
204211
if err != nil {
205212
return err
206213
}
214+
207215
return nil
208216
}

drivers/onewire/README.md

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# 1-wire
2+
3+
This package provides drivers for [1-wire](https://en.wikipedia.org/wiki/1-Wire) devices supported by Linux Kernel w1-gpio
4+
drivers. It must be used along with an adaptor such as [Tinker Board](https://gobot.io/documentation/platforms/tinkerboard/)
5+
that supports the needed interfaces for 1-wire devices.
6+
7+
## Getting Started
8+
9+
Please refer to the main [README.md](https://github.com/hybridgroup/gobot/blob/release/README.md)
10+
11+
## Hardware Support
12+
13+
Gobot has a extensible system for connecting to hardware devices. The following 1-wire devices are currently supported:
14+
15+
- DS18B20 Temperature Sensor

drivers/onewire/ds18b20_driver.go

+227
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
package onewire
2+
3+
import (
4+
"fmt"
5+
"math"
6+
"time"
7+
)
8+
9+
const (
10+
ds18b20DefaultResolution = 12
11+
ds18b20DefaultConversionTime = 750
12+
13+
temperatureCommand = "temperature"
14+
extPowerCommand = "ext_power"
15+
resolutionCommand = "resolution"
16+
convTimeCommand = "conv_time"
17+
)
18+
19+
// ds18b20OptionApplier needs to be implemented by each configurable option type
20+
type ds18b20OptionApplier interface {
21+
apply(cfg *ds18b20Configuration)
22+
}
23+
24+
// ds18b20Configuration contains all changeable attributes of the driver.
25+
type ds18b20Configuration struct {
26+
scaleUnit func(int) float32
27+
resolution uint8
28+
conversionTime uint16
29+
}
30+
31+
// ds18b20UnitscalerOption is the type for applying another unit scaler to the configuration
32+
type ds18b20UnitscalerOption struct {
33+
unitscaler func(int) float32
34+
}
35+
36+
type ds18b20ResolutionOption uint8
37+
38+
type ds18b20ConversionTimeOption uint16
39+
40+
// DS18B20Driver is a driver for the DS18B20 1-wire temperature sensor.
41+
type DS18B20Driver struct {
42+
*driver
43+
ds18b20Cfg *ds18b20Configuration
44+
}
45+
46+
// NewDS18B20Driver creates a new Gobot Driver for DS18B20 one wire temperature sensor.
47+
//
48+
// Params:
49+
//
50+
// a *Adaptor - the Adaptor to use with this Driver.
51+
// serial number int - the serial number of the device, without the family code
52+
//
53+
// Optional params:
54+
//
55+
// onewire.WithFahrenheit()
56+
// onewire.WithResolution(byte)
57+
// onewire.WithConversionTime(uint16)
58+
func NewDS18B20Driver(a connector, serialNumber uint64, opts ...interface{}) *DS18B20Driver {
59+
d := &DS18B20Driver{
60+
driver: newDriver(a, "DS18B20", 0x28, serialNumber),
61+
ds18b20Cfg: &ds18b20Configuration{
62+
scaleUnit: func(input int) float32 { return float32(input) / 1000 }, // 1000:1 in °C
63+
resolution: ds18b20DefaultResolution,
64+
conversionTime: ds18b20DefaultConversionTime,
65+
},
66+
}
67+
d.afterStart = d.initialize
68+
d.beforeHalt = d.shutdown
69+
for _, opt := range opts {
70+
switch o := opt.(type) {
71+
case optionApplier:
72+
o.apply(d.driverCfg)
73+
case ds18b20OptionApplier:
74+
o.apply(d.ds18b20Cfg)
75+
default:
76+
panic(fmt.Sprintf("'%s' can not be applied on '%s'", opt, d.driverCfg.name))
77+
}
78+
}
79+
return d
80+
}
81+
82+
// WithFahrenheit substitute the default °C scaler by a scaler for °F
83+
func WithFahrenheit() ds18b20OptionApplier {
84+
// (1°C × 9/5) + 32 = 33,8°F
85+
unitscaler := func(input int) float32 { return float32(input)/1000*9.0/5.0 + 32.0 }
86+
return ds18b20UnitscalerOption{unitscaler: unitscaler}
87+
}
88+
89+
// WithResolution substitute the default 12 bit resolution by the given one (9, 10, 11). The device will adjust
90+
// the conversion time automatically. Each smaller resolution will decrease the conversion time by a factor of 2.
91+
// Note: some devices are fixed in 12 bit mode only and do not support this feature (I/O error or just ignore it).
92+
// WithConversionTime() is most likely supported.
93+
func WithResolution(resolution uint8) ds18b20OptionApplier {
94+
return ds18b20ResolutionOption(resolution)
95+
}
96+
97+
// WithConversionTime substitute the default 750 ms by the given one (93, 187, 375, 750).
98+
// Note: Devices will not adjust the resolution automatically. Some devices accept conversion time values different
99+
// from common specification. E.g. 10...1000, which leads to real conversion time of conversionTime+50ms. This needs
100+
// to be tested for your device and measured for your needs, e.g. by DebugConversionTime(0, 500, 5, true).
101+
func WithConversionTime(conversionTime uint16) ds18b20OptionApplier {
102+
return ds18b20ConversionTimeOption(conversionTime)
103+
}
104+
105+
// Temperature returns the current temperature, in celsius degrees, if the default unit scaler is used.
106+
func (d *DS18B20Driver) Temperature() (float32, error) {
107+
d.mutex.Lock()
108+
defer d.mutex.Unlock()
109+
110+
val, err := d.connection.ReadInteger(temperatureCommand)
111+
if err != nil {
112+
return 0, err
113+
}
114+
115+
return d.ds18b20Cfg.scaleUnit(val), nil
116+
}
117+
118+
// Resolution returns the current resolution in bits (9, 10, 11, 12)
119+
func (d *DS18B20Driver) Resolution() (uint8, error) {
120+
d.mutex.Lock()
121+
defer d.mutex.Unlock()
122+
123+
val, err := d.connection.ReadInteger(resolutionCommand)
124+
if err != nil {
125+
return 0, err
126+
}
127+
128+
if val < 9 || val > 12 {
129+
return 0, fmt.Errorf("the read value '%d' is out of range (9, 10, 11, 12)", val)
130+
}
131+
132+
return uint8(val), nil
133+
}
134+
135+
// IsExternalPowered returns whether the device is external or parasitic powered
136+
func (d *DS18B20Driver) IsExternalPowered() (bool, error) {
137+
d.mutex.Lock()
138+
defer d.mutex.Unlock()
139+
140+
val, err := d.connection.ReadInteger(extPowerCommand)
141+
if err != nil {
142+
return false, err
143+
}
144+
145+
return val > 0, nil
146+
}
147+
148+
// ConversionTime returns the conversion time in ms
149+
func (d *DS18B20Driver) ConversionTime() (uint16, error) {
150+
d.mutex.Lock()
151+
defer d.mutex.Unlock()
152+
153+
val, err := d.connection.ReadInteger(convTimeCommand)
154+
if err != nil {
155+
return 0, err
156+
}
157+
158+
if val < 0 || val > math.MaxUint16 {
159+
return 0, fmt.Errorf("the read value '%d' is out of range (uint16)", val)
160+
}
161+
162+
return uint16(val), nil
163+
}
164+
165+
// DebugConversionTime try to set the conversion time and compare with real time to read temperature.
166+
func (d *DS18B20Driver) DebugConversionTime(start, end uint16, stepwide uint16, skipInvalid bool) {
167+
r, _ := d.Resolution()
168+
fmt.Printf("\n---- Conversion time check for '%s'@%dbit %d..%d +%d ----\n",
169+
d.connection.ID(), r, start, end, stepwide)
170+
fmt.Println("|r1(err)\t|w(err)\t\t|r2(err)\t|T(err)\t\t|real\t\t|diff\t\t|")
171+
fmt.Println("--------------------------------------------------------------------------------")
172+
for ct := start; ct < end; ct += stepwide {
173+
r1, e1 := d.ConversionTime()
174+
ew := d.connection.WriteInteger(convTimeCommand, int(ct))
175+
r2, e2 := d.ConversionTime()
176+
time.Sleep(100 * time.Millisecond) // relax the system
177+
start := time.Now()
178+
temp, err := d.Temperature()
179+
dur := time.Since(start)
180+
valid := ct == r2
181+
if valid || !skipInvalid {
182+
diff := dur - time.Duration(r2)*time.Millisecond
183+
fmt.Printf("|%d(%t)\t|%d(%t)\t|%d(%t)\t|%v(%t)\t|%s\t|%s\t|\n",
184+
r1, e1 != nil, ct, ew != nil, r2, e2 != nil, temp, err != nil, dur, diff)
185+
}
186+
}
187+
}
188+
189+
func (d *DS18B20Driver) initialize() error {
190+
if d.ds18b20Cfg.resolution != ds18b20DefaultResolution {
191+
if err := d.connection.WriteInteger(resolutionCommand, int(d.ds18b20Cfg.resolution)); err != nil {
192+
return err
193+
}
194+
}
195+
196+
if d.ds18b20Cfg.conversionTime != ds18b20DefaultConversionTime {
197+
return d.connection.WriteInteger(convTimeCommand, int(d.ds18b20Cfg.conversionTime))
198+
}
199+
200+
return nil
201+
}
202+
203+
func (d *DS18B20Driver) shutdown() error {
204+
if d.ds18b20Cfg.resolution != ds18b20DefaultResolution {
205+
if err := d.connection.WriteInteger(resolutionCommand, ds18b20DefaultResolution); err != nil {
206+
return err
207+
}
208+
}
209+
210+
if d.ds18b20Cfg.conversionTime != ds18b20DefaultConversionTime {
211+
return d.connection.WriteInteger(convTimeCommand, int(ds18b20DefaultConversionTime))
212+
}
213+
214+
return nil
215+
}
216+
217+
func (o ds18b20UnitscalerOption) apply(cfg *ds18b20Configuration) {
218+
cfg.scaleUnit = o.unitscaler
219+
}
220+
221+
func (o ds18b20ResolutionOption) apply(cfg *ds18b20Configuration) {
222+
cfg.resolution = uint8(o)
223+
}
224+
225+
func (o ds18b20ConversionTimeOption) apply(cfg *ds18b20Configuration) {
226+
cfg.conversionTime = uint16(o)
227+
}

0 commit comments

Comments
 (0)