diff --git a/mudpi/extensions/ds18b20/__init__.py b/mudpi/extensions/ds18b20/__init__.py new file mode 100644 index 0000000..15b1738 --- /dev/null +++ b/mudpi/extensions/ds18b20/__init__.py @@ -0,0 +1,12 @@ +""" + DS18B20 Extension + Includes sensor interface for DS18B20. + Works for Dallas 1-wire temperature sensors. +""" +from mudpi.extensions import BaseExtension + + +class Extension(BaseExtension): + namespace = 'ds18b20' + update_interval = 60 + diff --git a/mudpi/extensions/ds18b20/docs.html b/mudpi/extensions/ds18b20/docs.html new file mode 100644 index 0000000..b5a1f94 --- /dev/null +++ b/mudpi/extensions/ds18b20/docs.html @@ -0,0 +1,41 @@ +# DS18B20 +The `ds18b20` extension connects to a DS18B20 device to gather temperature readings. The sensor will return `temperature`. + +This extension does not take an extension level configs and is focused on [interfaces.]({{url('docs/developers-interfaces')}}) + +--- +
+ +## Sensor Interface +Provides a [sensor]({{url('docs/sensors')}}) that returns a DS18B20 readings. + + + + + + + + +
OptionTypeRequiredDescription
key[String]YesUnique slug id for the component
name[String]NoFriendly display name of component. Useful for UI.
one_wire_ID[String]NoThe address of the DS18B20 sensor. This should be set if using mulitple DS18B20 sensors and can be found as a directory in `/sys/bus/w1/devices/` in the format of `28-XXXXXXXXXXXXX` . Default 10
+ +### Config Examples +Here is a config of a complete example sensor. + +```json +"sensor": [{ + "key": "ds18b20_outside", + "interface": "ds18b20", + "name": "Outside temperature", + "one_wire_ID": "28-0000000000001" +}] +``` + +### Data +Here is an example of the data returned by the DS18B20: + +```json +{ + "temperature": 50 +} +``` +--- \ No newline at end of file diff --git a/mudpi/extensions/ds18b20/extension.json b/mudpi/extensions/ds18b20/extension.json new file mode 100644 index 0000000..a46ca2e --- /dev/null +++ b/mudpi/extensions/ds18b20/extension.json @@ -0,0 +1,9 @@ +{ + "name": "DS18B20 1-Wire Temperature Sensor", + "namespace": "ds18b20", + "details": { + "description": "Provides interface for ds18b20 sensors to take tempurature readings.", + "documentation": "https://mudpi.app/docs/sensors/ds18b20" + }, + "requirements": ["glob2", "python-time"] +} diff --git a/mudpi/extensions/ds18b20/sensor.py b/mudpi/extensions/ds18b20/sensor.py new file mode 100644 index 0000000..95b8e0c --- /dev/null +++ b/mudpi/extensions/ds18b20/sensor.py @@ -0,0 +1,133 @@ +""" + DS18B20 Sensor Interface + Connects to a DS18B20 device to get + temperature readings. +""" +import os +import glob +import time + +from mudpi.constants import METRIC_SYSTEM +from mudpi.extensions import BaseInterface +from mudpi.extensions.sensor import Sensor +from mudpi.logger.Logger import Logger, LOG_LEVEL +from mudpi.exceptions import MudPiError, ConfigError + +os.system('modprobe w1-gpio') +os.system('modprobe w1-therm') + + +#device_folder = '/sys/bus/w1/devices/28-XXXXXXXXXXXXX' +#device_folder = glob.glob(base_dir + '/28*')[0] +#device_file = device_folder + '/w1_slave' + +class Interface(BaseInterface): + + def load(self, config): + """ Load DS18B20 sensor component from configs """ + sensor = ds18b20(self.mudpi, config) + self.add_component(sensor) + return True + + def validate(self, config): + if not isinstance(config, list): + config = [config] + + for conf in config: + """ See if 1-wire ID was passed by the user in the config file """ + if not conf.get('one_wire_ID'): + Logger.log( + LOG_LEVEL["debug"], + 'DS18B20 one_wire_ID not set. Will search for device.' + ) + + return config + +class ds18b20(Sensor): + """ DS18B20 Sensor get readings temperature. """ + + """ Properties """ + @property + def id(self): + """ Return a unique id for the component """ + return self.config['key'] + + @property + def name(self): + """ Return the display name of the component """ + return self.config.get('name') or f"{self.id.replace('_', ' ').title()}" + + @property + def state(self): + """ Return the state of the component (from memory, no IO!) """ + return self._state + + @property + def classifier(self): + """ Classification further describing it, effects the data formatting """ + return 'temperature' + + @property + def one_wire_ID(self): + return self.config['one_wire_ID'] + + + """ Methods """ + def init(self): + """To support multiple 1-wire devices check to see if the ID is set in the config. + If the ID is not set, there should only be a single 28-xxxxxxxxx directory in the base directory, so we use that. """ + + base_dir = '/sys/bus/w1/devices' + + if self.config.get('one_wire_ID') and os.path.isdir(base_dir + '/' + self.config.get('one_wire_ID')): + self.device_file = base_dir + '/' + self.config.get('one_wire_ID') + '/w1_slave' + Logger.log( + LOG_LEVEL["debug"], + 'Setting device file to ' + self.device_file + ) + else: + Logger.log( + LOG_LEVEL["debug"], + 'DS18B20 one_wire_ID not set or not found.' + ) + """ Make sure 1-wire device directory exists """ + try: + device_folder = glob.glob(base_dir + '/28*')[0] + except: + Logger.log( + LOG_LEVEL["error"], + 'Failed to find 1-wire device directory. Ensure device is connected and one_wire_ID corret..' + ) + else: + self.device_file = device_folder + '/w1_slave' + return True + + def update(self): + + def read_temp_raw(): + f = open(self.device_file, 'r') + lines = f.readlines() + f.close() + return lines + + lines = read_temp_raw() + while lines[0].strip()[-3:] != 'YES': + time.sleep(0.2) + lines = read_temp_raw() + equals_pos = lines[1].find('t=') + if equals_pos != -1: + temp_string = lines[1][equals_pos+2:] + temperature_c = float(temp_string) / 1000.0 + temperature_f = temperature_c * 9.0 / 5.0 + 32.0 + _temperature = round(temperature_c if self.mudpi.unit_system == METRIC_SYSTEM else temperature_f, 2) + readings = { + 'temperature': _temperature, + } + self._state = readings + return readings + else: + Logger.log( + LOG_LEVEL["error"], + 'Failed to get reading [DS18B20]. Try again!' + ) + return None