def _add_devices(self, items): for item in items: if item['type'] == 'binary': self.add_binary_device(item) continue if item['type'] == 'enum': self._add_selector_device(item['name'][0:5], item) continue if item['type'] == 'numeric': self.add_numeric_device(item) continue if item['type'] == 'switch': self._add_devices(item['features']) continue if item['type'] == 'lock': self._add_devices(item['features']) continue if item['type'] == 'climate': self._add_devices(item['features']) continue domoticz.error(self.name + ': can not process feature type "' + item['type'] + '"') domoticz.debug(json.dumps(item))
def add_binary_device(self, feature): state_access = self._has_access(feature['access'], ACCESS_STATE) write_access = self._has_access(feature['access'], ACCESS_WRITE) if (feature['name'] == 'contact' and state_access): self._add_device('sensor', feature, ContactSensor) return if (feature['name'] == 'occupancy' and state_access): self._add_device('motion', feature, MotionSensor) return if (feature['name'] == 'water_leak' and state_access): self._add_device('wleak', feature, WaterLeakSensor) return if (feature['name'] == 'tamper' and state_access): self._add_device('tamper', feature, ContactSensor) return if (feature['name'] == 'state' and state_access and write_access): alias = feature['endpoint'] if 'endpoint' in feature else 'state' self._add_device(alias, feature, OnOffSwitch) return if (feature['name'] == 'away_mode' and state_access and write_access): alias = feature['endpoint'] if 'endpoint' in feature else 'away' self._add_device(alias, feature, OnOffSwitch) return domoticz.error(self.name + ': can not process binary item "' + feature['name'] + '"') domoticz.debug(json.dumps(feature))
def install(self): domoticz.log('Installing plugin custom page...') try: source_path = Parameters['HomeFolder'] + 'frontend' templates_path = Parameters['StartupFolder'] + 'www/templates' dst_plugin_path = templates_path + '/zigbee2mqtt' domoticz.debug('Copying files from ' + source_path + ' to ' + templates_path) if not (os.path.isdir(dst_plugin_path)): os.makedirs(dst_plugin_path) copy2(source_path + '/zigbee2mqtt.html', templates_path) copy2(source_path + '/zigbee2mqtt.js', templates_path) copy2(source_path + '/plugin_config.js', dst_plugin_path) copy2(source_path + '/zigbee_devices.js', dst_plugin_path) copy2(source_path + '/zigbee_groups.js', dst_plugin_path) copy2(source_path + '/libs/leaflet.js', dst_plugin_path) copy2(source_path + '/libs/leaflet.css', dst_plugin_path) copy2(source_path + '/libs/viz.js', dst_plugin_path) copy2(source_path + '/libs/viz.full.render.js', dst_plugin_path) copy2(source_path + '/libs/ace_json_mode.js', dst_plugin_path) copy2(source_path + '/libs/ace_worker_json.js', dst_plugin_path) domoticz.log('Installing plugin custom page completed.') except Exception as e: domoticz.error('Error during installing plugin custom page') domoticz.error(repr(e))
def set_devices(self, zigbee_devices): self.devices = {} for item in zigbee_devices: device_id = item['ieee_address'] if 'type' in item and item['type'] == 'Coordinator': domoticz.debug('Coordinator address is ' + device_id) continue if 'definition' not in item: domoticz.error(item['friendly_name'] + ': device definiton not found') model = item['definition']['model'] if model in adapter_by_model: adapter = adapter_by_model[model](domoticz.get_devices()) adapter.name = item['friendly_name'] adapter.zigbee_device = item adapter.register() self.devices[device_id] = adapter else: self.devices[device_id] = UniversalAdapter(item)
def onApiCommand(self, command, data): if command == 'publish_mqtt': return self.publishToMqtt(data['topic'], data['payload']) elif command == 'remove_device': return self.devices_manager.remove(data) else: domoticz.error('Internal API command "' + command + '" is not supported by plugin')
def handle_message(self, device_data, message): device_address = device_data['ieee_addr'] device = self.get_device(device_address) domoticz.debug('zigbee device:' + str(device_address) + ' sent message:' + str(message.raw)) n_value = None s_value = None color_value = None if (device == None): domoticz.error('no device in message') # Due to internal domoticz bug, app crashes if we try to use device just after we create it # so just create and exit for now # device = self._create_device(device_data, message) return self._create_device(device_data) if "brightness" in message.raw: value = message.raw["brightness"] n_value = 1 if value > 0 else 0 s_value = str(int(value * 100 / 255)) signal_level = message.get_signal_level() if "state" in message.raw: if message.raw['state'].upper() == 'OFF': n_value = 0 else: n_value = 1 if "color" in message.raw: color_value = self.get_color_value(message) #when no values in message, reuse existing values from device payload = {} if (n_value != None): payload['nValue'] = n_value if (s_value != None): payload['sValue'] = s_value if (signal_level != None): payload['SignalLevel'] = signal_level if (color_value != None): payload['Color'] = color_value if payload: if not 'nValue' in payload: payload['nValue'] = device.nValue if not 'sValue' in payload: payload['sValue'] = device.sValue self.update_device(device, payload) else: self.touch_device(device)
def __init__(self, zigbee_device): self.devices = [] self.zigbee_device = zigbee_device self.name = zigbee_device['friendly_name'] if 'exposes' not in zigbee_device['definition']: domoticz.error(self.name + ': device exposes not found') return self._add_features(zigbee_device['definition']['exposes']) self.register()
def create_device(self, unit, device_id, device_name): if (self.sensor_type == None): domoticz.error('Sensor type is not specified') return return domoticz.create_device(Unit=unit, DeviceID=device_id, Name=device_name, Type=244, Subtype=73, Switchtype=self.sensor_type)
def _add_light_feature(self, feature): light_features = feature['features'] state = get_feature(light_features, 'state') brightness = get_feature(light_features, 'brightness') color_temp = get_feature(light_features, 'color_temp') color = get_feature(light_features, 'color_xy') alias = generate_alias(state, 'light') devices = domoticz.get_devices() if state and brightness and color_temp and color: device = RGBWLight(devices, alias) device.set_state_feature(state) device.set_brightness_feature(brightness) device.set_color_temp_feature(color_temp) device.set_color_feature(color) elif state and brightness and color: device = RGBLight(devices, alias) device.set_state_feature(state) device.set_brightness_feature(brightness) device.set_color_feature(color) elif state and brightness and color_temp: device = CTLight(devices, alias) device.set_state_feature(state) device.set_brightness_feature(brightness) device.set_color_temp_feature(color_temp) elif state and brightness: device = DimmerLight(devices, alias) device.set_state_feature(state) device.set_brightness_feature(brightness) elif state: device = OnOffLight(devices, 'switch') device.set_state_feature(state) else: domoticz.error( self.adapter.name + ': can not find appropriate device type to handle light feature' ) domoticz.debug(json.dumps(feature)) if device: device.feature = feature # Add rest light features for item in light_features: name = item['name'] if name != 'state' and name != 'brightness' and name != 'color_temp' and name != 'color_xy': self.adapter._add_feature(item) return device
def _create_device(self, device_data): device_address = device_data['ieee_addr'] feature_name = self._get_feature_name() endpoint = self._get_zigbee_endpoint() alias_config = configuration.get_alias_by_zigbee( device_address, feature_name, endpoint) if alias_config: domoticz.log( 'Alias for handling ' + feature_name + '(endpoint: ' + str(endpoint) + ') feature of device ' + device_address + ' already exists in plugin configuration but Domoticz logical device with ID ' + alias_config['domoticz']['device_id'] + '(unit: ' + str(alias_config['domoticz']['unit']) + ') is not found. If you have removed device and want plugin to recreate it, please remove alias from configuration as well.' ) return None domoticz.debug('Creating domoticz device to handle "' + self.value_key + '" key for device with ieeeAddr ' + device_address) device_id = device_address if self.device_name_suffix != '': device_name = device_data['friendly_name'] + self.device_name_suffix elif hasattr(self, 'feature'): device_name = device_data['friendly_name'] + ' (' + self.feature[ 'property'] + ')' else: device_name = device_data['friendly_name'] unit = configuration.get_device_available_unit(device_address) feature_name = self._get_feature_name() endpoint = self._get_zigbee_endpoint() if blacklist.has(device_id + '_' + feature_name): domoticz.debug('Device is in blacklist, skipped.') return None if unit == None: domoticz.error( 'Can not create new Domoticz device: maximum of 255 logical devices per phisical is reached.' ) return None device = self.create_device(unit, device_id, device_name) configuration.set_zigbee_feature_device(device_address, feature_name, endpoint, device_id, unit, self.alias) return device
def _get_legacy_device_data(self): if 'type' not in self.zigbee_device: domoticz.error(self.name + ': device does not contain type') return if 'model' not in self.zigbee_device['definition']: domoticz.error(self.name + ': device definiton does not contain model') return return { 'type': self.zigbee_device['type'], 'model': self.zigbee_device['definition']['model'], 'ieee_addr': self.zigbee_device['ieee_address'], 'friendly_name': self.name }
def set_config_item(key, value): config = {} try: config = domoticz.get_configuration() if (key != None): config[key] = value else: config = value # set whole configuration if no key specified config = domoticz.set_configuration(config) except Exception as inst: domoticz.error("Domoticz.Configuration operation failed: '" + str(inst) + "'") return config
def get_config_item(key=None, default={}): value = default try: config = domoticz.get_configuration() if (key != None): value = config[key] # only return requested key if there was one else: value = config # return the whole configuration if no key except KeyError: value = default except Exception as inst: domoticz.error("Domoticz.Configuration read failed: '" + str(inst) + "'") return value
def __init__(self, zigbee_device): self.devices = [] self.zigbee_device = zigbee_device self.name = zigbee_device['friendly_name'] if 'exposes' not in zigbee_device['definition']: domoticz.error(self.name + ': device exposes not found') return self.feature_processors = [ CoverFeatureProcessor(self), EnergyFeatureProcessor(), LightFeatureProcesor(self), TempHumPressureFeatureProcessor(), ] self._add_features(zigbee_device['definition']['exposes']) self.register()
def _add_feature(self, item): if item['type'] == 'binary': self.add_binary_device(item) elif item['type'] == 'enum': self._add_selector_device(item['name'][0:5], item) elif item['type'] == 'numeric': self.add_numeric_device(item) elif item['type'] == 'switch': self._add_features(item['features']) elif item['type'] == 'light': self._add_light_feature(item) elif item['type'] == 'lock': self._add_features(item['features']) elif item['type'] == 'climate': self._add_features(item['features']) elif item['type'] == 'cover': self._add_cover_feature(item) else: domoticz.error(self.name + ': can not process feature type "' + item['type'] + '"') domoticz.debug(json.dumps(item))
def _create_device(self, device_data): device_address = device_data['ieee_addr'] domoticz.debug( 'Creating domoticz device to handle "' + self.value_key + '" key for device with ieeeAddr ' + device_address ) device_id = device_address + '_' + self.alias device_name = device_data['friendly_name'] + self.device_name_suffix unit = domoticz.get_first_available_unit() if blacklist.has(device_id): domoticz.debug('Device is in blacklist, skipped.') return None if unit == None: domoticz.error('Can not create new Domoticz device: maximum of 255 devices is reached.') return None return self.create_device(unit, device_id, device_name)
def uninstall(self): domoticz.log('Uninstalling plugin custom page...') try: templates_path = Parameters['StartupFolder'] + 'www/templates' dst_plugin_path = templates_path + '/zigbee2mqtt' domoticz.debug('Removing files from ' + templates_path) if (os.path.isdir(dst_plugin_path)): rmtree(dst_plugin_path) if os.path.exists(templates_path + "/zigbee2mqtt.html"): os.remove(templates_path + "/zigbee2mqtt.html") if os.path.exists(templates_path + "/zigbee2mqtt.js"): os.remove(templates_path + "/zigbee2mqtt.js") domoticz.log('Uninstalling plugin custom page completed.') except Exception as e: domoticz.error('Error during uninstalling plugin custom page') domoticz.error(repr(e))
def generate_command(self, command, level, color): cmd = command.upper() state_value_key = self.state_feature['property'] position_value_key = self.position_feature['property'] if cmd == 'ON': return { state_value_key: 'close' } elif cmd == 'OFF': return { state_value_key: 'open' } elif cmd == 'SET LEVEL': return { position_value_key: int(100 - level) } elif cmd == 'STOP': return { state_value_key: 'stop' } else: domoticz.error('Blind switch: unable to handle command "' + command + '"')
def get_string_value(self, value, device): domoticz.error( 'Device with alias "' + self.alias + '" for key ' + self.value_key + ' can not calculate string value' )
def add_numeric_device(self, feature): state_access = self._has_access(feature['access'], ACCESS_STATE) write_access = self._has_access(feature['access'], ACCESS_WRITE) # TODO: Use energy value for `power` feature if feature['name'] == 'energy': return if (feature['name'] == 'linkquality' and state_access): if domoticz.get_plugin_config('trackLinkQuality'): self._add_device('signal', feature, CustomSensor, ' (Link Quality)') return if (feature['name'] == 'battery' and state_access): if domoticz.get_plugin_config('useBatteryDevices'): self._add_device('btperc', feature, PercentageSensor, ' (Battery)') return if (feature['name'] == 'brightness' and state_access): alias = self._generate_alias(feature, 'light') self._add_device(alias, feature, DimmerSwitch) return if (feature['name'] == 'illuminance' and state_access): alias = self._generate_alias(feature, 'lux') self._add_device(alias, feature, LuxSensor, ' (Illuminance)') return if (feature['name'] == 'illuminance_lux' and state_access): alias = self._generate_alias(feature, 'lx') self._add_device(alias, feature, LuxSensor, ' (Illuminance Lux)') return if (feature['name'] == 'local_temperature' and state_access): alias = self._generate_alias(feature, 'ltemp') self._add_device(alias, feature, TemperatureSensor, ' (Local Temperature)') return if (feature['name'] == 'soil_moisture' and state_access): alias = self._generate_alias(feature, 'pres') self._add_device(alias, feature, PercentageSensor, ' (Soil Moisture)') return if (feature['name'] == 'voltage' and state_access): alias = self._generate_alias(feature, 'volt') self._add_device(alias, feature, VoltageSensor, ' (Voltage)') return if (feature['name'] == 'current' and state_access): alias = self._generate_alias(feature, 'ampere') self._add_device(alias, feature, CurrentSensor, ' (Current)') return if 'setpoint' in feature['name'] and feature['unit'] == '°C' and write_access: alias = self._generate_alias(feature, 'spoint') self._add_device(alias, feature, SetPoint, ' (Setpoint)') return if (feature['name'] == 'position' and state_access): alias = self._generate_alias(feature, 'level') self._add_device(alias, feature, LevelSwitch) return if (feature['name'] == 'color_temp_startup' and state_access): return if (feature['name'] == 'requested_brightness_level' and state_access): return if (feature['name'] == 'requested_brightness_percent' and state_access): return domoticz.error(self.name + ': can not process numeric item "' + feature['name'] + '"') domoticz.debug(json.dumps(feature))
def add_binary_device(self, feature): state_access = self._has_access(feature['access'], ACCESS_STATE) write_access = self._has_access(feature['access'], ACCESS_WRITE) if (feature['name'] == 'alarm' and state_access and write_access): alias = self._generate_alias(feature, 'alarm') self._add_device(alias, feature, SirenSwitch) return if (feature['name'] == 'battery_low' and state_access): alias = self._generate_alias(feature, 'lowbtr') self._add_device(alias, feature, ContactSensor, ' (Low Battery)') return if (feature['name'] == 'contact' and state_access): alias = self._generate_alias(feature, 'sensor') self._add_device(alias, feature, ContactSensor) return if (feature['name'] == 'gas' and state_access): alias = self._generate_alias(feature, 'gas') self._add_device(alias, feature, SmokeSensor, ' (Gas sensor)') return if (feature['name'] == 'occupancy' and state_access): alias = self._generate_alias(feature, 'motion') self._add_device(alias, feature, MotionSensor) return if (feature['name'] == 'smoke' and state_access): alias = self._generate_alias(feature, 'smoke') self._add_device(alias, feature, SmokeSensor, ' (Smoke sensor)') return if (feature['name'] == 'water_leak' and state_access): alias = self._generate_alias(feature, 'wleak') self._add_device(alias, feature, WaterLeakSensor) return if (feature['name'] == 'tamper' and state_access): alias = self._generate_alias(feature, 'tamper') self._add_device(alias, feature, ContactSensor) return if (feature['name'] == 'consumer_connected' and state_access): alias = self._generate_alias(feature, 'consmr') self._add_device(alias, feature, ContactSensor, ' (Consumer Connected)') return if (feature['name'] == 'state' and state_access and write_access): alias = self._generate_alias(feature, 'state') self._add_device(alias, feature, OnOffSwitch) return if (feature['name'] == 'led_disabled_night' and state_access and write_access): alias = self._generate_alias(feature, 'nled') self._add_device(alias, feature, OnOffSwitch) return if (feature['name'] == 'power_outage_memory' and state_access and write_access): alias = self._generate_alias(feature, 'pwrmem') self._add_device(alias, feature, OnOffSwitch, ' (Power Outage Memory)') return if (feature['name'] == 'auto_off' and state_access and write_access): alias = self._generate_alias(feature, 'autoff') self._add_device(alias, feature, OnOffSwitch, ' (Auto Off)') return if (feature['name'] == 'away_mode' and state_access and write_access): alias = self._generate_alias(feature, 'away') self._add_device(alias, feature, OnOffSwitch) return domoticz.error(self.name + ': can not process binary item "' + feature['name'] + '"') domoticz.debug(json.dumps(feature))
def onMQTTPublish(self, topic, message): # domoticz.debug("MQTT message: " + topic + " " + str(message)) topic = topic.replace(self.base_topic + '/', '') self.api.handle_mqtt_message(topic, message) bridge.handle_mqtt_message(topic, message) if (topic == 'bridge/config/permit_join'): return if (topic == 'bridge/config/logging') or (topic == 'bridge/logging'): # TODO: Add log feature return if (topic == 'bridge/devices'): self.devices_manager.set_devices(message) return if (topic == 'bridge/config'): permit_join = 'enabled' if message['permit_join'] else 'disabled' domoticz.debug('Zigbee2mqtt log level is ' + message['log_level']) domoticz.log('Joining new devices is ' + permit_join + ' on the zigbee bridge') return if (topic == 'bridge/state'): domoticz.log('Zigbee2mqtt bridge is ' + message) return if (topic == 'bridge/log'): is_connected = message['type'] == 'device_connected' is_removed = message['type'] == 'device_removed' is_paired = message['type'] == 'pairing' and message[ 'message'] == 'interview_successful' if message['type'] == 'groups': domoticz.log('Received groups list from bridge') self.groups_manager.register_groups(message['message']) if is_connected or is_removed or is_paired: self.publishToMqtt('bridge/config/devices/get', '') if message['type'] == 'ota_update': domoticz.log(message['message']) if (message['type'] == 'device_renamed'): domoticz.debug("Device renamed from '{0}' to '{1}'".format( message['message']['from'], message['message']['to'])) if (self.devices_manager.get_device_by_name( message['message']['from']) != None): domoticz.debug("attempt to rename on bridge/log") toRename = self.devices_manager.get_device_by_name( message['message']['from']) toRename.zigbee_device['friendly_name'] = message[ 'message']['to'] self.devices_manager.devices[ toRename.zigbee_device['ieee_address']] = toRename else: domoticz.debug("attempt to rename failed on bridge/log") if message['type'] == 'zigbee_publish_error': #an error occured on publish to the zigbee network deviceMeta = message['meta'] domoticz.error("A Zigbee publish error occured for device '" + deviceMeta['friendly_name'] + "' with error message: " + message['message']) return if (self.groups_manager.get_group_by_name(topic) != None): self.groups_manager.handle_mqtt_message(topic, message) elif (self.devices_manager.get_device_by_name(topic) != None): self.devices_manager.handle_mqtt_message(topic, message)
def create_device(self, unit, device_id, device_name): domoticz.error( 'Unable to create device to handle "' + self.value_key + '" value for device "' + device_name + '"' )
def get_numeric_value(self, value, device): domoticz.error( 'Device with alias "' + self.alias + '" for key ' + self.value_key + ' can not calculate numeric value' )
def add_numeric_device(self, feature): state_access = self._has_access(feature['access'], ACCESS_STATE) write_access = self._has_access(feature['access'], ACCESS_WRITE) # TODO: Use energy value for `power` feature if feature['name'] == 'energy': return if (feature['name'] == 'linkquality' and state_access): if domoticz.get_plugin_config('trackLinkQuality'): self._add_device('signal', feature, CustomSensor, ' (Link Quality)') return if (feature['name'] == 'battery' and state_access): if domoticz.get_plugin_config('useBatteryDevices'): self._add_device('btperc', feature, PercentageSensor, ' (Battery)') return if (feature['name'] == 'brightness' and state_access): alias = feature['endpoint'] if 'endpoint' in feature else 'light' self._add_device(alias, feature, DimmerSwitch) return if (feature['name'] == 'humidity' and state_access): self._add_device('hum', feature, HumiditySensor, ' (Humidity)') return if (feature['name'] == 'temperature' and state_access): self._add_device('temp', feature, TemperatureSensor, ' (Temperature)') return if (feature['name'] == 'local_temperature' and state_access): self._add_device('ltemp', feature, TemperatureSensor, ' (Local Temperature)') return if (feature['name'] == 'pressure' and state_access): self._add_device('pres', feature, PressureSensor, ' (Pressure)') return if (feature['name'] == 'voltage' and state_access): self._add_device('volt', feature, VoltageSensor, ' (Voltage)') return if (feature['name'] == 'current' and state_access): self._add_device('ampere', feature, CurrentSensor, ' (Current)') return if (feature['name'] == 'power' and state_access and feature['unit'] == 'W'): device = KwhSensor(domoticz.get_devices(), 'power', [feature['property']], ' (Power)') device.feature = feature self.devices.append(device) return if 'setpoint' in feature['name'] and feature[ 'unit'] == '°C' and write_access: alias = feature['endpoint'] if 'endpoint' in feature else 'spoint' self._add_device(alias, feature, SetPoint, ' (Setpoint)') return if (feature['name'] == 'position' and state_access): alias = feature['endpoint'] if 'endpoint' in feature else 'level' self._add_device(alias, feature, LevelSwitch) return domoticz.error(self.name + ': can not process numeric item "' + feature['name'] + '"') domoticz.debug(json.dumps(feature))
def add_binary_device(self, feature): state_access = self._has_access(feature['access'], ACCESS_STATE) write_access = self._has_access(feature['access'], ACCESS_WRITE) if (feature['name'] == 'battery_low' and state_access): self._add_device('lowbtr', feature, ContactSensor, ' (Low Battery)') return if (feature['name'] == 'contact' and state_access): self._add_device('sensor', feature, ContactSensor) return if (feature['name'] == 'occupancy' and state_access): self._add_device('motion', feature, MotionSensor) return if (feature['name'] == 'water_leak' and state_access): self._add_device('wleak', feature, WaterLeakSensor) return if (feature['name'] == 'tamper' and state_access): self._add_device('tamper', feature, ContactSensor) return if (feature['name'] == 'consumer_connected' and state_access): self._add_device('consmr', feature, ContactSensor, ' (Consumer Connected)') return if (feature['name'] == 'state' and state_access and write_access): alias = feature['endpoint'] if 'endpoint' in feature else 'state' self._add_device(alias, feature, OnOffSwitch) return if (feature['name'] == 'led_disabled_night' and state_access and write_access): alias = feature['endpoint'] if 'endpoint' in feature else 'nled' self._add_device(alias, feature, OnOffSwitch) return if (feature['name'] == 'power_outage_memory' and state_access and write_access): alias = feature['endpoint'] if 'endpoint' in feature else 'pwrmem' self._add_device(alias, feature, OnOffSwitch, ' (Power Outage Memory)') return if (feature['name'] == 'auto_off' and state_access and write_access): alias = feature['endpoint'] if 'endpoint' in feature else 'autoff' self._add_device(alias, feature, OnOffSwitch, ' (Auto Off)') return if (feature['name'] == 'away_mode' and state_access and write_access): alias = feature['endpoint'] if 'endpoint' in feature else 'away' self._add_device(alias, feature, OnOffSwitch) return domoticz.error(self.name + ': can not process binary item "' + feature['name'] + '"') domoticz.debug(json.dumps(feature))
def execute(self, params): domoticz.error('Command is not implemented')