def getMainDevice(self) -> Device: if not self._bufferedMainDevice: devices = self.DeviceManager.getDevicesForSkill('AliceCore') if len(devices) == 0: self.logWarning(f'No main device exists using DUMMY - RESTART REQUIRED!') values = {'id': 0, 'name': 'dummy', 'typeID': 0, 'uid': self.ConfigManager.getAliceConfigByName('uuid'), 'locationID': 1} self._bufferedMainDevice = Device(data=values) return self._bufferedMainDevice self._bufferedMainDevice = devices[0] if self._bufferedMainDevice.uid != self.ConfigManager.getAliceConfigByName('uuid'): self._bufferedMainDevice.pairingDone(self.ConfigManager.getAliceConfigByName('uuid')) raise Exception('UUID changed, restart required!') return self._bufferedMainDevice
def getDeviceIcon(self, device: Device) -> str: if not device.uid: return 'EspPir.png' if not device.connected: return 'PIR_offline.png' if device.getCustomValue( 'disabled' ): # TODO please implement "disabled" status - I don't own a PIR return 'PIR_disabled.png' if device.getCustomValue( 'cooldown' ): # TODO please implement some kind of cooldown so you can see recent acivities return 'PIR_justActivated.png' return 'EspPir.png'
def changeLocation(self, device: Device, locationId: int): # check location is good loc = self.LocationManager.getLocation(locId=locationId) if not loc: raise Exception("Location not found") # check location but not global self.assertDeviceTypeAllowedAtLocation(typeId=device.getDeviceType().id, locationId=locationId, moveDevice=True) # update device and trigger device type dependent Updates # might raise exception and cancle DB update device.changeLocation(locationId=locationId) # update DB self.DatabaseManager.update(tableName=self.DB_DEVICE, callerName=self.name, values={'locationID': locationId}, row=('id', device.id))
def _startBroadcast(self, device: Device, uid: str, replyOnDeviceUid: str = ''): """ Starts the internal process of broadcasting for a new device :param device: the device class :param uid: the attributed uid :param replyOnDeviceUid: if provided,confirm on the device :return: """ self._broadcastFlag.set() while self._broadcastFlag.isSet(): self._broadcastSocket.sendto( bytes(f'{self.Commons.getLocalIp()}:{self._listenPort}:{uid}', encoding='utf8'), ('<broadcast>', self._broadcastPort)) try: sock, address = self._listenSocket.accept() sock.settimeout(None) answer = sock.recv(1024).decode() deviceIp = answer.split(':')[0] deviceType = answer.split(':')[1] if deviceType.lower() != device.deviceTypeName.lower(): self.logWarning( f'Device with uid {uid} is not of correct type. Waiting on **{device.deviceTypeName}**, got **{deviceType}**' ) self._broadcastSocket.sendto( bytes('nok', encoding='utf8'), (deviceIp, self._broadcastPort)) raise socket.timeout device.pairingDone(uid=uid) self.logInfo(f'Device with uid **{uid}** successfully paired') if replyOnDeviceUid: self.MqttManager.say(text=self.TalkManager.randomTalk( 'newDeviceAdditionSuccess', skill='system'), deviceUid=replyOnDeviceUid) #self.ThreadManager.doLater(interval=5, func=self.WakewordRecorder.uploadToNewDevice, args=[uid]) self._broadcastSocket.sendto(bytes('ok', encoding='utf8'), (deviceIp, self._broadcastPort)) self._stopBroadcasting() except socket.timeout: self.logInfo('No device query received')
def addNewDevice(self, deviceTypeId: int, locationId: int = None, uid: str = None, noChecks: bool = False, skillName: str = None) -> Device: # get or create location from different inputs location = self.LocationManager.getLocation(locId=locationId) if not noChecks: self.assertDeviceTypeAllowedAtLocation(locationId=location.id, typeId=deviceTypeId) if not skillName: skillName = self.getDeviceType(_id=deviceTypeId).skill values = { 'typeID': deviceTypeId, 'uid': uid, 'locationID': location.id, 'display': "{'x': '10', 'y': '10', 'rotation': 0, 'width': 45, 'height': 45}", 'skillname': skillName } values['id'] = self.databaseInsert(tableName=self.DB_DEVICE, values=values) self._devices[values['id']] = Device(data=values) return self._devices[values['id']]
def getDeviceIcon(self, device: Device) -> str: if not device.connected: return 'satellite_offline.png' if device.getCustomValue('dnd'): return 'satellite_muted.png' if not device.uid: return 'satellite.png' return 'satellite_online.png'
def getDeviceIcon(self, device: Device) -> str: if not device.uid: return 'EspSwitch.png' if not device.connected: return 'switch_offline.png' if device.getCustomValue('on'): return 'switch_on.png' else: return 'switch_off.png'
def addNewDevice(self, ttype: str, room: str = None, uid: str = None) -> bool: if not room: room = self._broadcastRoom if not uid: uid = self._getFreeUID() try: values = {'type': ttype, 'uid': uid, 'room': room} values['id'] = self.databaseInsert(tableName='devices', query='INSERT INTO :__table__ (type, uid, room) VALUES (:type, :uid, :room)', values=values) self._devices[uid] = Device(values, True) return True except Exception as e: self.logWarning(f"Couldn't insert device in database: {e}") return False
def startBroadcast(self, device: Device, uid: str, replyOnSiteId: str = ''): self._broadcastFlag.set() location = device.getMainLocation() while self._broadcastFlag.isSet(): self._broadcastSocket.sendto(bytes(f'{self.Commons.getLocalIp()}:{self._listenPort}:{location.getSaveName()}:{uid}', encoding='utf8'), ('<broadcast>', self._broadcastPort)) try: sock, address = self._listenSocket.accept() sock.settimeout(None) answer = sock.recv(1024).decode() deviceIp = answer.split(':')[0] device.pairingDone(uid=uid) self.logWarning(f'Device with uid {uid} successfully paired') if replyOnSiteId: self.MqttManager.say(text=self.TalkManager.randomTalk('newDeviceAdditionSuccess'), client=replyOnSiteId) self.ThreadManager.doLater(interval=5, func=self.WakewordRecorder.uploadToNewDevice, args=[uid]) self._broadcastSocket.sendto(bytes('ok', encoding='utf8'), (deviceIp, self._broadcastPort)) self.stopBroadcasting() except socket.timeout: self.logInfo('No device query received')
def startTasmotaFlashingProcess(self, device: Device, replyOnSiteId: str, session: DialogSession) -> bool: replyOnSiteId = self.MqttManager.getDefaultSiteId(replyOnSiteId) if session: self.ThreadManager.doLater( interval=0.5, func=self.MqttManager.endDialog, args=[ session.sessionId, self.randomTalk('connectESPForFlashing') ]) elif replyOnSiteId: self.ThreadManager.doLater( interval=0.5, func=self.MqttManager.say, args=[self.randomTalk('connectESPForFlashing')]) self._broadcastFlag.set() binFile = Path('tasmota.bin') if binFile.exists(): binFile.unlink() try: tasmotaConfigs = TasmotaConfigs( deviceType=device.getDeviceType().ESPTYPE, uid='dummy') req = requests.get(tasmotaConfigs.getTasmotaDownloadLink()) with binFile.open('wb') as file: file.write(req.content) self.logInfo('Downloaded tasmota.bin') except Exception as e: self.logError(f'Something went wrong downloading tasmota.bin: {e}') self._broadcastFlag.clear() return False self.ThreadManager.newThread(name='flashThread', target=self.doFlashTasmota, args=[device, replyOnSiteId]) return True
def addNewDevice(self, deviceType: str, skillName: str, locationId: int = 0, uid: Union[str, int] = None, abilities: List[DeviceAbility] = None, displaySettings: Dict = None, deviceParam: dict = None, displayName: str = None, noChecks: bool = False) -> Optional[Device]: """ Adds a new device to Alice. The device is immediately saved in DB :param deviceType: the type of device, defined by each skill :param skillName: the skill this device belongs to :param locationId: the location id where the device is placed :param uid: the device uid :param abilities: a list of abilities for this device :param displaySettings: a dict of display settings, for the UI :param deviceParam: a dict of additional values :param displayName: the ui display name :param noChecks: if true, no condition checks will apply :return: A Device instance or None if something failed """ if not noChecks: dType: DeviceType = self.getDeviceType(skillName=skillName, deviceType=deviceType) if not dType: self.logError( f'Cannot add device **{deviceType}**, device type not found!' ) raise DeviceTypeUndefined(deviceType) if 0 < dType.totalDeviceLimit <= len( self.getDevicesByType(deviceType=dType, connectedOnly=False)): raise MaxDeviceOfTypeReached(dType.totalDeviceLimit) if 0 < dType.perLocationLimit <= len( self.getDevicesByLocation( locationId, deviceType=dType, connectedOnly=False)): raise MaxDevicePerLocationReached(dType.perLocationLimit) if not displaySettings: if locationId == 0: displaySettings = {'x': 50000, 'y': 50000} else: displaySettings = {'x': 2, 'y': 2} data = { 'abilities': abilities, 'displayName': displayName, 'parentLocation': locationId, 'settings': displaySettings, 'deviceParams': deviceParam, 'skillName': skillName, 'typeName': deviceType, 'uid': uid or str(uuid.uuid4()) } device = Device(data) self._devices[device.id] = device if device.deviceType.allowLocationLinks: self.addDeviceLink(targetLocation=locationId, deviceId=device.id) device.onStart() return device
class DeviceManager(Manager): DB_DEVICE = 'devices' DB_LINKS = 'deviceLinks' DB_TYPES = 'deviceTypes' DATABASE = { DB_DEVICE: [ 'id INTEGER PRIMARY KEY', #NOSONAR 'typeID INTEGER NOT NULL', 'uid TEXT', 'locationID INTEGER NOT NULL', 'name TEXT', 'display TEXT', 'devSettings TEXT', 'skillName TEXT' ], DB_LINKS: [ 'id INTEGER PRIMARY KEY', #NOSONAR 'deviceID INTEGER NOT NULL', 'locationID INTEGER NOT NULL', 'locSettings TEXT' ], DB_TYPES: [ 'id INTEGER PRIMARY KEY', #NOSONAR 'skill TEXT NOT NULL', 'name TEXT NOT NULL', 'devSettings TEXT', 'locSettings TEXT' ] } SAT_TYPE = 'AliceSatellite' def __init__(self): super().__init__(databaseSchema=self.DATABASE) self._devices: Dict[int, Device] = dict() self._deviceLinks: Dict[int, DeviceLink] = dict() # self._idToUID: Dict[int, str] = dict() #option: maybe relevant for faster access? self._deviceTypes: Dict[int, DeviceType] = dict() self._heartbeats = dict() self._heartbeatsCheckTimer = None self._heartbeat: Optional[Heartbeat] = None self._bufferedMainDevice = None def onStart(self): super().onStart() self.loadDevices() self.loadLinks() self.logInfo(f'Loaded **{len(self._devices)}** device', plural='device') @property def devices(self) -> Dict[int, Device]: return self._devices def onBooted(self): self.MqttManager.publish(topic=constants.TOPIC_CORE_RECONNECTION) if self._devices: self.ThreadManager.newThread(name='checkHeartbeats', target=self.checkHeartbeats) self._heartbeat = Heartbeat() def onStop(self): super().onStop() if self._heartbeat: self._heartbeat.stopHeartBeat() self.MqttManager.publish(topic=constants.TOPIC_CORE_DISCONNECTION) def onDeviceStatus(self, session: DialogSession): device = self.getDeviceByUID(uid=session.payload['uid']) if device: device.onDeviceStatus(session) def deviceMessage(self, message: MQTTMessage) -> DialogSession: return self.DialogManager.newTempSession(message=message) def loadDevices(self): for row in self.databaseFetch(tableName=self.DB_DEVICE, method='all'): self._devices[row['id']] = Device(row) def loadLinks(self): for row in self.databaseFetch(tableName=self.DB_LINKS, method='all'): self._deviceLinks[row['id']] = DeviceLink(row) # noinspection SqlResolve def isUIDAvailable(self, uid: str) -> bool: try: count = self.databaseFetch( tableName='devices', query='SELECT COUNT() FROM :__table__ WHERE uid = :uid', values={'uid': uid})[0] return count <= 0 except sqlite3.OperationalError as e: self.logWarning(f"Couldn't check device from database: {e}") return False # noinspection SqlResolve def addNewDevice(self, deviceTypeId: int, locationId: int = None, uid: str = None, noChecks: bool = False, skillName: str = None) -> Device: # get or create location from different inputs location = self.LocationManager.getLocation(locId=locationId) if not noChecks: self.assertDeviceTypeAllowedAtLocation(locationId=location.id, typeId=deviceTypeId) if not skillName: skillName = self.getDeviceType(_id=deviceTypeId).skill values = { 'typeID': deviceTypeId, 'uid': uid, 'locationID': location.id, 'display': "{'x': '10', 'y': '10', 'rotation': 0, 'width': 45, 'height': 45}", 'skillname': skillName } values['id'] = self.databaseInsert(tableName=self.DB_DEVICE, values=values) self._devices[values['id']] = Device(data=values) return self._devices[values['id']] def changeLocation(self, device: Device, locationId: int): # check location is good loc = self.LocationManager.getLocation(locId=locationId) if not loc: raise Exception("Location not found") # check location but not global self.assertDeviceTypeAllowedAtLocation( typeId=device.getDeviceType().id, locationId=locationId, moveDevice=True) # update device and trigger device type dependent Updates # might raise exception and cancle DB update device.changeLocation(locationId=locationId) # update DB self.DatabaseManager.update(tableName=self.DB_DEVICE, callerName=self.name, values={'locationID': locationId}, row=('id', device.id)) def devUIDtoID(self, uid: str) -> int: for _id, dev in self.devices.items(): if dev.uid == uid: return _id def devIDtoUID(self, _id: int) -> str: return self.devices[_id].uid def deleteDeviceID(self, deviceId: int): self.devices.pop(deviceId) self.DatabaseManager.delete(tableName=self.DB_DEVICE, callerName=self.name, values={"id": deviceId}) self.DatabaseManager.delete(tableName=self.DB_LINKS, callerName=self.name, values={"id": deviceId}) def getDeviceType(self, _id: int): return self.deviceTypes.get(_id, None) def getDeviceTypeByName(self, name: str) -> Optional[DeviceType]: for device in self.deviceTypes.values(): if device.name == name: return device return None def getDeviceTypesForSkill(self, skillName: str) -> Dict[int, DeviceType]: return { _id: deviceType for _id, deviceType in self.deviceTypes.items() if deviceType.skill == skillName } def removeDeviceTypesForSkill(self, skillName: str): for _id in self.getDeviceTypesForSkill(skillName): self.deviceTypes.pop(_id, None) def addDeviceTypes(self, deviceTypes: Dict): self.deviceTypes.update(deviceTypes) def removeDeviceType(self, _id: int): self.DatabaseManager.delete(tableName=self.DB_TYPES, callerName=self.name, values={'id': _id}) self._deviceTypes.pop(_id) def removeDeviceTypeName(self, _name: str): self.DatabaseManager.delete(tableName=self.DB_TYPES, callerName=self.name, values={'name': _name}) def getLink(self, _id: int = None, deviceId: int = None, locationId: Union[list, int] = None) -> DeviceLink: if _id: return self._deviceLinks.get(_id, None) if not deviceId or not locationId: raise Exception('getLink: supply locationID or deviceID!') if not isinstance(locationId, List): locationId = [locationId] for link in self._deviceLinks.values(): if link.deviceId == deviceId and link.locationId in locationId: return link def addLink(self, deviceId: int, locationId: int): device = self.getDeviceById(deviceId) deviceType = device.getDeviceType() if not deviceType.allowLocationLinks: raise Exception( f'Device type {deviceType.name} can\'t be linked to other rooms' ) if self.getLink(deviceId=deviceId, locationId=locationId): raise Exception( f'There is already a link from {deviceId} to {locationId}') values = { 'deviceID': deviceId, 'locationID': locationId, 'locSettings': json.dumps(deviceType.initialLocationSettings) } # noinspection SqlResolve values['id'] = self.databaseInsert( tableName=self.DB_LINKS, query= 'INSERT INTO :__table__ (deviceID, locationID, locSettings) VALUES (:deviceID, :locationID, :locSettings)', values=values) self.logInfo( f'Added link from device {deviceId} to location {locationId}') self._deviceLinks[values['id']] = DeviceLink(data=values) def deleteLink(self, _id: int = None, deviceId: int = None, locationId: int = None): link = self.DeviceManager.getLink(_id=_id, deviceId=deviceId, locationId=locationId) if not link: raise Exception('Link not found.') self.logInfo(f'Removing link {link.id}') self._deviceLinks.pop(link.id) self.DatabaseManager.delete(tableName=self.DB_LINKS, callerName=self.name, values={"id": link.id}) def deleteDeviceUID(self, deviceUID: str): self.deleteDeviceID(deviceId=self.devUIDtoID(uid=deviceUID)) def getFreeUID(self, base: str = '') -> str: """ Gets a free uid. A free uid is a uid not declared in database. If base is provided it will be used as a uid pattern :param base: str :return: str """ if not base: uid = str(uuid.uuid4()) else: uid = base = base.replace(':', '').replace(' ', '') while not self.isUIDAvailable(uid): if not base: uid = str(uuid.uuid4()) else: aList = list(base) shuffle(aList) uid = ''.join(aList) return uid def broadcastToDevices(self, topic: str, payload: dict = None, deviceType: DeviceType = None, location: Location = None, connectedOnly: bool = True): if not payload: payload = dict() for device in self._devices.values(): if deviceType and device.getDeviceType() != deviceType: continue if location and device.isInLocation(location): continue if connectedOnly and not device.connected: continue payload.setdefault('uid', device.uid) payload.setdefault('siteId', device.siteId) self.MqttManager.publish(topic=topic, payload=json.dumps(payload)) def deviceConnecting(self, uid: str) -> Optional[Device]: device = self.getDeviceByUID(uid) if not device: self.logWarning( f'A device with uid **{uid}** tried to connect but is unknown') return None if not device.connected: device.connected = True self.broadcast(method=constants.EVENT_DEVICE_CONNECTING, exceptions=[self.name], propagateToSkills=True) self.MqttManager.publish(constants.TOPIC_DEVICE_UPDATED, payload={ 'id': device.id, 'type': 'status' }) self._heartbeats[uid] = time.time() + 5 if not self._heartbeatsCheckTimer: self._heartbeatsCheckTimer = self.ThreadManager.newTimer( interval=3, func=self.checkHeartbeats) return device def deviceDisconnecting(self, uid: str): self._heartbeats.pop(uid, None) device = self.getDeviceByUID(uid) if not device: return if device.connected: self.logInfo(f'Device with uid **{uid}** disconnected') device.connected = False self.broadcast(method=constants.EVENT_DEVICE_DISCONNECTING, exceptions=[self.name], propagateToSkills=True) self.MqttManager.publish(constants.TOPIC_DEVICE_UPDATED, payload={ 'id': device.id, 'type': 'status' }) ## Heartbeats def onDeviceHeartbeat(self, uid: str, siteId: str = None): device = self.getDeviceByUID(uid=uid) if not device: self.logWarning(f'Device with uid **{uid}** does not exist') return device.connected = True self._heartbeats[uid] = time.time() def checkHeartbeats(self): now = time.time() for uid, lastTime in self._heartbeats.copy().items(): if now - self.getDeviceByUID( uid).getDeviceType().heartbeatRate > lastTime: self.logWarning( f'Device with uid **{uid}** has not given a signal since {self.getDeviceByUID(uid).getDeviceType().heartbeatRate} seconds or more' ) self._heartbeats.pop(uid) device = self.getDeviceByUID(uid) if device: device.connected = False self.MqttManager.publish(constants.TOPIC_DEVICE_UPDATED, payload={ 'id': device.id, 'type': 'status' }) self._heartbeatsCheckTimer = self.ThreadManager.newTimer( interval=3, func=self.checkHeartbeats) ## base def getDeviceTypeBySkillRAW(self, skill: str): # noinspection SqlResolve data = self.DatabaseManager.fetch( tableName=self.DB_TYPES, query='SELECT * FROM :__table__ WHERE skill = :skill', callerName=self.name, values={'skill': skill}, method='all') return { d['name']: { 'id': d['id'], 'name': d['name'], 'skill': d['skill'] } for d in data } def updateDeviceDisplay(self, device: dict): self.getDeviceById(device['id']).display = device['display'] self.DatabaseManager.update( tableName=self.DB_DEVICE, callerName=self.name, values={'display': json.dumps(device['display'])}, row=('id', device['id'])) def assertDeviceTypeAllowedAtLocation(self, typeId: int, locationId: int, moveDevice: bool = False): # check max allowed per Location deviceType = self.getDeviceType(typeId) # check if another instance of this device is allowed if deviceType.totalDeviceLimit > 0 and not moveDevice: currAmount = len( self.DeviceManager.getDevicesByTypeID(deviceTypeID=typeId)) if deviceType.totalDeviceLimit <= currAmount: raise MaxDeviceOfTypeReached( maxAmount=deviceType.totalDeviceLimit) # check if there are aleady too many of this device type in the location if deviceType.perLocationLimit > 0: currAmount = len( self.getDevicesByLocation(locationID=locationId, deviceTypeID=typeId)) if deviceType.perLocationLimit <= currAmount: raise MaxDevicePerLocationReached( maxAmount=deviceType.perLocationLimit) @property def deviceTypes(self) -> dict: return self._deviceTypes def getDevicesForSkill(self, skill: str): return [ device for device in self.devices.values() if device.skillName == skill ] def getMainDevice(self) -> Device: if not self._bufferedMainDevice: devices = self.DeviceManager.getDevicesForSkill('AliceCore') if len(devices) == 0: self.logWarning( f'No main device exists using DUMMY - RESTART REQUIRED!') values = { 'id': 0, 'name': 'dummy', 'typeID': 0, 'uid': self.ConfigManager.getAliceConfigByName('uuid'), 'locationID': 1 } self._bufferedMainDevice = Device(data=values) return self._bufferedMainDevice self._bufferedMainDevice = devices[0] if self._bufferedMainDevice.uid != self.ConfigManager.getAliceConfigByName( 'uuid'): self._bufferedMainDevice.pairingDone( self.ConfigManager.getAliceConfigByName('uuid')) raise Exception('UUID changed, restart required!') return self._bufferedMainDevice def getDevicesByLocation(self, locationID: int, deviceTypeID: int = None, connectedOnly: bool = False, withLinks: bool = True, pairedOnly: bool = False) -> List[Device]: if locationID and not isinstance(locationID, List): locationID = [locationID] if deviceTypeID and not isinstance(deviceTypeID, List): deviceTypeID = [deviceTypeID] return [ device for device in self._devices.values() #location: exact or link if ((locationID and device.locationID in locationID) or (withLinks and self.getLink(deviceId=device.id, locationId=locationID))) #check status and (not connectedOnly or device.connected) and (not pairedOnly or device.uid) #check deviceType and device.getDeviceType() and ( not deviceTypeID or device.deviceTypeID in deviceTypeID) ] def getDevicesByType(self, deviceType: str, connectedOnly: bool = False) -> List[Device]: deviceTypeObj = self.getDeviceTypeByName(deviceType) if deviceTypeObj: return [ x for x in self._devices.values() if x.deviceTypeID == deviceTypeObj.id and ( not connectedOnly or x.connected) ] return list() def getDevicesByTypeID(self, deviceTypeID: int, connectedOnly: bool = False) -> List[Device]: return [ x for x in self._devices.values() if x.deviceTypeID == deviceTypeID and ( not connectedOnly or x.connected) ] def getDeviceLinksByType(self, deviceType: int, connectedOnly: bool = False) -> List[DeviceLink]: return [ x for x in self._deviceLinks.values() if x.getDevice().deviceTypeID == deviceType and ( not connectedOnly or x.getDevice().connected) ] def getDeviceLinks(self, locationID: int, deviceTypeID: int = None, connectedOnly: bool = False, pairedOnly: bool = False) -> List[DeviceLink]: if locationID and not isinstance(locationID, List): locationID = [locationID] if deviceTypeID and not isinstance(deviceTypeID, List): deviceTypeID = [deviceTypeID] return [ x for x in self._deviceLinks.values() if (not locationID or x.locationId in locationID) and x.getDevice() and ( not deviceTypeID or x.getDevice().deviceTypeID in deviceTypeID) and (not connectedOnly or x.getDevice().connected) and ( not pairedOnly or x.getDevice().uid) ] def getDeviceLinksForSession(self, session: DialogSession, skill: str, noneIsEverywhere: bool = False): #get all relevant deviceTypes devTypes = self.DeviceManager.getDeviceTypesForSkill(skillName=skill) devTypeIds = [dev for dev in devTypes] # keys in dict are Ids #get all required locations locations = self.LocationManager.getLocationsForSession( sess=session, noneIsEverywhere=noneIsEverywhere) locationIds = [loc.id for loc in locations] return self.DeviceManager.getDeviceLinks(deviceTypeID=devTypeIds, locationID=locationIds) @staticmethod def groupDeviceLinksByDevice( links: List[DeviceLink]) -> Dict[int, DeviceLink]: # group links by device devGrouped = dict() for link in links: devGrouped.setdefault(link.deviceId, []).append(link) return devGrouped def getDeviceByUID(self, uid: str) -> Optional[Device]: return self._devices.get(self.devUIDtoID(uid), None) def getDeviceById(self, _id: int) -> Optional[Device]: return self._devices.get(_id, None) def getLinksForDevice(self, device: Device) -> List[DeviceLink]: return [ link for link in self._deviceLinks.values() if link.deviceId == device.id ] ## generic helper for finding a new USB device def findUSBPort(self, timeout: int) -> str: oldPorts = list() scanPresent = True found = False tries = 0 self.logInfo(f'Looking for USB device for the next {timeout} seconds') while not found: tries += 1 if tries > timeout * 2: break newPorts = list() for port, desc, hwid in sorted(list_ports.comports()): if scanPresent: oldPorts.append(port) newPorts.append(port) scanPresent = False if len(newPorts) < len(oldPorts): # User disconnected a device self.logInfo('USB device disconnected') oldPorts = list() scanPresent = True else: changes = [port for port in newPorts if port not in oldPorts] if changes: port = changes[0] self.logInfo(f'Found usb device on {port}') return port time.sleep(0.5) return '' def getAliceTypeDevices(self, connectedOnly: bool = False, includeMain: bool = False) -> List[Device]: #todo remove hard coded AliceSatellite. replace for example with some type of "device ability" -> "can broadcast" -> "can play sound" .. devices = self.DeviceManager.getDevicesByType( deviceType=self.SAT_TYPE, connectedOnly=connectedOnly) if includeMain: devices.append(self.DeviceManager.getMainDevice()) return devices def getAliceTypeDeviceTypeIds(self): return [ self.getMainDevice().deviceTypeID, self.getDeviceTypeByName(self.SAT_TYPE) ] def siteIdToDeviceName(self, siteId: str) -> str: device = self.DeviceManager.getDeviceByUID(uid=siteId) if device and device.name: return device.name elif device: return f'{device.getMainLocation().name} ({device.uid})' else: return siteId
def loadDevices(self): for row in self.databaseFetch(tableName=self.DB_DEVICE, method='all'): self._devices[row['id']] = Device(row)
def loadDevices(self): for row in self.databaseFetch(tableName='devices', query='SELECT * FROM :__table__', method='all'): self._devices[row['uid']] = Device(row)
def doFlashTasmota(self, device: Device, replyOnSiteId: str): port = self.DeviceManager.findUSBPort(timeout=60) if not port: if replyOnSiteId: self.MqttManager.say(text=self.TalkManager.randomTalk( 'noESPFound', skill='Tasmota'), client=replyOnSiteId) self._broadcastFlag.clear() return if replyOnSiteId: self.MqttManager.say(text=self.TalkManager.randomTalk( 'usbDeviceFound', skill='AliceCore'), client=replyOnSiteId) try: mac = ESPLoader.detect_chip(port=port, baud=115200).read_mac() mac = ':'.join([f'{x:02x}' for x in mac]) cmd = [ '--port', port, '--baud', '115200', '--after', 'no_reset', 'write_flash', '--flash_mode', 'dout', '0x00000', 'tasmota.bin', '--erase-all' ] esptool.main(cmd) except Exception as e: self.logError(f'Something went wrong flashing esp device: {e}') if replyOnSiteId: self.MqttManager.say(text=self.TalkManager.randomTalk( 'espFailed', skill='Tasmota'), client=replyOnSiteId) self._broadcastFlag.clear() return self.logInfo('Tasmota flash done') if replyOnSiteId: self.MqttManager.say(text=self.TalkManager.randomTalk( 'espFlashedUnplugReplug', skill='Tasmota'), client=replyOnSiteId) found = self.DeviceManager.findUSBPort(timeout=60) if found: if replyOnSiteId: self.MqttManager.say(text=self.TalkManager.randomTalk( 'espFoundReadyForConf', skill='Tasmota'), client=replyOnSiteId) time.sleep(10) uid = self.DeviceManager.getFreeUID(mac) tasmotaConfigs = TasmotaConfigs( deviceType=device.getDeviceType().ESPTYPE, uid=uid) confs = tasmotaConfigs.getBacklogConfigs( device.getMainLocation().getSaveName()) if not confs: self.logError( 'Something went wrong getting tasmota configuration') if replyOnSiteId: self.MqttManager.say(text=self.TalkManager.randomTalk( 'espFailed', skill='Tasmota'), client=replyOnSiteId) else: ser = serial.Serial() ser.baudrate = 115200 ser.port = port ser.open() try: for group in confs: command = ';'.join(group['cmds']) if len(group['cmds']) > 1: command = f'Backlog {command}' arr = list() if len(command) > 50: while len(command) > 50: arr.append(command[:50]) command = command[50:] arr.append(f'{command}\r\n') else: arr.append(f'{command}\r\n') for piece in arr: ser.write(piece.encode()) self.logInfo('Sent {}'.format( piece.replace('\r\n', ''))) time.sleep(0.5) time.sleep(group['waitAfter']) ser.close() self.logInfo('Tasmota flashing and configuring done') if replyOnSiteId: self.MqttManager.say(text=self.TalkManager.randomTalk( 'espFlashingDone', skill='Tasmota'), client=replyOnSiteId) # setting the uid marks the addition as complete device.pairingDone(uid=uid) self._broadcastFlag.clear() except Exception as e: self.logError( f'Something went wrong writting configuration to esp device: {e}' ) if replyOnSiteId: self.MqttManager.say(text=self.TalkManager.randomTalk( 'espFailed', skill='Tasmota'), client=replyOnSiteId) self._broadcastFlag.clear() ser.close() else: if replyOnSiteId: self.MqttManager.say(text=self.TalkManager.randomTalk( 'espFailed', skill='Tasmota'), client=replyOnSiteId) self._broadcastFlag.clear()