Пример #1
0
    def isCurrentKNXAttribute(knxDest, knxFormat, newVal) -> bool:
        """
        checks whether new value matches to stored value last sent on the bus
        :param knxFormat:   DPT format that is expected for the given destination
        :param knxDest:     KNX destionation under which the value is stored
        :param newVal:      value in dpt representation
        :returns            true if new value matches cached value
        """
        ret = False
        try:
            curKNXVal = EIBClientFactory().getClient().GroupCache_Read(knxDest)

            if len(newVal) == 1 and len(curKNXVal) == 2:
                curKNXVal = curKNXVal[1:]

            curKNXVal = curKNXVal.upper().strip()
            newVal = newVal.upper().strip()

            # normalize numeric and boolean values
            if is_number(curKNXVal):
                curKNXVal = convert_number(curKNXVal)
            elif is_bool(curKNXVal):
                curKNXVal = bool(curKNXVal)
            if is_number(newVal):
                newVal = convert_number(newVal)
            elif is_bool(newVal):
                newVal = bool(newVal)

            # compare normalized values
            ret = curKNXVal == newVal
        except ValueError as ex:
            log('warning', 'Value comparison failed - {0}'.format(ex))
        return ret
Пример #2
0
    def updateOccurred(self, srcAddr, val):
        """
        takes value from KNX and sends it to ZigBee device
        """
        knxSrc = printGroup(self.gaddrInt)

        val = printValue(val, len(val))

        # avoid error state in case KNX device is reset via the KNX app
        if not val:
            return

        val = int(val, 16)

        # transform data from python to zigbee protocol adequate form
        zbValue = zigbee_utils.getZigBeeValue(self.zbFormat, val)

        # sends update to zigbee device
        if self.zbClient.setAttribute(attr=self.zbAttr, val=zbValue):
            log(
                'info',
                'Value updated based on KNX value change {0}({1}): {2}(KNX value: {3}) for ZigBee client {4}'
                .format(self.attrName, knxSrc, zbValue, val,
                        self.zbClient.uniqueID))
        else:
            log(
                'error',
                'Value could not be updated based on KNX value change {0}({1}): {2}(KNX value: {3}) for ZigBee client {4}'
                .format(self.attrName, knxSrc, zbValue, val,
                        self.zbClient.uniqueID))
Пример #3
0
    def getClientState(self, id, type, attr, section=None):
        """ get attribute for a defined client """
        ret = None

        # default is state sectionwith only a few exceptions
        if section is None:
            section = 'state'

        if not ZigBeeGateway.__state:
            # check if state information was requested at least once
            self.getState()

        try:
            # section can be omitted by specifying >>zigbeeSection: ''<< in configuration
            if len(section) > 0:
                ret = ZigBeeGateway.__state[type][str(id)][section][attr]
            else:
                ret = ZigBeeGateway.__state[type][str(id)][attr]
        except KeyError as ex:
            log(
                'error',
                'Configuration error - attribute "{0}" in section "{1}" for deConzID "{2}" type "{3}" not defined: {4}'
                .format(attr, section, id, type, ex))

        return ret
Пример #4
0
    def getAttribute(self, attrName, mbFormat, mbAddr):
        val = None

        if self.__mbcImpl.connect():
            try:
                # check modbus data type
                # TODO implement more ModBus datatypes
                if mbFormat == 'float':
                    # configuration supports summing up multiple values automatically
                    # single value from corresponding modbus client
                    if isinstance(mbAddr, int):
                        val = modbus_utils.ReadFloat(self.__mbcImpl, mbAddr)
                    # sum of multiple values from corresponding modbus client
                    elif isinstance(mbAddr, list):
                        val = 0
                        # TODO implement functions
                        for ids in mbAddr:
                            val = val + modbus_utils.ReadFloat(
                                self.__mbcImpl, ids)
            except Exception as ex:
                log(
                    'error', 'Error reading ModBus value - {0}: {1}'.format(
                        attrName, ex))

            self.__mbcImpl.close()
        else:
            # log connection error
            log(
                'error', 'Could not connect to ModBus server {0}:{1}'.format(
                    self.__mbcImpl.host, self.__mbcImpl.port))

        return val
Пример #5
0
    def updateOccurred(self, srcAddr, val):
        """
        takes value from KNX and sends it to another knx device
        """
        knxSrc = printGroup(self.gaddrInt)

        # get DPT implementation
        # dc = DPTXlatorFactoryFacade().create(self.knxFormat)

        # if dc.checkFrame(val):
        #     val = dc.frameToData(val)

        val = int(printValue(val, len(val)), 16)

        # currently no conversion from one DPT type to another is foreseen

        # sends update to the other knx device
        if val is not None and \
                self.knxClient.setAttribute(attrName=self.attrName, val=val,
                                            dest=self.knxDest, format=self.knxFormat,
                                            function=self.function):
            log('info',
                'Value updated based on KNX value change {0}({1}): {2} for KNX client {3}'.format(self.attrName, knxSrc,
                                                                                                  val, self.knxDest))
        else:
            log('error',
                'Value could not be updated based on KNX value change {0}({1}): {2} for KNX client {3}'.format(
                    self.attrName, knxSrc,
                    val, self.knxDest))
Пример #6
0
    def __init__(self, host, port, user, passwd,
                 name, topic,
                 knxDest, knxFormat, function, flags):
        super(_MQTTClient, self).__init__()

        self.attrName = name
        self.knxDest = knxDest
        self.knxFormat = knxFormat
        self.function = function
        self.flags = flags

        # set up MQTT client connection to broker
        self.client = mqtt.Client("KNXBridgeDaemon-"+name)
        self.client.on_message = self.updateReceived
        # authenticate
        if user and passwd:
            self.client.username_pw_set(username=user,
                                        password=passwd)
        elif user:
            self.client.username_pw_set(username=user)
        # establish connection
        if host and port:
            self.client.connect(host=host, port=port)
        else:
            self.client.connect(host=host)
        # listener target/endpoint
        try:
            self.client.subscribe(topic)
        except ValueError as ex:
            log('error',
                'Could not connect to MQTT server {0} for endpoint {1} [{2}]'.format(host,
                                                                                     topic,
                                                                                     ex))
Пример #7
0
    def writeKNXAttribute(self,
                          attrName,
                          knxDest,
                          knxFormat,
                          val,
                          function=None,
                          flags=None) -> bool:
        """ adds additional reachable check for client """
        ret = None
        if self.reachable:
            ret = super().writeKNXAttribute(attrName, knxDest, knxFormat, val,
                                            function, flags)
        else:
            log(
                'error', 'Could not connect to ZigBee client {0}[{1}]'.format(
                    attrName, self.uniqueID))

        return ret
Пример #8
0
    def valueToData(self, value):
        """
        customize original implementation by returning hex-ified and chunked value representation for direct usage in cmd
        :returns:   4 typles á 00-FF hex representation separated by spaces
        :raises:    NotImplementedError in case value is not of type int or float
        """
        ret = self.__dptimpl.valueToData(value)
        if not isinstance(ret, (int, float)):
            log(
                'error',
                "KNXUtil.valueToData() - Data type {0} not yet implemented".
                format(type(value)))
            raise NotImplementedError

        ret = hex(int(ret))

        # fill up with leading 0's if hex not filling corresponding byte representation
        if len(ret[2:]) < (2 * self.typeSize):
            for i in range(0, (2 * self.typeSize) - len(ret[2:])):
                ret = ret[:2] + '0' + ret[2:]

        # print byte-wise representation separated by blanks
        return ' '.join(ret[i:i + 2] for i in range(2, len(ret), 2))
Пример #9
0
    def readKNXAttribute(self, attrName, knxSrc, knxFormat, function=None):
        """
        reads values via EIB/KNX client
        :returns    pythonic value from bus
        """
        val = None

        dpt = EIBClientFactory().getClient().GroupCache_Read(knxSrc)

        # convert value from string representation into hex
        dpt = int(dpt, 16)

        # get DPT implementation for python type conversion
        dc = DPTXlatorFactoryFacade().create(knxFormat)

        try:
            if dc.checkData(dpt):
                val = dc.dataToValue(dpt)

                log(
                    'info',
                    f'Value retrieved (group cache) "{attrName}"[{knxSrc}] value={val}({dpt})'
                )
        except DPTXlatorValueError as ex:
            # log failure
            log(
                'error',
                f'Value could not be read "{attrName}"[{knxSrc}] value={dpt} - Check type definition for DPT type "{knxFormat}" and value "{dpt}" - {ex}'
            )
        except (TypeError, ValueError) as ex:
            # log failure
            log(
                'error',
                f'Value could not be read "{attrName}"[{knxSrc}] value={dpt} - {ex}'
            )

        return val
Пример #10
0
    def writeKNXAttribute(self,
                          attrName: str,
                          knxDest: str,
                          knxFormat: str,
                          val,
                          function=None,
                          flags=None) -> bool:
        """
        writes values via the KNXD command line tool
        :returns true if successful
        """
        dpt = None

        # get DPT implementation
        dc = DPTXlatorFactoryFacade().create(knxFormat)

        # perform transformations if defined before sending to bus
        if function:
            val = self.performFunction(dc.dpt, function, val, attrName,
                                       knxDest, knxFormat)

        # check value, some functions like an exclusive equal comparison may return None
        # for valid reason with no further write action to be performed
        if val is None:
            return False

        # convert to DPT representation
        try:
            if dc.checkValue(val):
                dpt = dc.valueToData(val)
        except DPTXlatorValueError:
            # log failure
            log(
                'error',
                f'Value could not be updated "{attrName}"[{knxDest}] value={val} - Check type definition for DPT type "{knxFormat}" and value "{val}"'
            )
        except (TypeError, ValueError) as ex:
            # log failure
            log(
                'error',
                f'Value could not be updated "{attrName}"[{knxDest}] value={val} - {ex}'
            )

        if dpt is None:
            return False

        # do not load the bus with unnecessary request, check against cached value
        if (flags and Flags.FLAGS_FORCE in flags) or \
                not self.isCurrentKNXAttribute(knxDest, knxFormat, dpt):
            # send value to the knx bus
            os.popen('knxtool groupwrite ip:{0} {1} {2}'.format(
                KNXGateway().hostIP, knxDest, dpt))

            # log success
            if flags and Flags.FLAGS_FORCE in flags:
                log(
                    'change',
                    f'Updated value (enforced) on KNX bus "{attrName}"[{knxDest}] value={val}[DPT:{dpt}]'
                )
            else:
                log(
                    'change',
                    f'Updated value on KNX bus "{attrName}"[{knxDest}] value={val}[DPT:{dpt}]'
                )
        else:
            # log success
            log('info',
                f'Value is up to date "{attrName}"[{knxDest}] value={val}')

        return True
Пример #11
0
def __executeFunctionImpl(deviceInstance, dpt, function, val, attrName,
                          knxDest, knxFormat):
    errDetail = None
    if function[:3] == 'val':
        # replace current value by static value
        try:
            val = function[4:-1]
            if is_number(val):
                val = float(val)
            elif is_bool(val):
                val = convert_bool(val)
        except ValueError:
            val = function[4:-1]
    elif function[:3] == 'inv':
        # invert current value - restricted to boolean currently
        if is_bool(val):
            # generic 0/1 representation required for dpxlator DPT conversion
            val = not val
        else:
            errDetail = 'wrong value type'
    elif function[:3] == 'max':
        # returns the greater value, useful for greater 0 assurancce
        if is_number(val):
            try:
                val = convert_number(val)
                val = max(val, function[4:-1])
            except ValueError:
                errDetail = 'wrong function definition'
        else:
            errDetail = 'wrong value type'
    elif function[:3] == 'min':
        # returns the lower value
        if is_number(val):
            try:
                val = convert_number(val)
                val = min(val, function[4:-1])
            except ValueError:
                errDetail = 'wrong function definition'
        else:
            errDetail = 'wrong value type'
    elif function[:3] == 'rnd':
        # rounds the current value to the given precision
        if is_number(val):
            try:
                val = convert_number(val)
                val = round(val, function[4:-1])
            except ValueError:
                errDetail = 'wrong function definition'
        else:
            errDetail = 'wrong value type'
    elif function[:3] == 'div':
        # divide current value by given value
        if is_number(val):
            try:
                val = convert_number(val)
                div = float(function[4:-1])
                if div is not 0:
                    val = val / div
                else:
                    if float(function[4:-1]) == 0:
                        errDetail = 'divider is zero'
                    else:
                        errDetail = 'wrong function definition'
            except ValueError:
                errDetail = 'wrong function definition'
        else:
            errDetail = 'wrong value type'
    elif function[:3] == 'mul':
        # multiply current value with given value
        if is_number(val):
            try:
                val = convert_number(val)
                div = float(function[4:-1])
                val = val * div
            except ValueError:
                errDetail = 'wrong function definition'
        else:
            errDetail = 'wrong value type'
    elif function[:3] == 'add':
        # adds given value to current value
        if is_number(val):
            try:
                val = convert_number(val)
                div = float(function[4:-1])
                val = val + div
            except ValueError:
                errDetail = 'wrong function definition'
        else:
            errDetail = 'wrong value type'
    elif function[:3] == 'sub':
        # substracts given value from current value
        if is_number(val):
            gv = function[4:-1]
            # check for live KNX value
            if gv[:1] == '/':
                deviceInstance.readKNXAttribute("functions live value", gv,
                                                dpt)
            try:
                val = convert_number(val)
                val = val - float(gv)
            except ValueError:
                errDetail = 'wrong function definition'
        else:
            errDetail = 'wrong value type'
    elif function[:2] == 'lt':
        # checks if current value is less then given value
        if is_number(val):
            try:
                val = float(val) < float(function[3:-1])
            except ValueError:
                errDetail = 'wrong function definition'
        else:
            errDetail = 'wrong value type'
    elif function[:2] == 'gt':
        # checks if current value is greater then given value
        if is_number(val):
            try:
                val = float(val) > float(function[3:-1])
            except ValueError:
                errDetail = 'wrong function definition'
        else:
            errDetail = 'wrong value type'
    elif function[:6] == 'eqExcl':
        # checks if current value matches given value
        # in case of string values simply put the pattern into brackets without further escaping
        # example: eqExcl("Check against this string")
        # return only True if matching, otherwise None for no further processing
        if is_number(val):
            try:
                if float(val) == float(function[7:-1]):
                    val = True
                else:
                    val = None
            except ValueError:
                errDetail = 'wrong function definition'
        elif is_bool(val):
            try:
                if convert_bool(val) == convert_bool(function[7:-1]):
                    val = True
                else:
                    val = None
            except ValueError:
                errDetail = 'wrong function definition'
        elif isinstance(val, str):
            try:
                if str(val) == str(function[7:-1]):
                    val = True
                else:
                    val = None
            except ValueError:
                errDetail = 'wrong function definition'
        else:
            errDetail = 'wrong value type'
    elif function[:2] == 'eq':
        # checks if current value matches given value, return either true or false
        if is_number(val):
            try:
                val = (float(val) == float(function[3:-1]))
            except ValueError:
                errDetail = 'wrong function definition'
        elif is_bool(val):
            try:
                val = (convert_bool(val) == convert_bool(function[3:-1]))
            except ValueError:
                errDetail = 'wrong function definition'
        elif isinstance(val, str):
            try:
                val = (str(val) == str(function[3:-1]))
            except ValueError:
                errDetail = 'wrong function definition'
        else:
            errDetail = 'wrong value type'
    elif function[:9] == 'timedelta':
        # checks delta in seconds between now and given date
        # :returns:   true if delta is outside defined delta in seconds
        try:
            errDetail = 'wrong function definition'
            delta = abs(int(function[12:-1]))
            errDetail = 'wrong value type (date cannot be parsed)'
            # retrieve both date value and set them to UTC for comparison
            timenow = datetime.now(timezone.utc)
            clienttime = dateparser.parse(val)
            if function[9:11] == 'LT':
                val = timedelta(seconds=delta) > abs(timenow - clienttime)
            elif function[9:11] == 'GT':
                val = timedelta(seconds=delta) < abs(timenow - clienttime)
            errDetail = None
        except ValueError as ex:
            errDetail += str(ex)
    elif function[:7] == 'timechg':
        # adds/deducts the defined delta in seconds to given date
        # :returns:   the new time with the time in seconds added/deducted
        try:
            errDetail = 'wrong function definition'
            delta = int(function[8:-1])
            errDetail = 'wrong value type (no date)'
            # calculate time with delta and convert it to original value type
            val = type(val)(dateparser.parse(val) + timedelta(seconds=delta))
            errDetail = None
        except ValueError:
            pass
    elif function[:6] == 'asynch':
        # asynchronous method call with not interfere with current execution
        # but it will start another thread after defined duration with defined value as function
        # syntax: asynch(<duration in sec> <function call>)
        # important: use blank as separator not comma or semicolon!
        # examples: asynch(60 val(true))
        try:
            tok = re.split("[ ]", function[7:-1])
            duration = tok[0]
            func = tok[1]
            asynchVal = executeFunction(deviceInstance, dpt, func, val,
                                        attrName, knxDest, knxFormat)
            Timer(int(duration),
                  _asynchWrite,
                  kwargs={
                      "deviceInstance": deviceInstance,
                      "attrName": attrName,
                      "knxDest": knxDest,
                      "knxFormat": knxFormat,
                      "val": asynchVal
                  }).start()
        except Exception as e:
            errDetail = 'Could not start asynchronous function - ' + str(e)
    if errDetail:
        log(
            'error',
            'Could not apply function "{0}" to value {1} - {2}'.format(
                function, val, errDetail))
    return val
Пример #12
0
    def update(self, freq):
        global UPDATEFREQ

        #####   initialization of clients #####
        # ModBus clients will be implicitely update as part of the getAttribute call

        # initialize ZigBee Gateway with latest client state
        if ZigBeeGateway():
            ZigBeeGateway().getState()

        # iterate list of attributes to be updated
        for attr in self.attrs:

            # first time initialization steps
            if UPDATEFREQ['initial'] & freq == UPDATEFREQ['initial']:
                # setup knx-based event trigger based on EIB/KNX client listener
                # ModBus - currently not implemented
                if attr['type'] == 'knx2modbus':
                    raise NotImplementedError
                # set up ZigBee listener
                elif attr['type'] == 'knx2zigbee':
                    # find corresponding ZigBee device
                    if attr['zigbeeApplID'] in self.zigbeeClients:
                        client = self.zigbeeClients[attr['zigbeeApplID']]
                        client.installListener(
                            attr['name'], attr['knxAddr'], attr['knxFormat'],
                            attr['zigbeeAttr'], attr['zigbeeFormat'],
                            getAttrSafe(attr, 'zigbeeSection'))
                elif attr['type'] == 'knx2knx':
                    # knx2knx devices require dedicated handling only as part of the registration for KNX bus monitoring
                    # create client here without keeping handle to the instance
                    client = KNX2KNXClient()
                    client.installListener(attr['name'], attr['knxAddr'],
                                           attr['knxFormat'], attr['knxDest'],
                                           getAttrSafe(attr, 'function'))
                elif attr['type'] == 'mqtt2knx':
                    # mqtt client defines its own thread which permanently listens to update events
                    # avoid registering to targets which flood your KNX bus due to high frequency of update
                    if attr['mqttApplID'] in self.mqttAppliances:
                        appliance = self.mqttAppliances[attr['mqttApplID']]
                        appliance.setupClient(attr['name'], attr['mqttTopic'],
                                              attr['knxAddr'],
                                              attr['knxFormat'],
                                              getAttrSafe(attr, 'function'),
                                              getAttrSafe(attr, 'flags'))

            # check attribute update frequency matches current thread definition
            if 'updFreq' in attr and UPDATEFREQ[attr['updFreq']] & freq > 0:

                client = None
                newVal = None

                # check update type - currently only modbus read, knx write is supported
                if attr['type'] == 'modbus2knx':
                    # find corresponding ModBus device
                    if attr['modbusApplID'] in self.modbusClients:
                        client = self.modbusClients[attr['modbusApplID']]

                        # get latest ModBus value for attribute
                        newVal = client.getAttribute(attr['name'],
                                                     attr['modbusFormat'],
                                                     attr['modbusAddrDec'])
                    else:
                        log(
                            'error',
                            'Configuration error - modbusApplID({0}) not defined'
                            .format(attr['zigbeeApplID']))
                # handle ZigBee attributes
                elif attr['type'] == 'zigbee2knx':
                    # find corresponding ZigBee device
                    if attr['zigbeeApplID'] in self.zigbeeClients:
                        client = self.zigbeeClients[attr['zigbeeApplID']]

                        # get latest ZigBee value for attribute
                        newVal = client.getAttribute(
                            attr['name'], attr['zigbeeFormat'],
                            attr['zigbeeAttr'],
                            getAttrSafe(attr, 'zigbeeSection'))
                    else:
                        log(
                            'error',
                            'Configuration error - zigbeeApplID({0}) not defined'
                            .format(attr['zigbeeApplID']))

                # write value to bus
                if client is not None and newVal is not None:
                    client.writeKNXAttribute(attr['name'], attr['knxAddr'],
                                             attr['knxFormat'], newVal,
                                             getAttrSafe(attr, 'function'),
                                             getAttrSafe(attr, 'flags'))

        # run periodically update of values - each update frequency initiating its own thread
        # initial run by main will initiate all threads at once
        ut = None
        if freq & UPDATEFREQ['critical'] > 0:
            # CRITICAL - runs every 3 seconds - ONLY USE IN EXCEPTIONABLE CASES!!
            ut = Timer(3, gateway.update, (UPDATEFREQ['critical'], ))
            ut.start()
        if freq & UPDATEFREQ['very high'] > 0:
            # VERY HIGH - runs every 10 seconds
            ut = Timer(10, gateway.update, (UPDATEFREQ['very high'], ))
            ut.start()
        if freq & UPDATEFREQ['high'] > 0:
            # HIGH - runs every 60 seconds
            Timer(60, gateway.update, (UPDATEFREQ['high'], )).start()
        if freq & UPDATEFREQ['medium'] > 0:
            # MEDIUM - runs every 10 minutes
            Timer(600, gateway.update, (UPDATEFREQ['medium'], )).start()
        if freq & UPDATEFREQ['very low'] > 0:
            # LOW - runs every 60 minutes
            Timer(3600, gateway.update, (UPDATEFREQ['very low'], )).start()
        if freq & UPDATEFREQ['low'] > 0:
            # LOW - runs every 24 hours
            Timer(86400, gateway.update, (UPDATEFREQ['low'], )).start()

        # return reference to very high thread for synchronization
        return ut