Example #1
0
    def __init__(self,
                 command_on,
                 command_off,
                 expected_return_on=None,
                 expected_execution_time_on=0,
                 expected_return_off=None,
                 expected_execution_time_off=0,
                 iterations=1,
                 iter_delay=10,
                 mqtt_topic=None,
                 friendly_name=None):
        super().__init__()

        # This makes it possible to use multiple instances of Switch
        global _unit_index
        self._count = _count
        _unit_index += 1
        self._topic = mqtt_topic or _mqtt.getDeviceTopic(
            "{!s}/{!s}".format(COMPONENT_NAME, self._count), is_request=True)
        self._subscribe(self._topic, self.on_message)
        self._frn = friendly_name
        gc.collect()
        self.lock = config.Lock(
        )  # in case switch activates a device that will need a while to finish
        self._c_on = Popen(command_on, expected_return_on,
                           expected_execution_time_on, iterations, iter_delay)
        self._c_off = Popen(command_off, expected_return_off,
                            expected_execution_time_off, iterations,
                            iter_delay)
Example #2
0
    def __init__(self,
                 pin,
                 pwm_values,
                 on_time=500,
                 iters=1,
                 freq=1000,
                 mqtt_topic=None,
                 friendly_name=None):
        super().__init__()

        self.pin = PyPin(pin, Pin.OUT)
        self.on_time = on_time
        self.values = pwm_values
        self.iters = iters
        self.lock = config.Lock()
        self.pin = PWM(self.pin, freq=freq)
        self.pin.duty(0)
        # This makes it possible to use multiple instances of Buzzer
        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()
Example #3
0
    def __init__(self,
                 unit_code,
                 unit,
                 expected_execution_time_on=500,
                 expected_execution_time_off=500,
                 iterations=1,
                 iter_delay=10,
                 mqtt_topic=None,
                 friendly_name=None):
        super().__init__()
        self._log = _log

        # This makes it possible to use multiple instances of Switch
        global _unit_index
        self._count = _count
        _unit_index += 1
        self._topic = mqtt_topic or _mqtt.getDeviceTopic(
            "{!s}{!s}".format(COMPONENT_NAME, self._count), is_request=True)
        self._subscribe(self._topic, self.on_message)
        self._frn = friendly_name
        gc.collect()
        self.unit_lock = config.Lock()
        self._c_on = Popen(COMMAND_ON.format(unit_code, unit),
                           EXPECTED_RETURN_ON.format(unit_code, unit),
                           expected_execution_time_on, iterations, iter_delay)
        self._c_off = Popen(COMMAND_OFF.format(unit_code, unit),
                            EXPECTED_RETURN_OFF.format(unit_code, unit),
                            expected_execution_time_off, iterations,
                            iter_delay)
Example #4
0
    def __init__(self, mqtt_topic=None, friendly_name=None):
        super().__init__()

        # This makes it possible to use multiple instances of Switch
        global _count
        self._count = _count
        _count += 1
        self._topic = mqtt_topic or _mqtt.getDeviceTopic(
            "{!s}/{!s}".format(_component_name, self._count), is_request=True)
        self._subscribe(self._topic, self.on_message)
        self._frn = friendly_name
        gc.collect()
        self.lock = config.Lock(
        )  # in case switch activates a device that will need a while to finish
Example #5
0
 def __init__(self, pin, interval=None, auto_discovery=False):
     """
     The DS18 onewire controller. Reads all connected (and configured) units.
     :param pin: Pin object or integer or name
     :param interval: how often the sensors are read and published
     :param auto_discovery: if True then one object for each found DS18 unit will be created. This is only useful if
     the Units are not going to be used in other components and only the read temperature is interesting.
     """
     self._interval = interval or config.INTERVAL_SEND_SENSOR
     ds18x20.DS18X20.__init__(self, onewire.OneWire(Pin(pin)))
     gc.collect()
     self._lock = config.Lock()
     global _ds18_controller
     _ds18_controller = self
     asyncio.get_event_loop().create_task(self._loop(auto_discovery))
Example #6
0
 def __init__(self, command_topic, state_topic, timeout=_TIMEOUT):
     global _unit_index
     _unit_index += 1
     super().__init__(COMPONENT_NAME,
                      __version__,
                      _unit_index,
                      discover=False)
     self._state = False
     self._topic = command_topic
     self._state_topic = state_topic
     self.lock = config.Lock()
     # in case switch activates a device that will need a while to finish
     self._state_time = 0
     self._timeout = timeout
     _mqtt.subscribeSync(self._state_topic, self.on_message, self)
Example #7
0
class RF433(Switch):
    lock = config.Lock()  # only one method can have control over the RF433 device

    def __init__(self, unit_code, unit, expected_execution_time_on=500, expected_execution_time_off=500,
                 iterations=1, iter_delay=10, mqtt_topic=None, friendly_name=None):
        super().__init__()
        self._log = _log

        # This makes it possible to use multiple instances of Switch
        global _unit_index
        self._count = _count
        _unit_index += 1
        self._topic = mqtt_topic or _mqtt.getDeviceTopic("{!s}{!s}".format(COMPONENT_NAME, self._count),
                                                         is_request=True)
        self._subscribe(self._topic, self.on_message)
        self._frn = friendly_name
        gc.collect()
        self.unit_lock = config.Lock()
        self._c_on = Popen(COMMAND_ON.format(unit_code, unit), EXPECTED_RETURN_ON.format(unit_code, unit),
                           expected_execution_time_on, iterations, iter_delay)
        self._c_off = Popen(COMMAND_OFF.format(unit_code, unit), EXPECTED_RETURN_OFF.format(unit_code, unit),
                            expected_execution_time_off, iterations, iter_delay)

    async def on_message(self, topic, msg, retain):
        if self.unit_lock.locked():
            return False
        async with self.lock:
            async with self.unit_lock:
                if msg in _mqtt.payload_on:
                    r = await self._c_on.execute()
                    if r is True:
                        await _mqtt.publish(self._topic[:-4], "ON", qos=1, retain=True)  # makes it easier to subclass
                        return True
                    else:
                        await self._log.asyncLog("warn", "Got unexpected return: {!s}".format(r))
                        return False
                elif msg in _mqtt.payload_off:
                    r = await self._c_off.execute()
                    if r is True:
                        await _mqtt.publish(self._topic[:-4], "OFF", qos=1, retain=True)
                        return True
                    else:
                        await self._log.asyncLog("warn", "Got unexpected return: {!s}".format(r))
                        return False

    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)
Example #8
0
    def __init__(self,
                 component_name,
                 version,
                 unit_index: int,
                 command_topic=None,
                 instance_name=None,
                 wait_for_lock=True,
                 discover=True,
                 restore_state=True,
                 friendly_name=None,
                 initial_state=None):
        """
        :param component_name: name of the component that is subclassing this switch (used for discovery and topics)
        :param version: version of the component module. will be logged over mqtt
        :param unit_index: counter of the registerd unit of this sensor_type (used for default topics)
        :param command_topic: command_topic of subclass which controls the switch state. optional.
        :param instance_name: name of the instance. If not provided will get composed of component_name<count>
        :param wait_for_lock: if True then every request waits for the lock to become available,
        :param restore_state: restore the retained state topic state
        meaning the previous device request has to finish before the new one is started.
        Otherwise the new one will get ignored.
        :param friendly_name: friendly name for homeassistant gui
        :param initial_state: intitial state of the switch. By default unknown so first state change request will set initial state.
        """
        super().__init__(component_name,
                         version,
                         unit_index,
                         discover=discover)
        # discover: boolean, if this component should publish its mqtt discovery.
        # This can be used to prevent combined Components from exposing underlying
        # hardware components like a power switch

        self._state = initial_state  # initial state is unknown if None
        self._topic = command_topic or _mqtt.getDeviceTopic(
            "{!s}{!s}/set".format(component_name, self._count))
        _mqtt.subscribeSync(self._topic,
                            self.on_message,
                            self,
                            check_retained_state=restore_state)
        self.lock = config.Lock()
        # in case switch activates a device that will need a while to finish
        self._wfl = wait_for_lock
        self._name = instance_name
        self._event = None
        self._frn = friendly_name
        self._pub_task = None
        gc.collect()
Example #9
0
    def __init__(self,
                 uart_number,
                 uart_tx,
                 uart_rx,
                 set_pin=None,
                 reset_pin=None,
                 interval_passive_mode=None,
                 active_mode=True,
                 eco_mode=True,
                 interval=None,
                 mqtt_topic=None,
                 friendly_name: list = None):
        Component.__init__(self)
        self._interval = interval or config.INTERVAL_SEND_SENSOR
        self._int_pm = interval_passive_mode or self._interval
        self._topic = mqtt_topic or _mqtt.getDeviceTopic(_component_name)
        if type(friendly_name) is not None:
            if type(friendly_name) == list:
                if len(friendly_name) != 12:
                    _log.warn(
                        "Length of friendly name is wrong, expected 12 got {!s}"
                        .format(len(friendly_name)))
                    self._frn = None
                else:
                    self._frn = friendly_name
            else:
                _log.warn(
                    "Friendly name got unsupported type {!s}, expect list".
                    format(type(friendly_name)))
                self._frn = None
        else:
            self._frn = None
        uart = machine.UART(uart_number, tx=uart_tx, rx=uart_rx, baudrate=9600)

        ##############################
        # create sensor object
        sensorModule.PMS5003.__init__(self,
                                      uart,
                                      config.Lock(),
                                      set_pin,
                                      reset_pin,
                                      interval_passive_mode,
                                      active_mode=active_mode,
                                      eco_mode=eco_mode)
        gc.collect()
Example #10
0
 def __init__(self,
              pin,
              on_time=50,
              off_time=50,
              iters=20,
              mqtt_topic=None):
     if type(pin) == str:
         pin = config.pins[pin]
     mqtt_topic = mqtt_topic or mqtt.getDeviceTopic("LEDNotification",
                                                    is_request=True)
     self.pin = pin
     self.on_time = on_time
     self.off_time = off_time
     self.iters = iters
     self.lock = config.Lock()
     Pin(self.pin, Pin.OUT, value=0)
     mqtt.scheduleSubscribe(mqtt_topic,
                            self.notification,
                            check_retained=False)
     # not checking retained as led only activates single-shot
     self.mqtt_topic = mqtt_topic
Example #11
0
    def __init__(self, uart_number, uart_tx, uart_rx, set_pin=None, reset_pin=None,
                 interval_passive_mode=None, active_mode=True, eco_mode=True,
                 interval=None, mqtt_topic=None):
        interval = interval or config.INTERVAL_SEND_SENSOR
        interval_passive_mode = interval_passive_mode or interval
        self.component_name = component_name
        self.topic = mqtt_topic or mqtt.getDeviceTopic(self.component_name)
        self.log = logging.getLogger(self.component_name)
        uart = machine.UART(uart_number, tx=uart_tx, rx=uart_rx, baudrate=9600)

        ##############################
        # create sensor object
        super().__init__(uart, config.Lock(), set_pin, reset_pin, interval_passive_mode,
                         active_mode=active_mode, eco_mode=eco_mode)
        gc.collect()
        if (interval == interval_passive_mode and active_mode is False) or interval == 0:
            self.registerCallback(self.airQuality)
        else:
            # possible to have different timings in passive_read and publish interval
            # useful if other components use readings of sensor too
            asyncio.get_event_loop().create_task(self._loop(self.airQuality(), interval))
Example #12
0
 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()
Example #13
0
 def __init__(self,
              pin,
              pwm_values,
              on_time=500,
              iters=1,
              freq=1000,
              mqtt_topic=None):
     if type(pin) == str:
         pin = config.pins[pin]
     mqtt_topic = mqtt_topic or mqtt.getDeviceTopic("Buzzer",
                                                    is_request=True)
     self.pin = pin
     self.on_time = on_time
     self.values = pwm_values
     self.iters = iters
     self.lock = config.Lock()
     self.pin = PWM(Pin(self.pin, Pin.OUT), freq=freq)
     self.pin.duty(0)
     mqtt.scheduleSubscribe(mqtt_topic,
                            self.notification,
                            check_retained=False)
     # not checking retained as buzzer only activates single-shot
     self.mqtt_topic = mqtt_topic
Example #14
0
    def __init__(self,
                 uart_number,
                 uart_tx,
                 uart_rx,
                 set_pin=None,
                 reset_pin=None,
                 interval_reading=0.1,
                 active_mode=True,
                 eco_mode=True,
                 interval_publish=None,
                 mqtt_topic=None,
                 friendly_name: list = None,
                 discover=True,
                 expose_intervals=False,
                 intervals_topic=None):
        """
        :param uart_number: esp32 has multiple uarts
        :param uart_tx: tx pin number
        :param uart_rx: rx pin number
        :param set_pin: optional pin number for set pin
        :param reset_pin: optional pin number for reset pin
        :param interval_reading: In passive mode controls the reading interval, defaults to 0.1 in active_mode.
        :param active_mode:
        :param eco_mode:
        :param interval_publish: publish interval, independent of interval_reading and active_mode
        :param mqtt_topic:
        :param friendly_name: optional, list of friendly_names for all types. Has to provide a name for every type.
        :param discover:
        :param expose_intervals: intervals can be changed through mqtt
        :param intervals_topic:
        """
        super().__init__(COMPONENT_NAME, __version__, 0, discover,
                         interval_publish, interval_reading, mqtt_topic, _log,
                         expose_intervals, intervals_topic)
        if type(friendly_name) is not None:
            if type(friendly_name) == list:
                if len(friendly_name) != 12:
                    _log.warn(
                        "Length of friendly name is wrong, expected 12 got {!s}"
                        .format(len(friendly_name)))
                    friendly_name = None
            else:
                _log.warn(
                    "Friendly name got unsupported type {!s}, expect list".
                    format(type(friendly_name)))
                friendly_name = None
        for tp in TYPES:
            ind = TYPES.index(tp)
            self._addSensorType(
                tp, 0, 0, VALUE_TEMPLATE_JSON.format(tp), UNITS[ind],
                friendly_name[ind] if friendly_name is not None else tp, None,
                DISCOVERY_PM.format(UNITS[ind], tp))
        uart = machine.UART(uart_number, tx=uart_tx, rx=uart_rx, baudrate=9600)
        self._count = 0

        ##############################
        # create sensor object
        self.pms = sensorModule.PMS5003(self,
                                        uart,
                                        config.Lock(),
                                        set_pin,
                                        reset_pin,
                                        interval_reading,
                                        active_mode=active_mode,
                                        eco_mode=eco_mode)
        self._active_mode = active_mode
        gc.collect()
        if self._active_mode is False:
            # in passive mode using callback because reading intervals could drift apart
            # between sensor and ComponentSensor
            self.pms.registerCallback(self._saveVals)
Example #15
0
class DS18(ComponentSensor):
    """
    Helping class to use a singluar DS18 unit.
    This is not a full component object in terms of mqtt and discovery. This is handled by the controller.
    It can be used as a temperature component object.
    """
    _pins = {}  # pin number/name:onewire()
    _last_conv = {}  # onewire:time
    _lock = config.Lock()

    def __init__(self,
                 pin,
                 rom: str = None,
                 auto_detect=False,
                 interval_publish: float = None,
                 interval_reading: float = None,
                 precision_temp: int = 2,
                 offset_temp: float = 0,
                 mqtt_topic=None,
                 friendly_name=None,
                 discover=True,
                 expose_intervals=False,
                 intervals_topic=None):
        """
        Class for a single ds18 unit to provide an interface to a single unit.
        :param pin: pin number/name/object
        :param rom: optional, ROM of the specific DS18 unit, can be string or bytearray
        (in json bytearray not possible). If not given then the first found ds18 unit will be used,
        no matter the ROM. Makes it possible to have a generic ds18 unit.
        :param auto_detect: optional, if true and ROM is None then all connected ds18 units will automatically generate a sensor object with the given options.
        :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 precision_temp: the precision to for returning/publishing values
        :param offset_temp: temperature offset to adjust bad sensor readings
        :param mqtt_topic: optional mqtt topic of sensor
        :param friendly_name: friendly name in homeassistant
        :param discover: if DS18 object should send discovery message for homeassistnat
        :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.
        """
        if rom is None and auto_detect:
            # only a dummy sensor for detecting connected sensors
            self._interval_reading = interval_reading
            self._interval_publishing = interval_publish
            interval_reading = 60  # scan every 60 seconds for new units
            interval_publish = -1
            self._instances = {}  # rom:object
            self._auto_detect = True
            self._prec = precision_temp
            self._offs = offset_temp
            self._discover = discover
            self._expose = expose_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)
        if rom or not auto_detect:  # sensor with rom or generic sensor
            self._addSensorType(SENSOR_TEMPERATURE, precision_temp,
                                offset_temp, VALUE_TEMPLATE_FLOAT, "°C",
                                friendly_name)
            self._auto_detect = False
        self._generic = True if rom is None and not auto_detect else False
        if type(pin) == ds18x20.DS18X20:
            self.sensor: ds18x20.DS18X20 = pin
        else:
            self._pins[pin] = ds18x20.DS18X20(onewire.OneWire(Pin(pin)))
            self.sensor: ds18x20.DS18X20 = self._pins[pin]
            self._last_conv[self.sensor] = None
        self.rom: str = rom
        gc.collect()

    def _default_name(self):
        """Change default name to include sensor ROM. Will change name and default topic."""
        if self.rom is None or self._generic:
            return "{!s}".format(COMPONENT_NAME)
        else:
            return "{!s}_{!s}".format(COMPONENT_NAME, self.rom)

    async def _read(self):
        if self._auto_detect or self._generic:  # auto_detect unit or generic sensor
            roms = []
            for _ in range(4):
                roms_n = self.sensor.scan()
                for rom in roms_n:
                    if rom not in roms:
                        roms.append(rom)
                await asyncio.sleep_ms(100)
            if len(roms) == 0:
                await _log.asyncLog("error", "Found no ds18 unit", timeout=10)
                return
            if self._auto_detect:  # auto_detect instance
                for rom in roms:
                    rom = self.rom2str(rom)
                    if rom not in self._instances:
                        self._instances[rom] = DS18(self.sensor, rom, False,
                                                    self._interval_publishing,
                                                    self._interval_reading,
                                                    self._prec, self._offs,
                                                    None, None, self._discover,
                                                    self._expose)
                for rom in self._instances:
                    if rom not in roms:  # sensor not connected anymore
                        await self.removeComponent(roms[rom])
                        # will stop its loop and remove component and unsubcribe every topic
                        del self._instances[rom]
                        await _log.asyncLog("info",
                                            "DS18 removed:",
                                            rom,
                                            timeout=5)
            else:  # generic ds18 sensor
                rom = self.rom2str(roms[0])
                if rom != self.rom:  # sensor replaced
                    self.rom: str = rom
                    await _log.asyncLog("info",
                                        "Found new ds18:",
                                        rom,
                                        timeout=5)
        if self.rom is not None:  # DS18 sensor unit
            async with self._lock:
                if self._last_conv[self.sensor] is None or \
                        time.ticks_diff(time.ticks_ms(), self._last_conv[self.sensor]) > 5000:
                    # if sensors did convert time more than 5 seconds ago, convert temp again
                    self.sensor.convert_temp()
                    await asyncio.sleep_ms(750)
                value = None
                err = None
                for _ in range(3):
                    try:
                        value = self.sensor.read_temp(self.str2rom(self.rom))
                    except Exception as e:
                        await asyncio.sleep_ms(100)
                        err = e
                        continue
                if value is None:
                    await _log.asyncLog("error",
                                        "Sensor rom",
                                        self.rom,
                                        "got no value,",
                                        err,
                                        timeout=10)
                    return
                if value == 85.0:
                    await _log.asyncLog(
                        "error",
                        "Sensor rom",
                        self.rom,
                        "got value 85.00 [not working correctly]",
                        timeout=10)
                    return
                await self._setValue(SENSOR_TEMPERATURE, value)

    @staticmethod
    def rom2str(rom: bytearray) -> str:
        return ''.join('%02X' % i for i in iter(rom))

    @staticmethod
    def str2rom(rom: str) -> bytearray:
        a = bytearray(8)
        for i in range(8):
            a[i] = int(rom[i * 2:i * 2 + 2], 16)
        return a
Example #16
0
    def __init__(self,
                 temperature_sensor: ComponentSensor,
                 heating_unit: ComponentSwitch,
                 modes: list,
                 interval: float = 300,
                 temp_step=0.1,
                 min_temp: float = 16,
                 max_temp: float = 26,
                 temp_low: float = 20,
                 temp_high: float = 21,
                 away_temp_low: float = 16,
                 away_temp_high: float = 17,
                 friendly_name=None,
                 discover=True):
        self.checkSensorType(temperature_sensor, SENSOR_TEMPERATURE)
        self.checkSwitchType(heating_unit)
        # This makes it possible to use multiple instances of MyComponent
        global _unit_index
        _unit_index += 1
        super().__init__(COMPONENT_NAME, __version__, _unit_index, discover)

        self._temp_step = temp_step
        self._min_temp = min_temp
        self._max_temp = max_temp
        self.temp_sensor: ComponentSensor = temperature_sensor
        self.heating_unit: ComponentSwitch = heating_unit
        self._modes = {}
        if "off" not in modes:
            modes.append("off")
        for mode in modes:
            if mode not in MODES_SUPPORTED:
                _log.error("Mode {!s} not supported".format(mode))
                modes.remove(mode)
            else:
                try:
                    mod = __import__(
                        "pysmartnode.components.devices.climate.{}".format(
                            mode), globals(), locals(), [], 0)
                except ImportError as e:
                    _log.error("Mode {!s} not available: {!s}".format(mode, e))
                    continue
                if hasattr(mod, mode):
                    modeobj = getattr(mod, mode)
                else:
                    _log.error("Mode {!s} has no class {!r}".format(
                        mode, mode))
                    continue
                try:
                    modeobj = modeobj(self)
                except Exception as e:
                    _log.error("Error creating mode {!s} object: {!s}".format(
                        mode, e))
                    continue
                self._modes[mode] = modeobj
        self._frn = friendly_name
        self.state = {
            CURRENT_TEMPERATURE_HIGH: temp_high,  # current temperature high
            CURRENT_TEMPERATURE_LOW: temp_low,  # current temperature low
            AWAY_MODE_STATE: AWAY_OFF,  # away mode "ON"/"OFF"
            STORAGE_AWAY_TEMPERATURE_HIGH:
            away_temp_high,  # away temperature low
            STORAGE_AWAY_TEMPERATURE_LOW:
            away_temp_low,  # away temperature high
            STORAGE_TEMPERATURE_HIGH:
            temp_high,  # temperature high, storage value
            STORAGE_TEMPERATURE_LOW:
            temp_low,  # temperature low, storage value
            CURRENT_MODE: str(self._modes["off"]),
            CURRENT_ACTION: ACTION_OFF
        }
        self.event = Event()
        self.lock = config.Lock()
        # every extneral change (like mode) that could break an ongoing trigger needs
        # to be protected by self.lock.
        self.log = _log
        gc.collect()

        self._mode_topic = _mqtt.getDeviceTopic("{!s}{!s}/statem/set".format(
            COMPONENT_NAME, self._count))
        self._temp_low_topic = _mqtt.getDeviceTopic(
            "{!s}{!s}/statetl/set".format(COMPONENT_NAME, self._count))
        self._temp_high_topic = _mqtt.getDeviceTopic(
            "{!s}{!s}/stateth/set".format(COMPONENT_NAME, self._count))
        self._away_topic = _mqtt.getDeviceTopic("{!s}{!s}/stateaw/set".format(
            COMPONENT_NAME, self._count))
        _mqtt.subscribeSync(self._mode_topic, self.changeMode, self)
        _mqtt.subscribeSync(self._temp_low_topic, self.changeTempLow, self)
        _mqtt.subscribeSync(self._temp_high_topic, self.changeTempHigh, self)
        _mqtt.subscribeSync(self._away_topic, self.changeAwayMode, self)

        self._restore_done = False
        asyncio.get_event_loop().create_task(self._loop(interval))
Example #17
0
class Component:
    """
    Use this class as a base for components. Subclass to extend. See the template for examples.
    """
    _discovery_lock = config.Lock()

    # prevent multiple discoveries from running concurrently and creating Out-Of-Memory errors

    def __init__(self):
        self._topics = {}
        # No RAM allocation for topic strings as they are passed by reference if saved in a variable in subclass.
        # self._topics is used by mqtt to know which component a message is for.
        self._next_component = None  # needed to keep a list of registered components
        config.addComponent(self)
        asyncio.get_event_loop().create_task(self._init())

    async def _init(self):
        for t in self._topics:
            await _mqtt.subscribe(t, qos=1)
        if config.MQTT_DISCOVERY_ENABLED is True:
            async with self._discovery_lock:
                await self._discovery()

    def _subscribe(self, topic, cb):
        self._topics[topic] = cb

    async def on_reconnect(self):
        """
        Subclass to process a reconnect.
        Useful if you need to send a message on reconnect.
        Resubscribing to topics is done by mqtt_handler and doesn't need to be done manually.
        """
        pass

    async def _discovery(self):
        """Implement in subclass. Is only called by self._init unless config.MQTT_DISCOVERY_ON_RECONNECT is True."""
        pass

    @staticmethod
    async def _publishDiscovery(component_type,
                                component_topic,
                                unique_name,
                                discovery_type,
                                friendly_name=None):
        topic = Component._getDiscoveryTopic(component_type, unique_name)
        msg = Component._composeDiscoveryMsg(component_topic, unique_name,
                                             discovery_type, friendly_name)
        await _mqtt.publish(topic, msg, qos=1, retain=True)
        del msg, topic
        gc.collect()

    @staticmethod
    def _composeDiscoveryMsg(component_topic,
                             name,
                             component_type_discovery,
                             friendly_name=None,
                             no_avail=False):
        """
        Helper function to separate dynamic system values from user defineable values.
        :param component_topic: state topic of the component. device topics (see mqtt) are supported
        :param name: name of the component, must be unique on the device, typically composed of component name and count
        :param component_type_discovery: discovery values for the component type, e.g. switch, sensor
        :param friendly_name: optional a readable name that is used in the gui and entity_id
        :param no_avail: don't add availability configs (typically only used for the availability component itself)
        :return: str
        """
        friendly_name = friendly_name or name
        component_topic = component_topic if _mqtt.isDeviceTopic(
            component_topic) is False else _mqtt.getRealTopic(component_topic)
        if no_avail is True:
            return DISCOVERY_BASE_NO_AVAIL.format(
                component_topic,  # "~" component state topic
                friendly_name,  # name
                sys_vars.getDeviceID(),
                name,  # unique_id
                component_type_discovery,  # component type specific values
                sys_vars.getDeviceDiscovery())  # device
        return DISCOVERY_BASE.format(
            component_topic,  # "~" component state topic
            friendly_name,  # name
            config.MQTT_HOME,
            sys_vars.getDeviceID(),  # availability_topic
            sys_vars.getDeviceID(),
            name,  # unique_id
            component_type_discovery,  # component type specific values
            sys_vars.getDeviceDiscovery())  # device

    @staticmethod
    def _getDiscoveryTopic(component_type, name):
        return "{!s}/{!s}/{!s}/{!s}/config".format(
            config.MQTT_DISCOVERY_PREFIX, component_type,
            sys_vars.getDeviceID(), name)