示例#1
0
class GPIO(ComponentSwitch):
    def __init__(self,
                 pin,
                 active_high=True,
                 mqtt_topic=None,
                 instance_name=None,
                 **kwargs):
        mqtt_topic = mqtt_topic or _mqtt.getDeviceTopic(
            "{!s}/{!s}".format(COMPONENT_NAME, str(pin)), is_request=True)
        global _unit_index
        _unit_index += 1
        super().__init__(COMPONENT_NAME,
                         __version__,
                         _unit_index,
                         mqtt_topic=mqtt_topic,
                         instance_name=instance_name
                         or "{!s}_{!s}".format(COMPONENT_NAME, pin),
                         **kwargs)
        self.pin = Pin(pin, machine.Pin.OUT, value=0 if active_high else 1)
        self._state = not active_high
        self._active_high = active_high

    async def _on(self):
        self.pin.value(1 if self._active_high else 0)
        return True

    async def _off(self):
        self.pin.value(0 if self._active_high else 1)
        return True
示例#2
0
class GPIO(Component):
    def __init__(self, pin, mqtt_topic=None, friendly_name=None):
        super().__init__()
        mqtt_topic = mqtt_topic or _mqtt.getDeviceTopic(
            "{!s}/{!s}".format(_component_name, str(pin)), is_request=True)
        self._topic = mqtt_topic
        self._subscribe(self._topic, self.on_message)
        self.pin = Pin(pin, machine.Pin.OUT, value=0)
        self._frn = friendly_name
        self._name = "{!s}_{!s}".format(_component_name, pin)

    async def _discovery(self):
        await self._publishDiscovery(_component_type, self._topic[:-4],
                                     self._name, DISCOVERY_SWITCH, self._frn)

    async def on_message(self, topic, msg, retained):
        _log = logging.getLogger("gpio")
        if msg in _mqtt.payload_on:
            self.pin.value(1)
            return True
        elif msg in _mqtt.payload_off:
            self.pin.value(0)
            return True
        else:
            _log.error("Unknown payload {!r}, GPIO {!s}".format(msg, self.pin))
            return False
示例#3
0
class LEDNotification(Component):
    def __init__(self,
                 pin,
                 on_time=50,
                 off_time=50,
                 iters=20,
                 mqtt_topic=None,
                 friendly_name=None):
        super().__init__()
        self.pin = Pin(pin, machine.Pin.OUT, value=0)
        self.on_time = on_time
        self.off_time = off_time
        self.iters = iters
        self.lock = config.Lock()
        # This makes it possible to use multiple instances of LED
        global _count
        self._count = _count
        _count += 1
        mqtt_topic = mqtt_topic or _mqtt.getDeviceTopic(
            "{!s}{!s}".format(_component_name, self._count), is_request=True)
        self._topic = mqtt_topic
        self._frn = friendly_name
        gc.collect()

    async def _init(self):
        await super()._init()
        self._subscribe(self._topic, self.on_message)
        await _mqtt.subscribe(self._topic, check_retained_state_topic=False)
        # not checking retained state as led only activates single-shot and default state is always off

    async def on_message(self, topic, msg, retain):
        if self.lock.locked():
            return False
        async with self.lock:
            if msg in _mqtt.payload_on:
                _mqtt.schedulePublish(self._topic[:-4], "ON", qos=1)
                for i in range(0, self.iters):
                    self.pin.value(1)
                    await asyncio.sleep_ms(self.on_time)
                    self.pin.value(0)
                    await asyncio.sleep_ms(self.off_time)
                await _mqtt.publish(self._topic[:-4],
                                    "OFF",
                                    qos=1,
                                    retain=True)
        return False  # will not publish the state "ON" to mqtt

    async def _discovery(self):
        name = "{!s}{!s}".format(_component_name, self._count)
        await self._publishDiscovery(_component_type, self._topic[:-4], name,
                                     DISCOVERY_SWITCH, self._frn)
示例#4
0
class WIFILED(ComponentBase):
    def __init__(self, pin, active_high=True, **kwargs):
        super().__init__(COMPONENT_NAME,
                         __version__,
                         discover=False,
                         unit_index=0,
                         **kwargs)
        self.pin = Pin(pin, machine.Pin.OUT, value=0 if active_high else 1)
        self._active_high = active_high
        self._next = []
        asyncio.create_task(self._loop())

    async def _loop(self):
        mqtt = config.getMQTT()
        mqtt.registerWifiCallback(self._wifiChanged)
        mqtt.registerConnectedCallback(self._reconnected)
        await self._flash(500, 1)
        sta = network.WLAN(network.STA_IF)
        st = time.ticks_ms()
        while True:
            while self._next:
                await self._flash(*self._next.pop(0))
                await asyncio.sleep(1)
            if time.ticks_diff(time.ticks_ms(), st) > 60000:  # heartbeat
                st = time.ticks_ms()
                if sta.isconnected():
                    await self._flash(20, 1)
                    await asyncio.sleep_ms(250)
                    await self._flash(20, 1)
                else:
                    await self._flash(500, 3)
            await asyncio.sleep_ms(500)

    async def _flash(self, duration, iters):
        for _ in range(iters):
            self.pin.value(1 if self._active_high else 0)
            await asyncio.sleep_ms(duration)
            self.pin.value(0 if self._active_high else 1)
            await asyncio.sleep_ms(duration)

    async def _wifiChanged(self, state):
        if state is True:
            self._next.append((50, 2))
        else:
            self._next.append((500, 3))

    async def _reconnected(self, client):
        self._next.append((100, 5))
示例#5
0
class LEDNotification(ComponentButton):
    def __init__(self, pin, on_time=50, off_time=50, iters=20, **kwargs):
        self.pin = Pin(pin, machine.Pin.OUT, value=0)
        self.on_time = on_time
        self.off_time = off_time
        self.iters = iters
        # This makes it possible to use multiple instances of LED
        global _unit_index
        _unit_index += 1
        super().__init__(COMPONENT_NAME, __version__, _unit_index, **kwargs)
        gc.collect()

    async def _on(self):
        for _ in range(self.iters):
            self.pin.value(1)
            await asyncio.sleep_ms(self.on_time)
            self.pin.value(0)
            await asyncio.sleep_ms(self.off_time)
示例#6
0
class Mux:
    def __init__(self, shift_pin, store_pin, data_pin, number_multiplexer=1):
        self.shcp = PyPin(shift_pin, machine.Pin.OUT)
        self.stcp = PyPin(store_pin, machine.Pin.OUT)
        self.ds = PyPin(data_pin, machine.Pin.OUT)
        self.__data = bytearray()
        for i in range(0, 8 * number_multiplexer):
            self.__data.append(0)
        self.__size = number_multiplexer
        self.write()

    def write(self):
        self.stcp.value(0)
        for i in range((8 * self.__size) - 1, -1, -1):
            self.shcp.value(0)
            self.ds.value(self.__data[i])
            self.shcp.value(1)
        self.stcp.value(1)

    def __setitem__(self, a, b):
        if b != 1 and b != 0:
            raise ValueError("Value must be 1 or 0")
        self.__data[a] = b

    def __getitem__(self, a):
        return self.__data[a]

    def __delitem__(self, a):
        self.__data[a] = 0

    def set(self, i):
        self.__data[i] = 1

    def clear(self, i):
        self.__data[i] = 0

    def getSize(self):
        """ Get number of pins"""
        return self.__size * 8

    def Pin(self, i, *args, **kwargs):
        return Pin(self, i)
示例#7
0
class EC(Component):
    DEBUG = False

    def __init__(self, r1, ra, adc, power_pin, ppm_conversion, temp_coef, k, temp_sensor,
                 precision_ec=3, interval=None, topic_ec=None, topic_ppm=None,
                 friendly_name_ec=None, friendly_name_ppm=None):
        super().__init__()
        self._interval = interval or config.INTERVAL_SEND_SENSOR
        self._prec_ec = int(precision_ec)
        self._adc = ADC(adc)
        self._ppin = Pin(power_pin, machine.Pin.OUT)
        self._r1 = r1
        self._ra = ra
        self._ppm_conversion = ppm_conversion
        self._temp_coef = temp_coef
        self._k = k
        self._temp = temp_sensor
        if hasattr(temp_sensor, "temperature") is False:
            raise AttributeError(
                "Temperature sensor {!s}, type {!s} has no async method temperature()".format(temp_sensor,
                                                                                              type(temp_sensor)))
        gc.collect()
        self._ec25 = None
        self._ppm = None
        self._time = 0
        # This makes it possible to use multiple instances of MySensor
        global _count
        self._count = _count
        _count += 1
        self._topic_ec = topic_ec or _mqtt.getDeviceTopic("{!s}/{!s}".format("EC", self._count))
        self._topic_ppm = topic_ppm or _mqtt.getDeviceTopic("{!s}/{!s}".format("PPM", self._count))
        self._frn_ec = friendly_name_ec
        self._frn_ppm = friendly_name_ppm

    async def _init(self):
        await super()._init()
        gen = self._read
        interval = self._interval
        while True:
            await gen()
            await asyncio.sleep(interval)

    async def _discovery(self):
        sens = '"unit_of_meas":"mS",' \
               '"val_tpl":"{{ value|float }}",'
        name = "{!s}{!s}{!s}".format(_component_name, self._count, "EC25")
        await self._publishDiscovery(_component_type, self._topic_ec, name, sens, self._frn_ec or "EC25")
        del sens, name
        gc.collect()
        sens = '"unit_of_meas":"ppm",' \
               '"val_tpl":"{{ value|int }}",'
        name = "{!s}{!s}{!s}".format(_component_name, self._count, "PPM")
        await self._publishDiscovery(_component_type, self._topic_ppm, name, sens, self._frn_ppm or "PPM")
        del sens, name
        gc.collect()

    async def _read(self, publish=True):
        if time.ticks_ms() - self._time < 5000:
            self._time = time.ticks_ms()
            await asyncio.sleep(5)
        temp = await self._temp.temperature(publish=False)
        if temp is None:
            await asyncio.sleep(3)
            temp = await self._temp.temperature(publish=False)
            if temp is None:
                _log.warn("Couldn't get temperature, aborting EC measurement")
                self._ec25 = None
                self._ppm = None
                return None, None
        self._ppin.value(1)
        vol = self._adc.readVoltage()
        # vol = self._adc.readVoltage()
        # micropython on esp is probably too slow to need this. It was intended for arduino
        if self.DEBUG is True:
            print("Temp", temp)
            print("V", vol)
        self._ppin.value(0)
        if vol >= self._adc.maxVoltage():
            ec25 = 0
            ppm = 0
            await _log.asyncLog("warn", "Cable not in fluid")
        else:
            if vol <= 0.5:
                _log.warn("Voltage <=0.5, change resistor")
            rc = (vol * (self._r1 + self._ra)) / (self._adc.maxVoltage() - vol)
            # rc = rc - self._ra # sensor connected to ground, not a pin, therefore no Ra
            ec = 1000 / (rc * self._k)
            ec25 = ec / (1 + self._temp_coef * (temp - 25.0))
            ppm = int(ec25 * self._ppm_conversion * 1000)
            ec25 = round(ec25, self._prec_ec)
            if self.DEBUG:
                print("Rc", rc)
                print("EC", ec)
                print("EC25", ec25, "MilliSimens")
                print("PPM", ppm)
        self._ec25 = ec25
        self._ppm = ppm
        self._time = time.ticks_ms()

        if publish:
            await _mqtt.publish(self._topic_ec, ("{0:." + str(self._prec_ec) + "f}").format(ec25))
            await _mqtt.publish(self._topic_ppm, ppm)
        return ec25, ppm

    async def ec(self, publish=True):
        if time.ticks_ms() - self._time > 5000:
            await self._read(publish)
        return self._ec25

    async def ppm(self, publish=True):
        if time.ticks_ms() - self._time > 5000:
            await self._read(publish)
        return self._ppm
示例#8
0
class Bell(ComponentSensor):
    def __init__(self,
                 pin,
                 debounce_time,
                 on_time=None,
                 direction=None,
                 pull_up=True,
                 friendly_name=None,
                 friendly_name_last=None,
                 confirmations=1,
                 ac_signal=False,
                 **kwargs):
        global _unit_index
        _unit_index += 1
        super().__init__(COMPONENT_NAME,
                         __version__,
                         _unit_index,
                         logger=_log,
                         interval_reading=0.001,
                         interval_publish=0.001,
                         expose_intervals=False,
                         **kwargs)
        self._PIN_BELL_IRQ_DIRECTION = direction or machine.Pin.IRQ_FALLING
        self._debounce_time = debounce_time
        self._on_time = on_time or 500
        self._event_bell = EventISR(delay_ms=10)
        self._pin_bell = Pin(pin, machine.Pin.IN,
                             machine.Pin.PULL_UP if pull_up else None)
        self._pin_bell.irq(trigger=self._PIN_BELL_IRQ_DIRECTION,
                           handler=self.__irqBell)
        self._timer_delay = 0
        self._last_activation = 0
        self._confirmations = confirmations
        self._ac = ac_signal
        gc.collect()
        self._addSensorType("bell",
                            friendly_name=friendly_name,
                            binary_sensor=True,
                            discovery_type=_DISCOVERY_BELL,
                            retained_publication=True,
                            topic=_mqtt.getDeviceTopic("bell{!s}".format(
                                self._count)))
        self._addSensorType("last_bell",
                            friendly_name=friendly_name_last,
                            topic=_mqtt.getDeviceTopic("last_bell{!s}".format(
                                self._count)),
                            discovery_type=DISCOVERY_TIMELAPSE,
                            retained_publication=True)
        _log.info("Bell initialized")
        gc.collect()

    async def _read(self):
        if self._event_bell.is_set():
            # if event is set, wait the on_time and reset the state to publish "off"
            await asyncio.sleep_ms(
                self._on_time -
                time.ticks_diff(time.ticks_ms(), self.getTimestamp("bell")))
            await self._setValue("bell", False)
            self._event_bell.clear()
            # print(time.ticks_us(), "loop cleared event")
            return
        # print(time.ticks_us(), "loop awaiting event")
        await self._event_bell.wait()
        # print(time.ticks_us(), "loop got event")
        sl = int(self._debounce_time /
                 self._confirmations) if self._confirmations > 0 else 0
        confirmations = 0
        for _ in range(0, self._confirmations):
            diff = time.ticks_diff(time.ticks_us(), self._timer_delay)
            value = self._pin_bell.value()
            # print(time.ticks_us(), "Timer took", diff / 1000, "ms, pin value", value)
            if not self._ac and (
                    self._PIN_BELL_IRQ_DIRECTION == machine.Pin.IRQ_FALLING and value == 1 \
                    or self._PIN_BELL_IRQ_DIRECTION == machine.Pin.IRQ_RISING and value == 0):
                # print(time.ticks_us(), "pin value didn't stay")
                self._event_bell.clear()
                return False  # return False so no value gets published
            elif self._ac and (
                    self._PIN_BELL_IRQ_DIRECTION == machine.Pin.IRQ_FALLING and value == 0 \
                    or self._PIN_BELL_IRQ_DIRECTION == machine.Pin.IRQ_RISING and value == 1):
                confirmations += 1
            self._timer_delay = time.ticks_us()
            await asyncio.sleep_ms(sl)
        # print(self._ac, confirmations, self._confirmations)
        if self._ac:
            if confirmations > 0:
                # print(time.ticks_us(), "ac signal confirmed")
                pass
            else:
                # print(time.ticks_us(), "no ac signal, only irq")
                self._event_bell.clear()
                return False
        # print(time.ticks_us(), "irq confirmed")
        diff = time.ticks_diff(time.ticks_ms(), self._last_activation)
        if diff > 10000:
            _log.error("Bell rang {!s}s ago, not activated ringing".format(
                diff / 1000))
            self._event_bell.clear()
            return False  # return False so no value gets published
        await self._setValue("bell", True)
        t = time.localtime()
        await self._setValue(
            "last_bell", "{}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}".format(
                t[0], t[1], t[2], t[3], t[4], t[5]))
        if diff > 500:
            _log.warn(
                "Bell rang {!s}ms ago, activated ringing anyways".format(diff))

    def __irqBell(self, p):
        # print(time.ticks_us(), "irq bell", self._event_bell.is_set())
        if self._event_bell.is_set() is True:
            return
        # print("event set")
        self._timer_delay = time.ticks_us()
        self._event_bell.set()
        self._last_activation = time.ticks_ms()
示例#9
0
class Bell(Component):
    def __init__(self, pin, debounce_time, on_time=None, irq_direction=None, mqtt_topic=None, friendly_name=None,
                 friendly_name_last=None):
        super().__init__()
        self._topic = mqtt_topic or _mqtt.getDeviceTopic(_component_name)
        self._PIN_BELL_IRQ_DIRECTION = irq_direction or machine.Pin.IRQ_FALLING
        self._debounce_time = debounce_time
        self._on_time = on_time or 500
        self._pin_bell = pin
        self._last_activation = 0
        self._frn = friendly_name
        self._frn_l = friendly_name_last

    async def _init(self):
        await super()._init()
        if self._PIN_BELL_IRQ_DIRECTION == machine.Pin.IRQ_FALLING:
            self._pin_bell = Pin(self._pin_bell, machine.Pin.IN, machine.Pin.PULL_UP)
        else:
            self._pin_bell = Pin(self._pin_bell, machine.Pin.IN)
        self._event_bell = Event()
        self._timer_lock = Lock()
        irq = self._pin_bell.irq(trigger=self._PIN_BELL_IRQ_DIRECTION, handler=self.__irqBell)
        self._event_bell.clear()
        asyncio.get_event_loop().create_task(self.__bell())
        self._timer_bell = machine.Timer(1)
        await _log.asyncLog("info", "Bell initialized")
        gc.collect()

    async def __bell(self):
        while True:
            await self._event_bell
            diff = time.ticks_diff(time.ticks_ms(), self._last_activation)
            if diff > 10000:
                _log.error("Bell rang {!s}s ago, not activated ringing".format(diff / 1000))
                self._event_bell.clear()
                return
            else:
                await _mqtt.publish(self._topic, "ON", qos=1)
                await asyncio.sleep_ms(self._on_time)
                await _mqtt.publish(self._topic, "OFF", qos=1, retain=True)
                if config.RTC_SYNC_ACTIVE:
                    t = time.localtime()
                    await _mqtt.publish(_mqtt.getDeviceTopic("last_bell"),
                                        "{}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}".format(t[0],
                                                                                       t[1], t[2],
                                                                                       t[3], t[4],
                                                                                       t[5]),
                                        qos=1, retain=True)
                self._event_bell.clear()
                if diff > 500:
                    _log.warn("Bell rang {!s}ms ago, activated ringing".format(diff))

    def __irqBell(self, p):
        if self._timer_lock.locked() is True or self._event_bell.is_set() is True:
            return
        else:
            self._timer_lock.acquire()  # not checking return value as we checked locked state above
            self._timer_bell.init(period=self._debounce_time,
                                  mode=machine.Timer.ONE_SHOT, callback=self.__irqTime)

    def __irqTime(self, t):
        if self._PIN_BELL_IRQ_DIRECTION == machine.Pin.IRQ_FALLING and self._pin_bell.value() == 0:
            self._last_activation = time.ticks_ms()
            self._event_bell.set()
        elif self._PIN_BELL_IRQ_DIRECTION == machine.Pin.IRQ_RISING and self._pin_bell.value() == 1:
            self._last_activation = time.ticks_ms()
            self._event_bell.set()
        self._timer_bell.deinit()
        self._timer_lock.release()

    async def _discovery(self):
        await self._publishDiscovery("binary_sensor", self._topic, "bell", '"ic":"mdi:bell",', "Doorbell", self._frn)
        gc.collect()
        if config.RTC_SYNC_ACTIVE is True:
            await self._publishDiscovery("sensor", _mqtt.getDeviceTopic("last_bell"), "last_bell", TIMELAPSE_TYPE,
                                         "Last Bell", self._frn_l)
            gc.collect()
示例#10
0
class Amux:
    def __init__(self,
                 s0,
                 s1,
                 s2,
                 s3=None,
                 mux=None,
                 adc=None,
                 return_voltages=False):
        """ It is possibile to initialize with:
            - pin numbers (or string on esp8266)
            - mux object and pin numbers (of mux pins)
            - Pin objects (either from machine or mux Pin objects [no mux object needed], or Arduino)
            :type return_voltages: bool, True returns voltages on .read() else raw adc value
            :type mux: Mux object if a multiplexer is used
            :type adc: ADC pin number (esp32) or None (esp8266) or Arduino ADC object or any ADC object
            Amux uses default return values of ADC in .read()
            --> On esp8266/esp32 raw, on esp32_LoBo voltage
            s3 is optional, only needed if 16 pins are used, 8 pins possible with s0-s2.
            Amux can be read like a list: value=amux[2]
        """
        if mux:
            # MUX pin numbers, not pin objects
            self._s0 = s0
            self._s1 = s1
            self._s2 = s2
            self._s3 = s3
            self._mux = mux
        else:
            # Pin will take care of returning the correct object
            self._s0 = Pin(s0, machine.Pin.OUT)
            self._s1 = Pin(s1, machine.Pin.OUT)
            self._s2 = Pin(s2, machine.Pin.OUT)
            if s3:
                self._s3 = Pin(s3, machine.Pin.OUT)
        if s3:
            self.__size = 16
        else:
            self.__size = 8
        self._return_voltages = return_voltages
        self._adc = _ADC(
            adc
        )  # no matter what adc is, _ADC will return an object with the unified ADC API

    def setReturnVoltages(self, vol):
        self._return_voltages = vol

    def __getitem__(self, a):
        return self.read(a)

    def getSize(self):
        """ Get number of pins"""
        return self.__size

    def read(self, a, return_voltage=None):
        if a >= self.__size:
            raise ValueError("Maximum Port number is {!s}".format(self.__size -
                                                                  1))
        if type(self._s0) == int:  # mux pins
            if self.__size == 16:
                self._mux[self._s3] = (1 if a & 8 else 0)
            self._mux[self._s2] = (1 if a & 4 else 0)
            self._mux[self._s1] = (1 if a & 2 else 0)
            self._mux[self._s0] = (1 if a & 1 else 0)
            self._mux.write()
        else:
            if self.__size == 16:
                self._s3.value(1 if a & 8 else 0)
            self._s2.value(1 if a & 4 else 0)
            self._s1.value(1 if a & 2 else 0)
            self._s0.value(1 if a & 1 else 0)
        if return_voltage is True or return_voltage is None and self._return_voltages is True:
            return self._adc.readVoltage()
        else:
            return self._adc.readRaw()

    def readVoltage(self, a):
        return self.read(a, return_voltage=True)

    def readRaw(self, a):
        return self.read(a, return_voltage=False)

    def ADC(self, i, *args, **kwargs):
        """ compatible to machine.ADC, returns an ADC object"""
        return ADC(self, i)

    def atten(self, *args, **kwargs):
        self._adc.atten(*args, **kwargs)

    def width(self, *args, **kwargs):
        self._adc.width(*args, **kwargs)
示例#11
0
class Pmux:
    def __init__(self,
                 s0,
                 s1,
                 s2,
                 pin,
                 s3=None,
                 mux=None,
                 pin_direction="OUT",
                 pin_pull=None):
        """ It is possibile to initialize with:
            - pin numbers (or string on esp8266)
            - mux object and pin numbers (of mux pins)
            - Pin objects (either from machine or mux Pin objects [no mux object needed])
            :type mux: Mux object if a multiplexer is used
            :type pin: pin number or string (esp8266)
            :type pin_direction: str of pin_direction
            s3 is optional, only needed if 16 pins are used, 8 pins possible with s0-s2.
            pmux can be read like a list: value=amux[2]
            pmux can be set like a list: amux[2]=1
        """
        if mux:
            self.s0 = s0
            self.s1 = s1
            self.s2 = s2
            self.s3 = s3
            self.mux = mux
        else:
            if type(s0) in (int, str):
                self.s0 = PyPin(s0, machine.Pin.OUT)
                self.s1 = PyPin(s1, machine.Pin.OUT)
                self.s2 = PyPin(s2, machine.Pin.OUT)
                if s3:
                    self.s3 = PyPin(s3, machine.Pin.OUT)
            else:
                self.s0 = s0
                self.s1 = s1
                self.s2 = s2
                if s3:
                    self.s3 = s3
        if s3:
            self.__size = 16
        else:
            self.__size = 8
        self._selected_pin = None
        if pin_direction not in dir(machine.Pin):
            raise TypeError(
                "Pin_direction {!r} does not exist".format(pin_direction))
        if pin_pull not in dir(machine.Pin):
            raise TypeError("Pin_pull {!s} does not exist".format(pin_pull))
        self.pin = PyPin(pin, getattr(machine.Pin, pin_direction), pin_pull)

    def __getitem__(self, a):
        return self.value(a)

    def __setitem__(self, a, direction):
        return self.value(a, direction)

    def getSize(self):
        """ Get number of pins"""
        return self.__size

    def _selectPin(self, a):
        if a >= self.__size:
            raise ValueError("Maximum Port number is {!s}".format(self.__size -
                                                                  1))
        if type(self.s0) == int:  # mux pins
            if self.__size == 16:
                self.mux[self.s3] = (1 if a & 8 else 0)
            self.mux[self.s2] = (1 if a & 4 else 0)
            self.mux[self.s1] = (1 if a & 2 else 0)
            self.mux[self.s0] = (1 if a & 1 else 0)
            self.mux.write()
        else:
            if self.__size == 16:
                self.s3.value(1 if a & 8 else 0)
            self.s2.value(1 if a & 4 else 0)
            self.s1.value(1 if a & 2 else 0)
            self.s0.value(1 if a & 1 else 0)

    def value(self, a, value=None):
        if a != self._selected_pin:
            # only select pin if needed as it would slow IO-operations down and screw timings
            self._selectPin(a)
        if value is None:
            return self.pin.value()
        else:
            return self.pin.value(value)

    def mode(self, mode):
        if type(mode) == str:
            if mode not in dir(machine.Pin):
                raise TypeError("Mode {!r} is not available".format(mode))
            mode = getattr(machine.Pin, mode)
        self.pin.mode(mode)

    def pull(self, p=None):
        return self.pin.pull(p)

    def drive(self, d=None):
        return self.pin.drive(d)

    def init(self, *args, **kwargs):
        self.pin.init(*args, **kwargs)

    def Pin(self, p):
        return Pin(self, p)
示例#12
0
class HCSR04(ComponentSensor):
    def __init__(self, pin_trigger, pin_echo, timeout=30000, temp_sensor: ComponentSensor = None,
                 precision: int = 2, offset: float = 0, sleeping_time: int = 20,
                 iterations: int = 30, percentage_failed_readings_abort: float = 0.66,
                 value_template=None, friendly_name=None, **kwargs):
        """
        HC-SR04 ultrasonic sensor.
        Be sure to connect it to 5V but use a voltage divider to connect the Echo pin to an ESP.
        :param pin_trigger: pin number/object of trigger pin
        :param pin_echo: pin number/object of echo pin
        :param timeout: reading timeout, corresponds to sensor limit range of 4m
        :param temp_sensor: temperature sensor object
        :param precision: int, precision of distance value published and returned
        :param offset: float, distance value offset, shouldn't be needed
        :param sleeping_time: int, sleeping time between reading iterations
        :param iterations: int, reading iterations per sensor reading
        :param percentage_failed_readings_abort: float, if a higher percentage of readings was bad, the reading will be aborted
        :param value_template: optional template can be used to measure the reverse distance (e.g. water level)
        :param friendly_name: friendly name for homeassistant gui by mqtt discovery, defaults to "Distance"
        """
        global _unit_index
        _unit_index += 1
        super().__init__(COMPONENT_NAME, __version__, _unit_index, logger=_log, **kwargs)
        self._tr = Pin(pin_trigger, mode=machine.Pin.OUT)
        self._tr.value(0)
        self._ec = Pin(pin_echo, mode=machine.Pin.IN)
        self._to = timeout
        self._sleep = sleeping_time
        self._iters = iterations
        self._pfr = percentage_failed_readings_abort
        if temp_sensor is not None:
            self.checkSensorType(temp_sensor, SENSOR_TEMPERATURE)
        self._temp: ComponentSensor = temp_sensor
        self._addSensorType("distance", precision, offset, value_template or VALUE_TEMPLATE_FLOAT,
                            "cm", friendly_name, discovery_type=DISCOVERY_DISTANCE)

    def _pulse(self) -> int:
        """
        Send a pulse and wait for the echo pin using machine.time_pulse_us() to measure us.
        :return: int
        """
        tr = self._tr
        tr.value(0)
        time.sleep_us(5)
        tr.value(1)
        time.sleep_us(10)
        tr.value(0)
        try:
            return machine.time_pulse_us(self._ec, 1, self._to)
        except OSError as e:
            if e.args[0] == 100:  # TIMEOUT
                raise OSError("Object too far")
            raise e

    async def _read(self):
        if self._temp is not None:
            temp = await self._temp.getValue(SENSOR_TEMPERATURE, publish=self._intrd > 5,
                                             timeout=5 if self._intrd > 20 else 0)
            if temp is None:  # only log errors if reading interval allows it
                await _log.asyncLog("warn",
                                    "Couldn't read temp sensor, using fallback calculation",
                                    timeout=10 if self._intrd > 20 else 0)
        else:
            temp = None
        val = []
        diffs = []
        warnings = 0  # probably not going to happen that both warning types occur at the same time
        warning = "minimum distance reached or different problem"
        for _ in range(self._iters):
            try:
                a = time.ticks_us()
                pt = self._pulse()
                b = time.ticks_us()
                if pt > 175:  # ~3cm, sensor minimum distance, often read although other problems
                    val.append(pt)
                    diffs.append(time.ticks_diff(b, a))
                else:
                    warnings += 1
            except OSError as e:
                warning = e
                warnings += 1
            await asyncio.sleep_ms(self._sleep)
        if warnings > self._iters * self._pfr:  # self._pbr sensor readings are bad
            if config.DEBUG:
                print("HCSR04 len readings", len(val), "/", self._iters, "sleep", self._sleep)
                print("HCSR04 readings", val)
                print("HCSR04 t_diffs:", diffs)
            await self._setValue("distance", None, log_error=False)
            await _log.asyncLog("error",
                                "Too many bad sensor readings, error:", warning,
                                timeout=10 if self._intrd > 20 else 0)
            return
        # removing extreme values until only 5-6 remain
        val2 = [v for v in val]
        while len(val) >= 7:
            val.remove(max(val))
            val.remove(min(val))
        pt = sum(val) / len(val)
        if temp is None:
            dt = (pt / 2) / 29.14
        else:
            dt = (pt / 2) * ((331.5 + (0.6 * temp)) / 10000)
        if config.DEBUG:
            print("HCSR04 distance", dt, "temp ", temp, "len readings", len(val2), "/",
                  self._iters, "sleep", self._sleep)
            print("HCSR04 readings", val2)
            print("HCSR04 t_diffs:", diffs)
            print("HCSR04 used readings", val)
        if dt < 0:
            await self._setValue("distance", None, log_error=False)
            await _log.asyncLog("warn", "Sensor reading <0", timeout=10 if self._intrd > 20 else 0)
            return
        await self._setValue("distance", dt, timeout=10 if self._intrd > 20 else 0)
示例#13
0
class Moisture(Component):
    def __init__(self,
                 adc_pin,
                 water_voltage,
                 air_voltage,
                 sensor_types,
                 power_pin=None,
                 power_warmup=None,
                 publish_converted_value=False,
                 mqtt_topic=None,
                 interval=None,
                 friendly_name=None,
                 friendly_name_cv=None):
        super().__init__()
        self._adc = ADC(adc_pin)
        if power_pin is None:
            self._ppin = None
        else:
            if type(power_pin) == list:
                self._ppin = []
                for pin in power_pin:
                    self._ppin.append(Pin(pin, machine.Pin.OUT))
            else:
                self._ppin = Pin(power_pin, machine.Pin.OUT)
        self._power_warmup = power_warmup or None if power_pin is None else 10
        self._sensor_types = sensor_types
        if isinstance(
                self._adc, pyADC
        ):  # pyADC provides unified single ADC interface, not AMUX
            raise TypeError("Single ADC (no Amux) can't have multiple sensors")
        self._water_v = water_voltage
        self._air_v = air_voltage
        if type(sensor_types) == list:
            if type(water_voltage) != list or type(air_voltage) != list:
                raise TypeError(
                    "Voltages have to be lists with multiple sensor_types")
        self._pub_cv = publish_converted_value
        self._topic = mqtt_topic or _mqtt.getDeviceTopic("moisture")
        self._interval = interval or config.INTERVAL_SEND_SENSOR
        self._lock = Lock()
        self._frn = friendly_name
        self._frn_cv = friendly_name_cv
        gc.collect()

    async def _init(self):
        await super()._init()
        while True:
            await self.humidity()
            await asyncio.sleep(self._interval)

    def _getConverted(self, sensor_type, voltage):
        if voltage is None:
            return None
        air_voltage = self._air_v if type(
            self._air_v) != list else self._air_v[sensor_type]
        water_voltage = self._water_v if type(
            self._water_v) != list else self._water_v[sensor_type]
        if sensor_type == 0:  # std sensor
            if voltage > (water_voltage - air_voltage) / 2 + air_voltage:
                return "ON"  # wet
            else:
                return "OFF"  # dry
        elif sensor_type == 1:  # capacitive
            if voltage > air_voltage - (air_voltage - water_voltage) / 2:
                return "OFF"  # dry
            else:
                return "ON"  # wet
        else:
            raise NotImplementedError(
                "Sensor type {!s} not implemented".format(sensor_type))

    def _getPercentage(self, sensor_type, voltage):
        if voltage is None:
            return None
        air_voltage = self._air_v if type(
            self._air_v) != list else self._air_v[sensor_type]
        water_voltage = self._water_v if type(
            self._water_v) != list else self._water_v[sensor_type]
        if sensor_type == 0:  # std sensor:
            diff = water_voltage - air_voltage
            if voltage < air_voltage:
                return 0
            elif voltage > water_voltage:
                return 100
            return round((voltage - air_voltage) / diff * 100)
        elif sensor_type == 1:  # capacitive
            diff = air_voltage - water_voltage
            if voltage > air_voltage:
                return 0
            elif voltage < water_voltage:
                return 100
            return round((diff - (voltage - water_voltage)) / diff * 100)
        else:
            raise NotImplementedError(
                "Sensor type {!s} not implemented".format(sensor_type))

    async def _read(self, publish=True) -> list:
        res = []
        i = 0
        amux = not isinstance(self._adc, pyADC)
        async with self._lock:
            if type(self._sensor_types) == list:
                sensors = self._sensor_types
            elif amux is True:
                sensors = [self._sensor_types] * self._adc.getSize()
            else:
                sensors = [self._sensor_types]
            for sensor in sensors:
                if self._ppin is not None:
                    if type(self._ppin) == list:
                        self._ppin[sensor].value(1)
                    else:
                        self._ppin.value(1)
                    await asyncio.sleep_ms(self._power_warmup)
                voltage = None
                if sensor is None:
                    res.append(None)
                else:
                    voltage = 0
                    for j in range(3):
                        voltage += self._adc.readVoltage(
                            i) if amux else self._adc.readVoltage()
                    voltage /= 3
                    res.append(self._getPercentage(sensor, voltage))
                if publish:
                    await _mqtt.publish(self._topic + "/" + str(i), res[-1])
                    if self._pub_cv:
                        await _mqtt.publish(
                            self._topic + "/" + str(i) + "/conv",
                            self._getConverted(sensor, voltage))
                if self._ppin is not None:
                    if type(self._ppin) == list:
                        self._ppin[sensor].value(0)
                    else:
                        self._ppin.value(0)
                gc.collect()
                i += 1
        if len(res) == 0:
            return [None] * len(sensors)
        return res

    async def _discovery(self):
        amux = not isinstance(self._adc, pyADC)
        if type(self._sensor_types) == list:
            im = len(self._sensor_types)
        elif amux is True:
            im = self._adc.getSize()
        else:
            im = 1
        for i in range(im):
            if self._pub_cv:
                name = "{!s}{!s}CV".format(_component_name, i)
                sens = DISCOVERY_BINARY_SENSOR.format(
                    "moisture")  # device_class
                t = "{!s}/{!s}/conv".format(self._topic, i)
                await self._publishDiscovery("binary_sensor", t, name, sens,
                                             self._frn_cv or "Moisture")
            name = "{!s}{!s}".format(_component_name, i)
            t = "{!s}/{!s}".format(self._topic, i)
            sens = DISCOVERY_SENSOR.format(
                "humidity",  # device_class
                "%",  # unit_of_measurement
                "{{ value|float }}")  # value_template
            await self._publishDiscovery(_component_type, t, name, sens,
                                         self._frn or "Moisture rel.")
            del name, sens, t
            gc.collect()

    async def humidity(self, publish=True) -> list:
        """
        Returns a list of all sensor values.
        Does currently not conform to new API definitions.
        """
        return await self._read(publish=publish)
示例#14
0
class Bell(ComponentSensor):
    def __init__(self, pin, debounce_time, on_time=None, direction=None, pull_up=True,
                 friendly_name=None, friendly_name_last=None, confirmations=1,
                 ac_signal=False, **kwargs):
        global _unit_index
        _unit_index += 1
        super().__init__(COMPONENT_NAME, __version__, _unit_index, logger=_log,
                         interval_reading=0.001, interval_publish=0.001, expose_intervals=False,
                         **kwargs)
        self._low_active = True if direction == 2 else False
        self._debounce_time = debounce_time
        self._on_time = on_time or 500
        self._pin_bell = Pin(pin, machine.Pin.IN, machine.Pin.PULL_UP if pull_up else None)
        self._timer_delay = 0
        self._last_activation = 0
        self._confirmations = confirmations
        self._ac = ac_signal
        self._active = False
        gc.collect()
        self._addSensorType("bell", friendly_name=friendly_name, binary_sensor=True,
                            discovery_type=_DISCOVERY_BELL, retained_publication=True,
                            topic=_mqtt.getDeviceTopic("bell{!s}".format(self._count)))
        self._addSensorType("last_bell", friendly_name=friendly_name_last,
                            topic=_mqtt.getDeviceTopic("last_bell{!s}".format(self._count)),
                            discovery_type=DISCOVERY_TIMELAPSE, retained_publication=True)
        _log.info("Bell initialized")
        gc.collect()

    async def _read(self):
        if self._active:
            # if bell was active, wait the on_time and reset the state to publish "off"
            await asyncio.sleep_ms(
                self._on_time - time.ticks_diff(time.ticks_ms(), self.getTimestamp("bell")))
            await self._setValue("bell", False)
            self._active = False
            # print(time.ticks_us(), "loop cleared event")
            return
        # print(time.ticks_us(), "loop awaiting pin value change")
        while self._pin_bell.value() == self._low_active:
            await asyncio.sleep_ms(20)
        self._timer_delay = time.ticks_us()
        self._last_activation = time.ticks_ms()
        # print(time.ticks_us(), "loop pin value changed")
        sl = int(self._debounce_time / self._confirmations) if self._confirmations > 0 else 0
        diff = []
        confirmations = 0
        for _ in range(0, self._confirmations):
            self._timer_delay = time.ticks_us()
            await asyncio.sleep_ms(sl)
            diff.append(time.ticks_diff(time.ticks_us(), self._timer_delay))
            value = self._pin_bell.value()
            # print(time.ticks_us(), "Timer took", diff / 1000, "ms, pin value", value)
            if not self._ac and value == self._low_active:
                # print(time.ticks_us(), "pin value didn't stay")
                # print(diff)
                return False  # return False so no value gets published
            elif self._ac and value != self._low_active:
                confirmations += 1
        if self._ac:
            if confirmations > 0:
                # print(time.ticks_us(), "ac signal confirmed")
                pass
            else:
                # print(time.ticks_us(), "no ac signal, only irq")
                return False
        # print(time.ticks_us(), "pin change confirmed")
        # print(diff)
        diff = time.ticks_diff(time.ticks_ms(), self._last_activation)
        if diff > 10000:
            _log.error("Bell rang {!s}s ago, not activated ringing".format(diff / 1000))
            return False  # return False so no value gets published
        self._active = True
        await self._setValue("bell", True)
        t = time.localtime()
        await self._setValue("last_bell",
                             "{}-{:02d}-{:02d} {:02d}:{:02d}:{:02d}".format(t[0], t[1],
                                                                            t[2], t[3],
                                                                            t[4], t[5]))
        if diff > 500:
            _log.warn("Bell rang {!s}ms ago, activated ringing anyways".format(diff))
示例#15
0
class HCSR04(ComponentSensor):
    def __init__(self,
                 pin_trigger,
                 pin_echo,
                 timeout=30000,
                 temp_sensor: ComponentSensor = None,
                 precision=2,
                 offset=0,
                 interval_publish=None,
                 interval_reading=None,
                 mqtt_topic=None,
                 value_template=None,
                 friendly_name=None,
                 discover=True,
                 expose_intervals=False,
                 intervals_topic=None):
        """
        HC-SR04 ultrasonic sensor.
        Be sure to connect it to 5V but use a voltage divider to connect the Echo pin to an ESP.
        :param pin_trigger: pin number/object of trigger pin
        :param pin_echo: pin number/object of echo pin
        :param timeout: reading timeout, corresponds to sensor limit range of 4m
        :param temp_sensor: temperature sensor object
        :param precision: int, precision of distance value published and returned
        :param offset: float, distance value offset, shouldn't be needed
        :param interval_publish: seconds, set to interval_reading to publish every reading. -1 for not publishing.
        :param interval_reading: seconds, set to -1 for not reading/publishing periodically. >0 possible for reading, 0 not allowed for reading..
        :param mqtt_topic: distance mqtt topic
        :param value_template: optional template can be used to measure the reverse distance (e.g. water level)
        :param friendly_name: friendly name for homeassistant gui by mqtt discovery, defaults to "Distance"
        :param discover: boolean, if the device should publish its discovery
        :param expose_intervals: Expose intervals to mqtt so they can be changed remotely
        :param intervals_topic: if expose_intervals then use this topic to change intervals.
        Defaults to <home>/<device-id>/<COMPONENT_NAME><_unit_index>/interval/set
        Send a dictionary with keys "reading" and/or "publish" to change either/both intervals.
        """
        global _unit_index
        _unit_index += 1
        super().__init__(COMPONENT_NAME, __version__, _unit_index, discover,
                         interval_publish, interval_reading, mqtt_topic, _log,
                         expose_intervals, intervals_topic)
        self._tr = Pin(pin_trigger, mode=machine.Pin.OUT)
        self._tr.value(0)
        self._ec = Pin(pin_echo, mode=machine.Pin.IN)
        self._to = timeout
        self.checkSensorType(temp_sensor, SENSOR_TEMPERATURE)
        self._temp: ComponentSensor = temp_sensor
        self._addSensorType("distance",
                            precision,
                            offset,
                            value_template or VALUE_TEMPLATE_FLOAT,
                            "cm",
                            friendly_name,
                            discovery_type=DISCOVERY_DISTANCE)

    def _pulse(self) -> int:
        """
        Send a pulse and wait for the echo pin using machine.time_pulse_us() to measure us.
        :return: int
        """
        tr = self._tr
        tr.value(0)
        time.sleep_us(5)
        tr.value(1)
        time.sleep_us(10)
        tr.value(0)
        try:
            return machine.time_pulse_us(self._ec, 1, self._to)
        except OSError as e:
            if e.args[0] == 100:  # TIMEOUT
                raise OSError("Object too far")
            raise e

    async def _read(self):
        if self._temp is not None:
            temp = await self._temp.getValue(
                SENSOR_TEMPERATURE,
                publish=self._intrd > 5,
                timeout=5 if self._intrd > 20 else 0)
            if temp is None:  # only log errors if reading interval allows it
                await _log.asyncLog(
                    "warn",
                    "Couldn't read temp sensor, using fallback calculation",
                    timeout=10 if self._intrd > 20 else 0)
        else:
            temp = None
        val = []
        warnings = 0  # probably not going to happen that both warning types occur at the same time
        warning = "minimum distance reached or different problem"
        for _ in range(20):
            try:
                pt = self._pulse()
                if pt > 175:  # ~3cm, sensor minimum distance, often read although other problems
                    val.append(pt)
                else:
                    warnings += 1
            except OSError as e:
                warning = e
                warnings += 1
            await asyncio.sleep_ms(10)
        if warnings > 10:  # half sensor readings are bad
            await _log.asyncLog("error",
                                "Too many bad sensor readings, error:",
                                warning,
                                timeout=10 if self._intrd > 20 else 0)
            return
        # removing extreme values
        val.remove(max(val))
        val.remove(max(val))
        val.remove(min(val))
        val.remove(min(val))
        pt = 0
        for v in val:
            pt += v
        pt /= len(val)
        if temp is None:
            dt = (pt / 2) / 29.14
        else:
            dt = (pt / 2) * ((331.5 + (0.6 * temp)) / 10000)
        if dt < 0:
            await _log.asyncLog("warn",
                                "Sensor reading <0",
                                timeout=10 if self._intrd > 20 else 0)
            return
        await self._setValue("distance",
                             dt,
                             timeout=10 if self._intrd > 20 else 0)
示例#16
0
class HCSR04(Component):
    def __init__(self, pin_trigger, pin_echo, timeout=30000, temp_sensor=None,
                 precision=2, offset=0,
                 interval=None, mqtt_topic=None,
                 mqtt_topic_interval=None, value_template=None, friendly_name=None):
        """
        HC-SR04 ultrasonic sensor.
        Be sure to connect it to 5V but use a voltage divider to connect the Echo pin to an ESP.
        :param pin_trigger: pin number/object of trigger pin
        :param pin_echo: pin number/object of echo pin
        :param timeout: reading timeout, corresponds to sensor limit range of 4m
        :param temp_sensor: temperature sensor object
        :param precision: int, precision of distance value published and returned
        :param offset: float, distance value offset, shouldn't be needed
        :param interval: float, interval in which the distance value gets measured and published
        :param mqtt_topic: distance mqtt topic
        :param mqtt_topic_interval: interval mqtt topic for changing the reading interval
        :param value_template: optional template can be used to measure the reverse distance (e.g. water level)
        :param friendly_name: friendly name for homeassistant gui by mqtt discovery, defaults to "Distance"
        """
        super().__init__()
        self._frn = friendly_name
        self._valt = value_template
        self._tr = Pin(pin_trigger, mode=machine.Pin.OUT)
        self._tr.value(0)
        self._ec = Pin(pin_echo, mode=machine.Pin.IN)
        self._to = timeout
        self._temp = temp_sensor
        self._pr = int(precision)
        self._off = float(offset)
        self._topic = mqtt_topic or _mqtt.getDeviceTopic("hcsr04")
        self._topic_int = mqtt_topic_interval or _mqtt.getDeviceTopic("hcsr04/interval", is_request=True)
        self.interval = interval or config.INTERVAL_SEND_SENSOR  # can be changed anytime
        global _count
        self._count = _count
        _count += 1
        self._subscribe(self._topic_int, self._changeInterval)

    async def _init(self):
        await super()._init()
        gen = self.distance
        await asyncio.sleep(1)
        while True:
            await gen()
            t = time.ticks_ms()
            while time.ticks_diff(time.ticks_ms(), t) < self.interval:
                # this way interval can be changed and sensor reacts within 1s
                await asyncio.sleep(1)

    async def _discovery(self):
        # interval change can't be discovered as homeassistant doesn't offer a type
        sens = DISCOVERY_DISTANCE.format("{{ value|float }}" if self._valt is None else self._valt)
        name = "{!s}{!s}".format(_component_name, self._count)
        await self._publishDiscovery(_component_type, self._topic, name, sens, self._frn or "Distance")

    async def _changeInterval(self, topic, msg, retain):
        self.interval = float(msg)
        return True  # will publish the new interval

    def _pulse(self) -> int:
        """
        Send a pulse and wait for the echo pin using machine.time_pulse_us() to measure us.
        :return: int
        """
        tr = self._tr
        tr.value(0)
        time.sleep_us(5)
        tr.value(1)
        time.sleep_us(10)
        tr.value(0)
        try:
            return machine.time_pulse_us(self._ec, 1, self._to)
        except OSError as e:
            if e.args[0] == 100:  # TIMEOUT
                raise OSError("Object too far")
            raise e

    async def _read(self, temp=None, ignore_errors=False, publish=True) -> float:
        """
        Returns distance in cm.
        Optionally compensated by temperature in °C.
        :return: float
        """
        if temp is None:
            if self._temp is not None:
                temp = await self._temp.temperature(publish=False)
                if temp is None:
                    await _log.asyncLog("warn", "Couldn't read temp sensor, using fallback caluclation")
        val = []
        warnings = 0  # probably not going to happen that both warning types occur at the same time
        warning = "minimum distance reached or different problem"
        for _ in range(20):
            try:
                pt = self._pulse()
                if pt > 175:  # ~3cm, sensor minimum distance, often read although other problems
                    val.append(pt)
                else:
                    warnings += 1
            except OSError as e:
                warning = e
                warnings += 1
            await asyncio.sleep_ms(10)
        if warnings > 10:  # half sensor readings are bad
            if ignore_errors is False:
                await _log.asyncLog("error", "Too many bad sensor readings, error: {!s}".format(warning))
            return None
        # removing extreme values
        val.remove(max(val))
        val.remove(max(val))
        val.remove(min(val))
        val.remove(min(val))
        pt = 0
        for i in range(len(val)):
            pt += val[i]
        pt /= len(val)
        if temp is None:
            dt = (pt / 2) / 29.14
        else:
            dt = (pt / 2) * ((331.5 + (0.6 * temp)) / 10000)
        if dt < 0:
            await _log.asyncLog("warn", "Sensor reading <0")
            return None
        try:
            dt = round(dt, self._pr)
            dt += self._off
        except Exception as e:
            await _log.asyncLog("error", "Error rounding value {!s}".format(dt))
            return dt
        if publish:
            await _mqtt.publish(self._topic, ("{0:." + str(self._pr) + "f}").format(dt))
        return dt

    async def distance(self, temp=None, ignore_errors=False, publish=True) -> float:
        """
        Returns distance in cm, optionally temperature compensated
        :param temp: temperature value for compensation, optional
        :param ignore_errors: prevent bad readings from being published to the log in case the application expects those
        :param publish: if value should be published
        :return:
        """
        return await self._read(temp, ignore_errors, publish)