Source code for duct.sources.sensors.environment.ds18b20

"""
.. module:: ds18b20
   :platform: Any
   :synopsis: DS18B20 sensor with 1-wire interface presented by /sys/bus/w1

.. moduleauthor:: Colin Alston <colin@tamvera.com>
"""
# pylint: disable=W1514
import asyncio
import os
import pathlib
import logging

from zope.interface import implementer

from duct.interfaces import IDuctSource
from duct.objects import Source


log = logging.getLogger(__name__)


[docs] @implementer(IDuctSource) class DS18B20(Source): """DS18B20 1-wire temperature sensor. This module reads sensors connected with the `wire` kernel module typically used on a Raspberry Pi with the w1-gpio device tree overlay. These can be slow to read if you have a lot of them, so make sure you have a sufficient interval set. :param device_path: Path for w1_therm and w1_gpio devices :type device_path: str :param device_map: A dictionary structure re-mapping sensor serials to custom names :type device_map: dict :param ignore_unmapped: If True any sensor not listed in the device_map is ignored (default: False) :type ignore_unmapped: bool """ def __init__(self, *a, **kw): super().__init__(*a, **kw) self.device_path = pathlib.Path(self.config.get('device_path', '/sys/devices/')) self.device_map = self.config.get('device_map', {}) self.ignore_unmapped = self.config.get('ignore_unmapped', False) self.busses = [d for d in os.listdir(self.device_path) if d.startswith('w1_bus_master')]
[docs] def read_temp(self, bus, sensor_id): """Reads the temperature from a 1-Wire sensor device.""" try: with open(self.device_path/bus/sensor_id/'w1_slave', 'r') as f: lines = f.readlines() if lines[0].strip()[-3:] != 'YES': return None # CRC check failed temp_str = lines[1].split('t=')[-1] return float(temp_str) / 1000.0 except Exception as e: log.warning("Error reading sensor %s: %s", sensor_id, str(e)) return None
[docs] def read_sensors(self, bus): """Read all sensors on the device path""" sensors = [d for d in os.listdir(self.device_path/bus) if d.startswith('28-')] metrics = [] for sensor_id in sensors: if self.ignore_unmapped and (sensor_id not in self.device_map): log.debug("Ignoring unmapped sensor %s", sensor_id) continue label = self.device_map.get(sensor_id, sensor_id) temp = self.read_temp(bus, sensor_id) if temp is not None: metrics.append(self.createEvent('ok', f'{sensor_id} Temperature C', temp, prefix=label)) return metrics
[docs] async def get(self): """Polls all sensors and sends metrics in a batch every POLL_INTERVAL seconds.""" metrics = await asyncio.gather( *(asyncio.to_thread(self.read_sensors, bus) for bus in self.busses)) return sum(metrics, [])