From d079b842a87ef8263d3882a8afb5a87b437dadc3 Mon Sep 17 00:00:00 2001 From: fetchbot Date: Wed, 16 Aug 2017 21:04:23 +0200 Subject: [PATCH 1/8] add ds18b20 module (#2003) * add ds18b20 module * add intitial eeprom value definition * adjust read() function and address handling --- app/include/user_modules.h | 1 + app/modules/ds18b20.c | 295 +++++++++++++++++++++++++++++++++++++ docs/en/modules/ds18b20.md | 130 ++++++++++++++++ mkdocs.yml | 1 + 4 files changed, 427 insertions(+) create mode 100644 app/modules/ds18b20.c create mode 100644 docs/en/modules/ds18b20.md diff --git a/app/include/user_modules.h b/app/include/user_modules.h index 74bf1719..d1d672c8 100644 --- a/app/include/user_modules.h +++ b/app/include/user_modules.h @@ -29,6 +29,7 @@ //#define LUA_USE_MODULES_CRON //#define LUA_USE_MODULES_CRYPTO #define LUA_USE_MODULES_DHT +//#define LUA_USE_MODULES_DS18B20 //#define LUA_USE_MODULES_ENCODER //#define LUA_USE_MODULES_ENDUSER_SETUP // USE_DNS in dhcpserver.h needs to be enabled for this module to work. #define LUA_USE_MODULES_FILE diff --git a/app/modules/ds18b20.c b/app/modules/ds18b20.c new file mode 100644 index 00000000..b07bc173 --- /dev/null +++ b/app/modules/ds18b20.c @@ -0,0 +1,295 @@ +//*************************************************************************** +// DS18B20 module for ESP8266 with nodeMCU +// fetchbot @github +// MIT license, http://opensource.org/licenses/MIT +//*************************************************************************** + +#include "module.h" +#include "lauxlib.h" +#include "platform.h" +#include "osapi.h" +#include "driver/onewire.h" +#include "c_stdio.h" +#include "c_stdlib.h" + +//*************************************************************************** +// OW ROM COMMANDS +//*************************************************************************** + +#define DS18B20_ROM_SEARCH (0xF0) +#define DS18B20_ROM_READ (0x33) +#define DS18B20_ROM_MATCH (0x55) +#define DS18B20_ROM_SKIP (0xCC) +#define DS18B20_ROM_SEARCH_ALARM (0xEC) + +//*************************************************************************** +// OW FUNCTION COMMANDS +//*************************************************************************** + +#define DS18B20_FUNC_CONVERT (0x44) +#define DS18B20_FUNC_SCRATCH_WRITE (0x4E) +#define DS18B20_FUNC_SCRATCH_READ (0xBE) +#define DS18B20_FUNC_SCRATCH_COPY (0x48) +#define DS18B20_FUNC_E2_RECALL (0xB8) +#define DS18B20_FUNC_POWER_READ (0xB4) + +//*************************************************************************** +// Initial EEPROM values +//*************************************************************************** + +#define DS18B20_EEPROM_TH (0x4B) // 75 degree +#define DS18B20_EEPROM_TL (0x46) // 70 degree +#define DS18B20_EEPROM_RES (0x7F) // 12 bit resolution + +//*************************************************************************** + +static uint8_t ds18b20_bus_pin; +static uint8_t ds18b20_device_family; +static uint8_t ds18b20_device_search = 0; +static uint8_t ds18b20_device_index; +static uint8_t ds18b20_device_par; +static uint8_t ds18b20_device_conf[3]; +static uint8_t ds18b20_device_rom[8]; +static uint8_t ds18b20_device_scratchpad[9]; +static double ds18b20_device_scratchpad_temp; +static int ds18b20_device_scratchpad_temp_dec; +static uint8_t ds18b20_device_scratchpad_conf; +static uint8_t ds18b20_device_res = 12; // 12 bit resolution (750ms conversion time) + +os_timer_t ds18b20_timer; // timer for conversion delay +int ds18b20_timer_ref; // callback when readout is ready + +int ds18b20_table_ref; +static int ds18b20_table_offset; + +static int ds18b20_lua_readoutdone(void); + +// Setup onewire bus for DS18B20 temperature sensors +// Lua: ds18b20.setup(OW_BUS_PIN) +static int ds18b20_lua_setup(lua_State *L) { + // check ow bus pin value + if (!lua_isnumber(L, 1) || lua_isnumber(L, 1) == 0) { + return luaL_error(L, "wrong 1-wire pin"); + } + + ds18b20_bus_pin = luaL_checkinteger(L, 1); + MOD_CHECK_ID(ow, ds18b20_bus_pin); + onewire_init(ds18b20_bus_pin); +} + +static int ds18b20_set_device(uint8_t *ds18b20_device_rom) { + onewire_reset(ds18b20_bus_pin); + onewire_select(ds18b20_bus_pin, ds18b20_device_rom); + onewire_write(ds18b20_bus_pin, DS18B20_FUNC_SCRATCH_WRITE, 0); + onewire_write_bytes(ds18b20_bus_pin, ds18b20_device_conf, 3, 0); +} + +// Change sensor settings +// Lua: ds18b20.setting(ROM, RES) +static int ds18b20_lua_setting(lua_State *L) { + // check rom table and resolution setting + if (!lua_istable(L, 1) || !lua_isnumber(L, 2)) { + return luaL_error(L, "wrong arg range"); + } + + ds18b20_device_res = luaL_checkinteger(L, 2); + + if (!((ds18b20_device_res == 9) || (ds18b20_device_res == 10) || (ds18b20_device_res == 11) || (ds18b20_device_res == 12))) { + return luaL_error(L, "Invalid argument: resolution"); + } + + // no change to th and tl setting + ds18b20_device_conf[0] = DS18B20_EEPROM_TH; + ds18b20_device_conf[1] = DS18B20_EEPROM_TL; + ds18b20_device_conf[2] = ((ds18b20_device_res - 9) << 5) + 0x1F; + + uint8_t table_len = lua_objlen(L, 1); + + const char *str[table_len]; + const char *sep = ":"; + + uint8_t string_index = 0; + + lua_pushnil(L); + while (lua_next(L, -3)) { + str[string_index] = lua_tostring(L, -1); + lua_pop(L, 1); + string_index++; + } + lua_pop(L, 1); + + for (uint8_t i = 0; i < string_index; i++) { + for (uint8_t j = 0; j < 8; j++) { + ds18b20_device_rom[j] = strtoul(str[i], NULL, 16); + str[i] = strchr(str[i], *sep); + if (str[i] == NULL || *str[i] == '\0') break; + str[i]++; + } + ds18b20_set_device(ds18b20_device_rom); + } + + // set conversion delay once to max if sensors with higher resolution still on the bus + ds18b20_device_res = 12; + + return 0; +} + +// Reads sensor values from all devices +// Lua: ds18b20.read(function(INDEX, ROM, RES, TEMP, TEMP_DEC, PAR) print(INDEX, ROM, RES, TEMP, TEMP_DEC, PAR) end, ROM[, FAMILY]) +static int ds18b20_lua_read(lua_State *L) { + + luaL_argcheck(L, (lua_type(L, 1) == LUA_TFUNCTION || lua_type(L, 1) == LUA_TLIGHTFUNCTION), 1, "Must be function"); + + lua_pushvalue(L, 1); + ds18b20_timer_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + if (!lua_istable(L, 2)) { + return luaL_error(L, "wrong arg range"); + } + + if (lua_isnumber(L, 3)) { + ds18b20_device_family = luaL_checkinteger(L, 3); + onewire_target_search(ds18b20_bus_pin, ds18b20_device_family); + ds18b20_table_offset = -3; + } else { + ds18b20_table_offset = -2; + } + + lua_pushvalue(L, 2); + ds18b20_table_ref = luaL_ref(L, LUA_REGISTRYINDEX); + + lua_pushnil(L); + if (lua_next(L, ds18b20_table_offset)) { + lua_pop(L, 2); + ds18b20_device_search = 0; + } else { + ds18b20_device_search = 1; + } + + os_timer_disarm(&ds18b20_timer); + + // perform a temperature conversion for all sensors and set timer + onewire_reset(ds18b20_bus_pin); + onewire_write(ds18b20_bus_pin, DS18B20_ROM_SKIP, 0); + onewire_write(ds18b20_bus_pin, DS18B20_FUNC_CONVERT, 1); + os_timer_setfn(&ds18b20_timer, (os_timer_func_t *)ds18b20_lua_readoutdone, NULL); + + switch (ds18b20_device_res) { + case (9): + os_timer_arm(&ds18b20_timer, 95, 0); + break; + case (10): + os_timer_arm(&ds18b20_timer, 190, 0); + break; + case (11): + os_timer_arm(&ds18b20_timer, 380, 0); + break; + case (12): + os_timer_arm(&ds18b20_timer, 760, 0); + break; + } +} + +static int ds18b20_read_device(uint8_t *ds18b20_device_rom) { + lua_State *L = lua_getstate(); + + if (onewire_crc8(ds18b20_device_rom,7) == ds18b20_device_rom[7]) { + + onewire_reset(ds18b20_bus_pin); + onewire_select(ds18b20_bus_pin, ds18b20_device_rom); + onewire_write(ds18b20_bus_pin, DS18B20_FUNC_POWER_READ, 0); + + if (onewire_read(ds18b20_bus_pin)) ds18b20_device_par = 0; + else ds18b20_device_par = 1; + + onewire_reset(ds18b20_bus_pin); + onewire_select(ds18b20_bus_pin, ds18b20_device_rom); + onewire_write(ds18b20_bus_pin, DS18B20_FUNC_SCRATCH_READ, 0); + onewire_read_bytes(ds18b20_bus_pin, ds18b20_device_scratchpad, 9); + + if (onewire_crc8(ds18b20_device_scratchpad,8) == ds18b20_device_scratchpad[8]) { + + lua_rawgeti(L, LUA_REGISTRYINDEX, ds18b20_timer_ref); + + lua_pushinteger(L, ds18b20_device_index); + + lua_pushfstring(L, "%d:%d:%d:%d:%d:%d:%d:%d", ds18b20_device_rom[0], ds18b20_device_rom[1], ds18b20_device_rom[2], ds18b20_device_rom[3], ds18b20_device_rom[4], ds18b20_device_rom[5], ds18b20_device_rom[6], ds18b20_device_rom[7]); + + ds18b20_device_scratchpad_conf = (ds18b20_device_scratchpad[4] >> 5) + 9; + ds18b20_device_scratchpad_temp = ((int8_t)(ds18b20_device_scratchpad[1] << 4) + (ds18b20_device_scratchpad[0] >> 4) + ((double)(ds18b20_device_scratchpad[0] & 0x0F) / 16)); + ds18b20_device_scratchpad_temp_dec = ((double)(ds18b20_device_scratchpad[0] & 0x0F) / 16 * 1000); + + if (ds18b20_device_scratchpad_conf >= ds18b20_device_res) { + ds18b20_device_res = ds18b20_device_scratchpad_conf; + } + + lua_pushinteger(L, ds18b20_device_scratchpad_conf); + lua_pushnumber(L, ds18b20_device_scratchpad_temp); + lua_pushinteger(L, ds18b20_device_scratchpad_temp_dec); + + lua_pushinteger(L, ds18b20_device_par); + + lua_pcall(L, 6, 0, 0); + + ds18b20_device_index++; + } + } +} + +static int ds18b20_lua_readoutdone(void) { + + lua_State *L = lua_getstate(); + os_timer_disarm(&ds18b20_timer); + + ds18b20_device_index = 1; + // set conversion delay to min and change it after finding the sensor with the highest resolution setting + ds18b20_device_res = 9; + + if (ds18b20_device_search) { + // iterate through all sensors on the bus and read temperature, resolution and parasitc settings + while (onewire_search(ds18b20_bus_pin, ds18b20_device_rom)) { + ds18b20_read_device(ds18b20_device_rom); + } + } else { + lua_rawgeti(L, LUA_REGISTRYINDEX, ds18b20_table_ref); + uint8_t table_len = lua_objlen(L, -1); + + const char *str[table_len]; + const char *sep = ":"; + + uint8_t string_index = 0; + + lua_pushnil(L); + while (lua_next(L, -2)) { + str[string_index] = lua_tostring(L, -1); + lua_pop(L, 1); + string_index++; + } + lua_pop(L, 1); + + for (uint8_t i = 0; i < string_index; i++) { + for (uint8_t j = 0; j < 8; j++) { + ds18b20_device_rom[j] = strtoul(str[i], NULL, 16); + str[i] = strchr(str[i], *sep); + if (str[i] == NULL || *str[i] == '\0') break; + str[i]++; + } + ds18b20_read_device(ds18b20_device_rom); + } + } + + luaL_unref(L, LUA_REGISTRYINDEX, ds18b20_table_ref); + ds18b20_table_ref = LUA_NOREF; + + luaL_unref(L, LUA_REGISTRYINDEX, ds18b20_timer_ref); + ds18b20_timer_ref = LUA_NOREF; +} + +static const LUA_REG_TYPE ds18b20_map[] = { + { LSTRKEY( "read" ), LFUNCVAL(ds18b20_lua_read) }, + { LSTRKEY( "setting" ), LFUNCVAL(ds18b20_lua_setting) }, + { LSTRKEY( "setup" ), LFUNCVAL(ds18b20_lua_setup) }, + { LNILKEY, LNILVAL } +}; + +NODEMCU_MODULE(DS18B20, "ds18b20", ds18b20_map, NULL); diff --git a/docs/en/modules/ds18b20.md b/docs/en/modules/ds18b20.md new file mode 100644 index 00000000..c3d81b6f --- /dev/null +++ b/docs/en/modules/ds18b20.md @@ -0,0 +1,130 @@ +# DS18B20 Module +| Since | Origin / Contributor | Maintainer | Source | +| :----- | :-------------------- | :---------- | :------ | +| 2017-06-11 | [fetchbot](https://github.com/fetchbot) | [fetchbot](https://github.com/fetchbot) | [ds18b20.c](../../../app/modules/ds18b20.c)| + +This module provides access to the DS18B20 1-Wire digital thermometer. + +## ds18b20.read() +Issues a temperature conversion of all connected sensors on the onewire bus and returns the measurment results after a conversion delay in a callback function. +The returned measurements can be filtered through the ROM addresses passed as a table or by the family type. +The callback function gets invoked for every specified sensor. + +#### Syntax +`ds18b20.read(CALLBACK, ROM[, FAMILY_ADDRESS])` + +#### Parameters +- `CALLBACK` callback function executed for each sensor + * e.g. `function(INDEX, ROM, RES, TEMP, TEMP_DEC, PAR) print(INDEX, ROM, RES, TEMP, TEMP_DEC, PAR) end` +- `ROM` table which contains the addresses for the specified sensors, or left empty to perform a onewire bus search for all sensors + * e.g. `{"28:FF:FF:FF:FF:FF:FF:FF","28:FF:FF:FF:FF:FF:FF:FF"}`, `{}` +- `FAMILY_ADDRESS` optional to limit the search for devices to a specific family type + * e.g `0x28` + +#### Returns +`nil` + +#### Callback function parameters +- `INDEX` index of the sensor on the bus +- `ROM` sensors 64-bit lasered rom code + * `28:FF:FF:FF:FF:FF:FF:FF` LSB, 8-bit family code, 48-bit serial number, MSB 8-bit crc +- `RES` temperature resolution +- `TEMP` temperature +- `TEMP_DEC` temperature decimals for integer firmware +- `PAR` sensor parasitic flag + +!!! note + + If using float firmware then `temp` is a floating point number. On an integer firmware, the final value has to be concatenated from `temp` and `temp_dec`. + +#### Example +```lua +local ow_pin = 3 +ds18b20.setup(ow_pin) + +-- read all sensors and print all measurement results +ds18b20.read( + function(ind,rom,res,temp,tdec,par) + print(ind,string.format("%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X",string.match(rom,"(%d+):(%d+):(%d+):(%d+):(%d+):(%d+):(%d+):(%d+)")),res,temp,tdec,par) + end,{}); + +-- read only sensors with family type 0x28 and print all measurement results +ds18b20.read( + function(ind,rom,res,temp,tdec,par) + print(ind,string.format("%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X",string.match(rom,"(%d+):(%d+):(%d+):(%d+):(%d+):(%d+):(%d+):(%d+)")),res,temp,tdec,par) + end,{},0x28); + +-- save device roms in a variable +local addr = {} +ds18b20.read( + function(ind,rom,res,temp,tdec,par) + addr[ind] = {string.format("%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X",string.match(rom,"(%d+):(%d+):(%d+):(%d+):(%d+):(%d+):(%d+):(%d+)"))} + end,{}); + +-- read only sensors listed in the variable addr +ds18b20.read( + function(ind,rom,res,temp,tdec,par) + print(ind,string.format("%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X",string.match(rom,"(%d+):(%d+):(%d+):(%d+):(%d+):(%d+):(%d+):(%d+)")),res,temp,tdec,par) + end,addr); + +-- print only parasitic sensors +ds18b20.read( + function(ind,rom,res,temp,tdec,par) + if (par == 1) then + print(ind,string.format("%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X",string.match(rom,"(%d+):(%d+):(%d+):(%d+):(%d+):(%d+):(%d+):(%d+)")),res,temp,tdec,par) + end + end,{}); + +-- print if temperature is greater or less than a defined value +ds18b20.read( + function(ind,rom,res,temp,tdec,par) + if (t > 25) then + print(ind,string.format("%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X",string.match(rom,"(%d+):(%d+):(%d+):(%d+):(%d+):(%d+):(%d+):(%d+)")),res,temp,tdec,par) + end + if (t < 20) then + print(ind,string.format("%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X",string.match(rom,"(%d+):(%d+):(%d+):(%d+):(%d+):(%d+):(%d+):(%d+)")),res,temp,tdec,par) + end + end,{}); +``` + +## ds18b20.setting() +Configuration of the temperature resolution settings. + +#### Syntax +`ds18b20.setting(ROM, RES)` + +#### Parameters +- `ROM` table which contains the addresses for the specified sensors, or empty for all sensors + * e.g. `{"28:FF:FF:FF:FF:FF:FF:FF","28:FF:FF:FF:FF:FF:FF:FF"}`, `{}` +- `RES` temperature bit resolution + * `9` - `12` + +#### Returns +`nil` + +#### Example +```lua +local ow_pin = 3 +ds18b20.setup(ow_pin) + +ds18b20.setting({"28:FF:FF:FF:FF:FF:FF:FF","28:FF:FF:FF:FF:FF:FF:FF"}, 9) +``` + +## ds18b20.setup() +Initializes the onewire bus on the selected pin. + +#### Syntax +`ds18b20.setup(OW_BUS_PIN)` + +#### Parameters +- `OW_BUS_PIN` + * `1` - `12` + +#### Returns +`nil` + +#### Example +```lua +local ow_pin = 3 +ds18b20.setup(ow_pin) +``` \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index e200d50e..4b62c304 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -48,6 +48,7 @@ pages: - 'cron': 'en/modules/cron.md' - 'crypto': 'en/modules/crypto.md' - 'dht': 'en/modules/dht.md' + - 'ds18b20': 'en/modules/ds18b20.md' - 'encoder': 'en/modules/encoder.md' - 'enduser setup': 'en/modules/enduser-setup.md' - 'file': 'en/modules/file.md' From 55c368ac030f7165af5095380c4e20e7f45a9775 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Voborsk=C3=BD?= Date: Wed, 16 Aug 2017 21:04:52 +0200 Subject: [PATCH 2/8] DS18B20 module - update (enhanced, optimized) (#1996) --- lua_modules/ds18b20/README.md | 49 ++-- lua_modules/ds18b20/ds18b20-example.lua | 60 +++-- lua_modules/ds18b20/ds18b20-web.lua | 7 +- lua_modules/ds18b20/ds18b20.lua | 334 ++++++++++++++++-------- 4 files changed, 294 insertions(+), 156 deletions(-) diff --git a/lua_modules/ds18b20/README.md b/lua_modules/ds18b20/README.md index 0bcd2a4c..bd122cf7 100644 --- a/lua_modules/ds18b20/README.md +++ b/lua_modules/ds18b20/README.md @@ -10,31 +10,40 @@ package.loaded["ds18b20"]=nil ``` -## readTemp() -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. +# Methods + +## 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 module requires `ow` module. -The also module uses `encoder` module for printing debug information with more readable representation of sensor address (`encoder.toHex()`). - #### Syntax -`readTemp(callback, pin)` +`read_temp(callback, pin, unit, force_search, save_search)` #### 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). - `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 nil #### Example ```lua -t = require("ds18b20") -pin = 3 -- gpio0 = 3, gpio2 = 4 +local t = require("ds18b20") +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 - 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 -- Module can be released when it is no longer needed @@ -42,13 +51,15 @@ function readout(temp) package.loaded["ds18b20"]=nil end --- t:readTemp(readout) -- default pin value is 3 -t:readTemp(readout, pin) -if t.sens then - print("Total number of DS18B20 sensors: "..table.getn(t.sens)) - 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 "")) - 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 -end -``` +t:read_temp(readout, pin, t.C)``` + +## enable_debug() +Enables debug output of the module. + +# Properties + +## sens +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 diff --git a/lua_modules/ds18b20/ds18b20-example.lua b/lua_modules/ds18b20/ds18b20-example.lua index 9921829c..7a12b069 100644 --- a/lua_modules/ds18b20/ds18b20-example.lua +++ b/lua_modules/ds18b20/ds18b20-example.lua @@ -1,26 +1,52 @@ --- 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") 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 - -- print(string.format("Sensor %s: %s 'C", addr, temp)) - print(string.format("Sensor %s: %s °C", encoder.toHex(addr), temp)) -- readable address with base64 encoding is preferred when encoder module is available + print(string.format("Sensor %s: %s °C", ('%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X'):format(addr:byte(1,8)), temp)) end -- Module can be released when it is no longer needed - t = nil - package.loaded["ds18b20"]=nil + --t = nil + --package.loaded["ds18b20"]=nil end --- t:readTemp(readout) -- default pin value is 3 -t:readTemp(readout, pin) -if t.sens then - print("Total number of DS18B20 sensors: "..table.getn(t.sens)) - 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 "")) - 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 - end -end +t:enable_debug() +file.remove("ds18b20_save.lc") -- remove saved addresses +print("=============================================", node.heap()) +print("first call, no addresses in flash, search is performed") +t:read_temp(readout, pin, t.C) + +tmr.create():alarm(2000, tmr.ALARM_SINGLE, function() + print("=============================================", node.heap()) + 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) diff --git a/lua_modules/ds18b20/ds18b20-web.lua b/lua_modules/ds18b20/ds18b20-web.lua index a8347097..408612f9 100644 --- a/lua_modules/ds18b20/ds18b20-web.lua +++ b/lua_modules/ds18b20/ds18b20-web.lua @@ -11,8 +11,7 @@ function readout(temp) "ESP8266
" for addr, temp in pairs(temp) do - -- resp = resp .. string.format("Sensor %s: %s ℃
", addr, temp) - resp = resp .. string.format("Sensor %s: %s ℃
", encoder.toHex(addr), temp) -- readable address with base64 encoding is preferred when encoder module is available + resp = resp .. string.format("Sensor %s: %s ℃
", ('%02X:%02X:%02X:%02X:%02X:%02X:%02X:%02X '):format(addr:byte(1,8)), temp) end resp = resp .. @@ -30,7 +29,7 @@ srv=net.createServer(net.TCP) srv:listen(port, function(conn) gconn = conn - -- t:readTemp(readout) -- default pin value is 3 - t:readTemp(readout, pin) + -- t:read_temp(readout) -- default pin value is 3 + t:read_temp(readout, pin) end ) diff --git a/lua_modules/ds18b20/ds18b20.lua b/lua_modules/ds18b20/ds18b20.lua index 7f192346..c86cbfc5 100644 --- a/lua_modules/ds18b20/ds18b20.lua +++ b/lua_modules/ds18b20/ds18b20.lua @@ -1,121 +1,223 @@ -------------------------------------------------------------------------------- -- DS18B20 one wire module for NODEMCU --- by @voborsky, @devsaurus --- encoder module is needed only for debug output; lines can be removed if no --- debug output is needed and/or encoder module is missing --- --- by default the module is for integer version, comment integer version and --- uncomment float version part for float version +-- NODEMCU TEAM +-- LICENCE: http://opensource.org/licenses/MIT +-- @voborsky, @devsaurus, TerryE 26 Mar 2017 +---------------------------------------------------------------------------------------------------------------------------------------------------------------- +local modname = ... + +-- Used modules and functions +local table, string, ow, tmr, print, type, tostring, pcall, ipairs = + table, string, ow, tmr, print, type, tostring, pcall, ipairs +-- 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 + +table, string, tmr, ow = nil, nil, nil, nil + +local DS18B20FAMILY = 0x28 +local DS1920FAMILY = 0x10 -- and DS18S20 series +local CONVERT_T = 0x44 +local READ_SCRATCHPAD = 0xBE +local READ_POWERSUPPLY= 0xB4 +local MODE = 1 + +local pin, cb, unit = 3 +local status = {} + +local debugPrint = function() return end + -------------------------------------------------------------------------------- +-- Implementation +-------------------------------------------------------------------------------- +local function enable_debug() + debugPrint = function (...) print(now(),' ', ...) end +end -return({ - pin=3, - sens={}, - temp={}, - - conversion = function(self) - 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) - if lpin then self.pin = lpin end - local pin = self.pin - self.cb = cb - self.temp={} - ow.setup(pin) - - self.sens={} - ow.reset_search(pin) - -- 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) - tmr.wdclr() - end - - -- place powered sensors first - table.sort(self.sens, function(a,b) return a.parasite 0x7fff) then t = t - 0x10000 end - if (s.addr:byte(1) == 0x28) then - t = t * 625 -- DS18B20, 4 fractional bits - else - t = t * 5000 -- DS18S20, 1 fractional bit - end - - if 1/2 == 0 then - -- integer version - 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 (tH~=85) then - self.temp[s.addr]=(sgn<0 and "-" or "")..tH.."."..tL - print(encoder.toHex(s.addr),(sgn<0 and "-" or "")..tH.."."..tL) - s.status = 2 - end - -- 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 - next = next or s.status == 0 - end - if next then - node.task.post(node.task.MEDIUM_PRIORITY, function() self:conversion() end) - else - self.sens = nil - if self.cb then - node.task.post(node.task.MEDIUM_PRIORITY, function() self.cb(self.temp) end) - end - end - +local function to_string(addr, esc) + 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 + +local function readout(self) + local next = false + local sens = self.sens + local temp = self.temp + for i, s in ipairs(sens) do + if status[i] == 1 then + ow_reset(pin) + local addr = s:sub(1,8) + ow_select(pin, addr) -- select the sensor + ow_write(pin, READ_SCRATCHPAD, MODE) + data = ow_read_bytes(pin, 9) + + local t=(data:byte(1)+data:byte(2)*256) + -- t is actually signed so process the sign bit and adjust for fractional bits + -- the DS18B20 family has 4 fractional bits and the DS18S20s, 1 fractional bit + t = ((t <= 32767) and t or t - 65536) * + ((addr:byte(1) == DS18B20FAMILY) and 625 or 5000) + + 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 integer version + else + -- float version + if t and (math_floor(t/10000)~=85) then + t = t / 10000 + if unit == 'F' then + t = t * 18/10 + 32 + elseif unit == 'K' then + t = t + 27315/100 + end + self.temp[addr]=t + debugPrint(to_string(addr), t) + status[i] = 2 + end + -- end float version + end + end + next = next or status[i] == 0 + end + if next then + node_task_post(node_task_LOW_PRIORITY, function() return conversion(self) end) + else + --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 + 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 + + 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) Date: Wed, 16 Aug 2017 21:32:04 +0200 Subject: [PATCH 3/8] Link ds18b20 C/Lua module docs --- docs/en/modules/ds18b20.md | 2 +- lua_modules/ds18b20/README.md | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/en/modules/ds18b20.md b/docs/en/modules/ds18b20.md index c3d81b6f..ed15ff78 100644 --- a/docs/en/modules/ds18b20.md +++ b/docs/en/modules/ds18b20.md @@ -3,7 +3,7 @@ | :----- | :-------------------- | :---------- | :------ | | 2017-06-11 | [fetchbot](https://github.com/fetchbot) | [fetchbot](https://github.com/fetchbot) | [ds18b20.c](../../../app/modules/ds18b20.c)| -This module provides access to the DS18B20 1-Wire digital thermometer. +This module provides access to the DS18B20 1-Wire digital thermometer. Note that NodeMCU offers both a C module (this one) and [a Lua module for this sensor](https://github.com/nodemcu/nodemcu-firmware/tree/dev/lua_modules/ds18b20). See [#2003](https://github.com/nodemcu/nodemcu-firmware/pull/2003) for a discussion on the respective merits of them. ## ds18b20.read() Issues a temperature conversion of all connected sensors on the onewire bus and returns the measurment results after a conversion delay in a callback function. diff --git a/lua_modules/ds18b20/README.md b/lua_modules/ds18b20/README.md index bd122cf7..deb8a683 100644 --- a/lua_modules/ds18b20/README.md +++ b/lua_modules/ds18b20/README.md @@ -1,4 +1,7 @@ # DS18B20 Module + +This is a Lua module for the DS18B20 1-Wire digital thermometer. Note that NodeMCU offers both a Lua module (this one) and [a C module for this sensor](http://nodemcu.readthedocs.io/en/latest/en/modules/ds18b20/). See [#2003](https://github.com/nodemcu/nodemcu-firmware/pull/2003) for a discussion on the respective merits of them. + ## Require ```lua ds18b20 = require("ds18b20") @@ -8,7 +11,6 @@ ds18b20 = require("ds18b20") ds18b20 = nil package.loaded["ds18b20"]=nil ``` - # Methods From fe032edd9cea45ac4220b1983efa30f1bd27535d Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Wed, 16 Aug 2017 15:38:10 -0400 Subject: [PATCH 4/8] Add %p to libc's debug printf (#2062) --- app/libc/dbg_printf.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/libc/dbg_printf.c b/app/libc/dbg_printf.c index 02ba01a0..90d225ec 100644 --- a/app/libc/dbg_printf.c +++ b/app/libc/dbg_printf.c @@ -120,6 +120,10 @@ reswitch: switch (ch = *fmt++) { va_arg(ap, uint32_t) : va_arg(ap, uint32_t); kprintn(put, ul, 10, width, zwidth); break; + case 'p': + ul = va_arg(ap, ptrdiff_t); + kprintn(put, ul, 16, width, zwidth); + break; case 'x': ul = lflag ? va_arg(ap, uint32_t) : va_arg(ap, uint32_t); From 569628789cdf83fa6b0e27bafa5624c6b29b701d Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Thu, 17 Aug 2017 01:23:00 -0400 Subject: [PATCH 5/8] Makefile: add an optional compiler wrapper (#2066) Use as 'make WRAPCC="ccache"', for example. --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 395339d4..840f7a3b 100644 --- a/Makefile +++ b/Makefile @@ -76,9 +76,9 @@ else endif CCFLAGS += -ffunction-sections -fno-jump-tables -fdata-sections AR = xtensa-lx106-elf-ar - CC = xtensa-lx106-elf-gcc + CC = $(WRAPCC) xtensa-lx106-elf-gcc NM = xtensa-lx106-elf-nm - CPP = xtensa-lx106-elf-cpp + CPP = $(WRAPCC) xtensa-lx106-elf-gcc -E OBJCOPY = xtensa-lx106-elf-objcopy FIRMWAREDIR = ../bin/ UNAME_S := $(shell uname -s) From 003c2453c31d29c2138ae435062178d58a04e924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20St=C3=B6r?= Date: Tue, 22 Aug 2017 22:05:24 +0200 Subject: [PATCH 6/8] Add note to mqtt:subscribe Fixes #2039 --- docs/en/modules/mqtt.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/modules/mqtt.md b/docs/en/modules/mqtt.md index a79f9d4e..f445f8e7 100644 --- a/docs/en/modules/mqtt.md +++ b/docs/en/modules/mqtt.md @@ -221,6 +221,10 @@ m:subscribe("/topic",0, function(conn) print("subscribe success") end) m:subscribe({["topic/0"]=0,["topic/1"]=1,topic2=2}, function(conn) print("subscribe success") end) ``` +!!! caution + + Rather than calling `subscribe` multiple times you should use the multiple topics syntax shown in the above example if you want to subscribe to more than one topic at once. + ## mqtt.client:unsubscribe() Unsubscribes from one or several topics. From 0a11e84f360f667da2ed7fc6104bce0cc50acd2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20St=C3=B6r?= Date: Tue, 22 Aug 2017 22:22:36 +0200 Subject: [PATCH 7/8] Add note to BME280 docs A delay is required between `setup` and reading from sensor. Fixes #1994. --- docs/en/modules/bme280.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/en/modules/bme280.md b/docs/en/modules/bme280.md index 2b71cfe4..faa7d9b9 100644 --- a/docs/en/modules/bme280.md +++ b/docs/en/modules/bme280.md @@ -5,7 +5,10 @@ This module provides a simple interface to [BME280/BMP280 temperature/air presssure/humidity sensors](http://www.bosch-sensortec.com/bst/products/all_products/bme280) (Bosch Sensortec). -Note that you must call [`setup()`](#bme280setup) before you can start reading values! +!!! caution + + Note that you must call [`setup()`](#bme280setup) before you can start reading values! Furthermore, there has to be a variable delay between some tens to hundreds of milliseconds between `setup()` and reading measurements. Instead of using a fixed delay you might also poll the sensor until data is delivered e.g. `humi()` not returning `nil` anymore. + ## bme280.altitude() @@ -215,6 +218,7 @@ print(string.format("altitude=%s%d.%02d", curAltsgn<0 and "-" or "", curAlt/100, ``` Or simpler and more efficient + ```lua alt=320 -- altitude of the measurement place From f9e0aab200e974116c569e6241f4270e8afbe637 Mon Sep 17 00:00:00 2001 From: Nathaniel Wesley Filardo Date: Thu, 24 Aug 2017 15:17:31 -0400 Subject: [PATCH 8/8] cron: don't include rtctime_internal.h (#2084) That file is supposed to only be included once because it does things like declare static globals. As it stands, cron doesn't believe time is ticking. Fixes #2080 --- app/modules/cron.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/modules/cron.c b/app/modules/cron.c index 8d93b6af..23f2c02b 100644 --- a/app/modules/cron.c +++ b/app/modules/cron.c @@ -8,7 +8,6 @@ #include "c_string.h" #include "ets_sys.h" #include "time.h" -#include "rtc/rtctime_internal.h" #include "rtc/rtctime.h" #include "stdlib.h" #include "mem.h" @@ -189,7 +188,7 @@ static void cron_handle_time(uint8_t mon, uint8_t dom, uint8_t dow, uint8_t hour static void cron_handle_tmr() { struct rtc_timeval tv; - rtc_time_gettimeofday(&tv); + rtctime_gettimeofday(&tv); if (tv.tv_sec == 0) { // Wait for RTC time ets_timer_arm_new(&cron_timer, 1000, 0, 1); return;