예제 #1
0
	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
예제 #2
0
 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'
예제 #3
0
	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))
예제 #4
0
    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')
예제 #5
0
    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']]
예제 #6
0
	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'
예제 #7
0
    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'
예제 #8
0
	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
예제 #9
0
	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')
예제 #10
0
    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
예제 #11
0
    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
예제 #12
0
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
예제 #13
0
 def loadDevices(self):
     for row in self.databaseFetch(tableName=self.DB_DEVICE, method='all'):
         self._devices[row['id']] = Device(row)
예제 #14
0
	def loadDevices(self):
		for row in self.databaseFetch(tableName='devices', query='SELECT * FROM :__table__', method='all'):
			self._devices[row['uid']] = Device(row)
예제 #15
0
    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()