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