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'