Commit 55c368ac authored by Lukáš Voborský's avatar Lukáš Voborský Committed by Marcel Stör
Browse files

DS18B20 module - update (enhanced, optimized) (#1996)

parent d079b842
...@@ -10,31 +10,40 @@ package.loaded["ds18b20"]=nil ...@@ -10,31 +10,40 @@ package.loaded["ds18b20"]=nil
``` ```
<a id="ds18b20_setup"></a> <a id="ds18b20_setup"></a>
## readTemp() # Methods
Scans the bus for DS18B20 sensors, starts a readout (conversion) for all sensors and calls a callback function when all temperatures are available. Powered sensors are read at once first. Parasite-powered sensors are read one by one. The first parasite-powered sensor is read together with all powered sensors.
The module requires `ow` module. ## read_temp()
Scans the bus for DS18B20 sensors (optional), starts a readout (conversion) for all sensors and calls a callback function when all temperatures are available. Powered sensors are read at once first. Parasite-powered sensors are read one by one. The first parasite-powered sensor is read together with all powered sensors.
The also module uses `encoder` module for printing debug information with more readable representation of sensor address (`encoder.toHex()`). The module requires `ow` module.
#### Syntax #### Syntax
`readTemp(callback, pin)` `read_temp(callback, pin, unit, force_search, save_search)`
#### Parameters #### Parameters
- `callback` function that receives all results when all conversions finish. The callback function has one parameter - an array addressed by sensor addresses and a value of the temperature (string for integer version). - `callback` function that receives all results when all conversions finish. The callback function has one parameter - an array addressed by sensor addresses and a value of the temperature (string for integer version).
- `pin` pin of the one-wire bus. If nil, GPIO0 (3) is used. - `pin` pin of the one-wire bus. If nil, GPIO0 (3) is used.
- `unit` unit can be Celsius ("C" or ds18b20.C), Kelvin ("K" or ds18b20.K) or Fahrenheit ("F" or ds18b20.F). If not specified (nil) latest used unit is used.
- `force_search` if not nil a bus search for devices is performed before readout. If nil the existing list of sensors in memory is used. If the bus has not been searched yet the search performed as well.
- `save_search` if not nil found sensors are saved to the file `ds18b20_save.lc`. When `read_temp` is called, list of sensors in memory is empty and file `ds18b20_save.lc` is present then sensor addresses are loaded from file - usefull when running from batteries & deepsleep - immediate readout is performed (no bus scan).
#### Returns #### Returns
nil nil
#### Example #### Example
```lua ```lua
t = require("ds18b20") local t = require("ds18b20")
pin = 3 -- gpio0 = 3, gpio2 = 4 local pin = 3 -- gpio0 = 3, gpio2 = 4
function readout(temp) local function readout(temp)
if t.sens then
print("Total number of DS18B20 sensors: ".. #t.sens)
for i, s in ipairs(t.sens) do
print(string.format(" sensor #%d address: %s%s", i, ('%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X'):format(s:byte(1,8)), s:byte(9) == 1 and " (parasite)" or ""))
end
end
for addr, temp in pairs(temp) do for addr, temp in pairs(temp) do
print(string.format("Sensor %s: %s 'C", encoder.toHex(addr), temp)) print(string.format("Sensor %s: %s °C", ('%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X'):format(addr:byte(1,8)), temp))
end end
-- Module can be released when it is no longer needed -- Module can be released when it is no longer needed
...@@ -42,13 +51,15 @@ function readout(temp) ...@@ -42,13 +51,15 @@ function readout(temp)
package.loaded["ds18b20"]=nil package.loaded["ds18b20"]=nil
end end
-- t:readTemp(readout) -- default pin value is 3 t:read_temp(readout, pin, t.C)```
t:readTemp(readout, pin)
if t.sens then ## enable_debug()
print("Total number of DS18B20 sensors: "..table.getn(t.sens)) Enables debug output of the module.
for i, s in ipairs(t.sens) do
-- print(string.format(" sensor #%d address: %s%s", i, s.addr, s.parasite == 1 and " (parasite)" or "")) # Properties
print(string.format(" sensor #%d address: %s%s", i, encoder.toHex(s.addr), s.parasite == 1 and " (parasite)" or "")) -- readable address with Hex encoding is preferred when encoder module is available
end ## sens
end A table with sensors present on the bus. It includes its address (8 bytes) and information whether the sensor is parasite-powered (9-th byte, 0 or 1).
```
## temp
A table with readout values (also passed as a parameter to callback function). It is addressed by sensor addresses.
\ No newline at end of file
-- encoder module is needed only for debug output; lines can be removed if no
-- debug output is needed and/or encoder module is missing
t = require("ds18b20") t = require("ds18b20")
pin = 3 -- gpio0 = 3, gpio2 = 4 pin = 3 -- gpio0 = 3, gpio2 = 4
function readout(temp) local function readout(temp)
if t.sens then
print("Total number of DS18B20 sensors: ".. #t.sens)
for i, s in ipairs(t.sens) do
print(string.format(" sensor #%d address: %s%s", i, ('%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X'):format(s:byte(1,8)), s:byte(9) == 1 and " (parasite)" or ""))
end
end
for addr, temp in pairs(temp) do for addr, temp in pairs(temp) do
-- print(string.format("Sensor %s: %s 'C", addr, temp)) print(string.format("Sensor %s: %s °C", ('%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X'):format(addr:byte(1,8)), temp))
print(string.format("Sensor %s: %s °C", encoder.toHex(addr), temp)) -- readable address with base64 encoding is preferred when encoder module is available
end end
-- Module can be released when it is no longer needed -- Module can be released when it is no longer needed
t = nil --t = nil
package.loaded["ds18b20"]=nil --package.loaded["ds18b20"]=nil
end end
-- t:readTemp(readout) -- default pin value is 3 t:enable_debug()
t:readTemp(readout, pin) file.remove("ds18b20_save.lc") -- remove saved addresses
if t.sens then print("=============================================", node.heap())
print("Total number of DS18B20 sensors: "..table.getn(t.sens)) print("first call, no addresses in flash, search is performed")
for i, s in ipairs(t.sens) do t:read_temp(readout, pin, t.C)
-- print(string.format(" sensor #%d address: %s%s", i, s.addr, s.parasite == 1 and " (parasite)" or ""))
print(string.format(" sensor #%d address: %s%s", i, encoder.toHex(s.addr), s.parasite == 1 and " (parasite)" or "")) -- readable address with base64 encoding is preferred when encoder module is available tmr.create():alarm(2000, tmr.ALARM_SINGLE, function()
end print("=============================================", node.heap())
end print("second readout, no new search, found addresses are used")
t:read_temp(readout, pin)
tmr.create():alarm(2000, tmr.ALARM_SINGLE, function()
print("=============================================", node.heap())
print("force search again")
t:read_temp(readout, pin, nil, true)
tmr.create():alarm(2000, tmr.ALARM_SINGLE, function()
print("=============================================", node.heap())
print("save search results")
t:read_temp(readout, pin, nil, false, true)
tmr.create():alarm(2000, tmr.ALARM_SINGLE, function()
print("=============================================", node.heap())
print("use saved addresses")
t.sens={}
t:read_temp(readout, pin)
end)
end)
end)
end)
...@@ -11,8 +11,7 @@ function readout(temp) ...@@ -11,8 +11,7 @@ function readout(temp)
"<b>ESP8266</b></br>" "<b>ESP8266</b></br>"
for addr, temp in pairs(temp) do for addr, temp in pairs(temp) do
-- resp = resp .. string.format("Sensor %s: %s &#8451</br>", addr, temp) resp = resp .. string.format("Sensor %s: %s &#8451</br>", ('%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X '):format(addr:byte(1,8)), temp)
resp = resp .. string.format("Sensor %s: %s &#8451</br>", encoder.toHex(addr), temp) -- readable address with base64 encoding is preferred when encoder module is available
end end
resp = resp .. resp = resp ..
...@@ -30,7 +29,7 @@ srv=net.createServer(net.TCP) ...@@ -30,7 +29,7 @@ srv=net.createServer(net.TCP)
srv:listen(port, srv:listen(port,
function(conn) function(conn)
gconn = conn gconn = conn
-- t:readTemp(readout) -- default pin value is 3 -- t:read_temp(readout) -- default pin value is 3
t:readTemp(readout, pin) t:read_temp(readout, pin)
end end
) )
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- DS18B20 one wire module for NODEMCU -- DS18B20 one wire module for NODEMCU
-- by @voborsky, @devsaurus -- NODEMCU TEAM
-- encoder module is needed only for debug output; lines can be removed if no -- LICENCE: http://opensource.org/licenses/MIT
-- debug output is needed and/or encoder module is missing -- @voborsky, @devsaurus, TerryE 26 Mar 2017
-- ----------------------------------------------------------------------------------------------------------------------------------------------------------------
-- by default the module is for integer version, comment integer version and local modname = ...
-- uncomment float version part for float version
--------------------------------------------------------------------------------
return({ -- Used modules and functions
pin=3, local table, string, ow, tmr, print, type, tostring, pcall, ipairs =
sens={}, table, string, ow, tmr, print, type, tostring, pcall, ipairs
temp={}, -- Local functions
local ow_setup, ow_search, ow_select, ow_read, ow_read_bytes, ow_write, ow_crc8, ow_reset, ow_reset_search, ow_skip, ow_depower =
ow.setup, ow.search, ow.select, ow.read, ow.read_bytes, ow.write, ow.crc8, ow.reset, ow.reset_search, ow.skip, ow.depower
local node_task_post, node_task_LOW_PRIORITY = node.task.post, node.task.LOW_PRIORITY
local string_char, string_dump = string.char, string.dump
local now, tmr_create, tmr_ALARM_SINGLE = tmr.now, tmr.create, tmr.ALARM_SINGLE
local table_sort, table_concat = table.sort, table.concat
local math_floor = math.floor
local file_open = file.open
conversion = function(self) table, string, tmr, ow = nil, nil, nil, nil
local pin = self.pin
for i,s in ipairs(self.sens) do
if s.status == 0 then
print("starting conversion:", encoder.toHex(s.addr), s.parasite == 1 and "parasite" or " ")
ow.reset(pin)
ow.select(pin, s.addr) -- select the sensor
ow.write(pin, 0x44, 1) -- and start conversion
s.status = 1
if s.parasite == 1 then break end -- parasite sensor blocks bus during conversion
end
end
tmr.create():alarm(750, tmr.ALARM_SINGLE, function() self:readout() end)
end,
readTemp = function(self, cb, lpin) local DS18B20FAMILY = 0x28
if lpin then self.pin = lpin end local DS1920FAMILY = 0x10 -- and DS18S20 series
local pin = self.pin local CONVERT_T = 0x44
self.cb = cb local READ_SCRATCHPAD = 0xBE
self.temp={} local READ_POWERSUPPLY= 0xB4
ow.setup(pin) local MODE = 1
self.sens={} local pin, cb, unit = 3
ow.reset_search(pin) local status = {}
-- ow.target_search(pin,0x28)
-- search the first device
local addr = ow.search(pin)
-- and loop through all devices
while addr do
-- search next device
local crc=ow.crc8(string.sub(addr,1,7))
if (crc==addr:byte(8)) and ((addr:byte(1)==0x10) or (addr:byte(1)==0x28)) then
ow.reset(pin)
ow.select(pin, addr) -- select the found sensor
ow.write(pin, 0xB4, 1) -- Read Power Supply [B4h]
local parasite = (ow.read(pin)==0 and 1 or 0)
table.insert(self.sens,{addr=addr, parasite=parasite, status=0})
print("contact: ", encoder.toHex(addr), parasite == 1 and "parasite" or " ")
end
addr = ow.search(pin) local debugPrint = function() return end
tmr.wdclr()
end
-- place powered sensors first --------------------------------------------------------------------------------
table.sort(self.sens, function(a,b) return a.parasite<b.parasite end) -- Implementation
--------------------------------------------------------------------------------
local function enable_debug()
debugPrint = function (...) print(now(),' ', ...) end
end
node.task.post(node.task.MEDIUM_PRIORITY, function() self:conversion() end) local function to_string(addr, esc)
end, if type(addr) == 'string' and #addr == 8 then
return ( esc == true and
'"\\%u\\%u\\%u\\%u\\%u\\%u\\%u\\%u"' or
'%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X '):format(addr:byte(1,8))
else
return tostring(addr)
end
end
readout=function(self) local function readout(self)
local pin = self.pin local next = false
local next = false local sens = self.sens
if not self.sens then return 0 end local temp = self.temp
for i,s in ipairs(self.sens) do for i, s in ipairs(sens) do
-- print(encoder.toHex(s.addr), s.status) if status[i] == 1 then
if s.status == 1 then ow_reset(pin)
ow.reset(pin) local addr = s:sub(1,8)
ow.select(pin, s.addr) -- select the sensor ow_select(pin, addr) -- select the sensor
ow.write(pin, 0xBE, 0) -- READ_SCRATCHPAD ow_write(pin, READ_SCRATCHPAD, MODE)
data = ow.read_bytes(pin, 9) data = ow_read_bytes(pin, 9)
local t=(data:byte(1)+data:byte(2)*256) local t=(data:byte(1)+data:byte(2)*256)
if (t > 0x7fff) then t = t - 0x10000 end -- t is actually signed so process the sign bit and adjust for fractional bits
if (s.addr:byte(1) == 0x28) then -- the DS18B20 family has 4 fractional bits and the DS18S20s, 1 fractional bit
t = t * 625 -- DS18B20, 4 fractional bits t = ((t <= 32767) and t or t - 65536) *
else ((addr:byte(1) == DS18B20FAMILY) and 625 or 5000)
t = t * 5000 -- DS18S20, 1 fractional bit
if 1/2 == 0 then
-- integer version
if unit == 'F' then
t = (t * 18)/10 + 320000
elseif unit == 'K' then
t = t + 2731500
end
local sgn = t<0 and -1 or 1
local tA = sgn*t
local tH=tA/10000
local tL=(tA%10000)/1000 + ((tA%1000)/100 >= 5 and 1 or 0)
if tH and (t~=850000) then
temp[addr]=(sgn<0 and "-" or "")..tH.."."..tL
debugPrint(to_string(addr),(sgn<0 and "-" or "")..tH.."."..tL)
status[i] = 2
end end
-- end integer version
if 1/2 == 0 then else
-- integer version -- float version
local sgn = t<0 and -1 or 1 if t and (math_floor(t/10000)~=85) then
local tA = sgn*t t = t / 10000
local tH=tA/10000 if unit == 'F' then
local tL=(tA%10000)/1000 + ((tA%1000)/100 >= 5 and 1 or 0) t = t * 18/10 + 32
elseif unit == 'K' then
if tH and (tH~=85) then t = t + 27315/100
self.temp[s.addr]=(sgn<0 and "-" or "")..tH.."."..tL end
print(encoder.toHex(s.addr),(sgn<0 and "-" or "")..tH.."."..tL) self.temp[addr]=t
s.status = 2 debugPrint(to_string(addr), t)
end status[i] = 2
-- end integer version
else
-- float version
if t and (math.floor(t/10000)~=85) then
self.temp[s.addr]=t/10000
print(encoder.toHex(s.addr), t)
s.status = 2
end
-- end float version
end end
-- end float version
end end
next = next or s.status == 0
end end
if next then next = next or status[i] == 0
node.task.post(node.task.MEDIUM_PRIORITY, function() self:conversion() end) end
else if next then
self.sens = nil node_task_post(node_task_LOW_PRIORITY, function() return conversion(self) end)
if self.cb then else
node.task.post(node.task.MEDIUM_PRIORITY, function() self.cb(self.temp) end) --sens = {}
if cb then
node_task_post(node_task_LOW_PRIORITY, function() return cb(temp) end)
end
end
end
local function conversion(self)
local sens = self.sens
local powered_only = true
for _, s in ipairs(sens) do powered_only = powered_only and s:byte(9) ~= 1 end
if powered_only then
debugPrint("starting conversion: all sensors")
ow_reset(pin)
ow_skip(pin) -- select the sensor
ow_write(pin, CONVERT_T, MODE) -- and start conversion
for i, s in ipairs(sens) do status[i] = 1 end
else
for i, s in ipairs(sens) do
if status[i] == 0 then
local addr, parasite = s:sub(1,8), s:byte(9)
debugPrint("starting conversion:", to_string(addr), parasite == 1 and "parasite" or " ")
ow_reset(pin)
ow_select(pin, addr) -- select the sensor
ow_write(pin, CONVERT_T, MODE) -- and start conversion
status[i] = 1
if parasite == 1 then break end -- parasite sensor blocks bus during conversion
end end
end end
end
tmr_create():alarm(750, tmr_ALARM_SINGLE, function() return readout(self) end)
end
local function _search(self, lcb, lpin, search, save)
self.temp = {}
if search then self.sens = {}; status = {} end
local temp = self.temp
local sens = self.sens
pin = lpin or pin
local addr
if not search and #sens == 0 then
-- load addreses if available
debugPrint ("geting addreses from flash")
local s,check,a = pcall(dofile, "ds18b20_save.lc")
if s and check == "ds18b20" then
for i = 1, #a do sens[i] = a[i] end
end
debugPrint (#sens, "addreses found")
end end
})
ow_setup(pin)
if search or #sens == 0 then
ow_reset_search(pin)
-- ow_target_search(pin,0x28)
-- search the first device
addr = ow_search(pin)
else
for i, s in ipairs(sens) do status[i] = 0 end
end
local function cycle()
debugPrint("cycle")
if addr then
local crc=ow_crc8(addr:sub(1,7))
if (crc==addr:byte(8)) and ((addr:byte(1)==DS1920FAMILY) or (addr:byte(1)==DS18B20FAMILY)) then
ow_reset(pin)
ow_select(pin, addr)
ow_write(pin, READ_POWERSUPPLY, MODE)
local parasite = (ow_read(pin)==0 and 1 or 0)
sens[#sens+1]= addr..string_char(parasite) -- {addr=addr, parasite=parasite, status=0}
debugPrint("contact: ", to_string(addr), parasite == 1 and "parasite" or " ")
end
addr = ow_search(pin)
node_task_post(node_task_LOW_PRIORITY, cycle)
else
ow_depower(pin)
-- place powered sensors first
table_sort(sens, function(a, b) return a:byte(9)<b:byte(9) end) -- parasite
-- save sensor addreses
if save then
debugPrint ("saving addreses to flash")
local addr_list = {}
for i =1, #sens do
local s = sens[i]
addr_list[i] = to_string(s:sub(1,8), true)..('.."\\%u"'):format(s:byte(9))
end
local save_statement = 'return "ds18b20", {' .. table_concat(addr_list, ',') .. '}'
debugPrint (save_statement)
local save_file = file_open("ds18b20_save.lc","w")
save_file:write(string_dump(loadstring(save_statement)))
save_file:close()
end
-- end save sensor addreses
if lcb then node_task_post(node_task_LOW_PRIORITY, lcb) end
end
end
cycle()
end
local function read_temp(self, lcb, lpin, lunit, force_search, save_search)
cb, unit = lcb, lunit or unit
_search(self, function() return conversion(self) end, lpin, force_search, save_search)
end
-- Set module name as parameter of require and return module table
local M = {
sens = {},
temp = {},
C = 'C', F = 'F', K = 'K',
read_temp = read_temp, enable_debug = enable_debug
}
_G[modname or 'ds18b20'] = M
return M
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment