def playStream(name, casts=CASTS, volume=None): ''' Play the given stream url. :param str name: see _STREAMS :param list(ChromeCast) casts: :return: boolean True if success; False if stream name is invalid. ''' if None != volume and (volume < 0 or volume > 100): raise ValueError('volume must be between 0 and 100') url = getStreamUrl(name) if None != url: for cast in casts: if None != volume: scope.events.sendCommand(cast.getVolumeName(), str(volume)) if url == cast.getStreamUrl(): resume([cast]) else: Audio.playStream(cast.getSinkName(), url) cast.setStream(name, url) return True else: PE.logInfo("Missing stream URL for '{0}'".format(name)) return False
def armAndSendAlert(): # Don't know why list comprehension version like below # doesn't work, Jython just hang on this statement: # if not any(z.isOccupied(elapsedTime) for z in zoneManager.getZones()): # Below is a work around. occupied = False activeDevice = None for z in zoneManager.getZones(): if z.isExternal(): continue # motion sensor switches off after around 3', need to # that into account. motionDelayInSec = 3 * 60 delayTimeInSec = self.maxElapsedTimeInSeconds + motionDelayInSec (occupied, activeDevice) = z.isOccupied([], delayTimeInSec) if occupied: break if occupied: PE.logInfo( 'Auto-arm cancelled (activities detected @ {}).'. format(activeDevice)) else: securityPartitions[0].armAway(events) msg = 'The house has been automatically armed-away (front door closed and no activity)' alert = Alert.createWarningAlert(msg) AlertManager.processAlert(alert, zoneManager)
def alertIfRainInShortermForecast(event): forecasts = EnvCanada.retrieveHourlyForecast('Ottawa', 12) rainPeriods = [f for f in forecasts if \ 'High' == f.getPrecipationProbability() or \ 'Medium' == f.getPrecipationProbability()] if len(rainPeriods) > 0: if len(rainPeriods) == 1: subject = u"Possible precipation at {}".format( rainPeriods[0].getUserFriendlyForecastTime()) else: subject = u"Possible precipation from {} to {}".format( rainPeriods[0].getUserFriendlyForecastTime(), rainPeriods[-1].getUserFriendlyForecastTime()) body = u'Forecasts:\n' body += u"{:5} {:7} {:25} {:6} {:6}\n".format('Hour: ', 'Celsius', 'Condition', 'Prob.', 'Wind') for f in forecasts: body += unicode(f) + '\n' alert = Alert.createInfoAlert(subject, body) result = AlertManager.processAlert(alert, zm) if not result: PE.logInfo('Failed to send rain alert')
def sendAlert(event): json = items[_ALERT_ITEM_NAME].toString() alert = Alert.fromJson(json) if AlertManager.processAlert(alert, zm): return True else: PE.logError('Failed to send alert {}'.format(alert.toString())) return False
def onDoorOrWindowsChanged(event): triggeringItem = itemRegistry.getItem(event.itemName) if PE.isInStateOn(triggeringItem.getState()) \ or PE.isInStateOpen(triggeringItem.getState()): dispatchEvent(ZoneEvent.CONTACT_OPEN, event) else: dispatchEvent(ZoneEvent.CONTACT_CLOSED, event)
def dispatchEvent(zoneEvent, event, enforceItemInZone = True): ''' Dispatches an event to the ZoneManager. If the event is not processed, create a debug log. ''' triggeringItem = itemRegistry.getItem(event.itemName) if not _mutableZoneManager.dispatchEvent(zoneEvent, events, triggeringItem, enforceItemInZone): PE.logDebug('Event {} for item {} is not processed.'.format(zoneEvent, event.itemName))
def onSwitchIsChanged(event): triggeringItem = itemRegistry.getItem(event.itemName) if switch_manager.isSwitchOn(triggeringItem): if not _mutableZoneManager.onSwitchTurnedOn(events, triggeringItem): PE.logDebug('Switch on event for {} is not processed.'.format( event.itemName)) else: if not _mutableZoneManager.onSwitchTurnedOff(events, triggeringItem): PE.logDebug('Switch off event for {} is not processed.'.format( event.itemName))
def startAutoReporWatchDog(self, timerIntervalInSeconds=10 * 60, inactiveIntervalInSeconds=10 * 60): ''' Starts a timer that run every timerIntervalInSeconds. When the timer is triggered, it will scan auto-report devices (Devices::isAutoReport), and if any of them hasn't been triggered in the last inactiveIntervalInSeconds, it will reset the item value. This method is safe to call multiple times (a new timer will be started and any old timer is cancelled). :param int timerIntervalInSeconds: the timer duration :param int inactiveIntervalInSeconds: the inactive duration after which the device's value will be reset. :rtype: None ''' def resetFailedAutoReportDevices(): devices = [] for z in self.getZones(): [devices.append(d) for d in z.getDevices() \ if d.isAutoReport() and \ not d.wasRecentlyActivated(inactiveIntervalInSeconds)] if len(devices) > 0: itemNames = [] for d in devices: itemNames.append(d.getItemName()) d.resetValueStates() PE.logWarning( "AutoReport Watchdog: {} failed auto-report devices: {}". format(len(devices), itemNames)) else: PE.logDebug( "AutoReport Watchdog: no failed auto-report devices") # restart the timer self.autoReportWatchDogTimer = Timer(timerIntervalInSeconds, resetFailedAutoReportDevices) self.autoReportWatchDogTimer.start() if None != self.autoReportWatchDogTimer \ and self.autoReportWatchDogTimer.isAlive(): self.autoReportWatchDogTimer.cancel() self.autoReportWatchDogTimer = None self.autoReportWatchDogTimer = Timer(timerIntervalInSeconds, resetFailedAutoReportDevices) self.autoReportWatchDogTimer.start() PE.logInfo("Started auto-report watchdog timer.")
def turnOffSwitch(): zone = self.getZoneManager().getContainingZone(self) (occupied, device) = zone.isOccupied([Fan, Light], 60) if not occupied: events.sendCommand(self.getItemName(), "OFF") PE.logDebug("{}: turning off {}.".format( zone.getName(), self.getItemName())) else: self.timer = Timer(self.durationInMinutes * 60, turnOffSwitch) self.timer.start() PE.logDebug("{}: {} is in use by {}.".format( zone.getName(), self.getItemName(), device))
def playAnnouncementAndMusicInTheMorning(event): global morningMusicStartCount global inMorningSession if isInMorningTimeRange() and \ morningMusicStartCount < MAX_MORNING_MUSIC_START_COUNT: if not inMorningSession: PE.logInfo('{} Playing morning annoucement.'.format(LOG_PREFIX)) inMorningSession = True msg = getMorningAnnouncement() casts = cast_manager.getFirstFloorCasts() cast_manager.playMessage(msg, casts) cast_manager.playStream("WWFM Classical", casts, 35) morningMusicStartCount += 1
def onAction(self, eventInfo): zone = eventInfo.getZone() zoneManager = eventInfo.getZoneManager() securityPartitions = zoneManager.getDevicesByType(AlarmPartition) if len(securityPartitions) == 0: return False if not securityPartitions[0].isArmedAway(): return False # Get an audio sink from the first floor. audioSink = None zones = [ z for z in zoneManager.getZones() if z.getLevel() == Level.FIRST_FLOOR ] for z in zones: sinks = z.getDevicesByType(ChromeCastAudioSink) if len(sinks) > 0: audioSink = sinks[0] break if None == audioSink: return False activities = zoneManager.getDevicesByType(ActivityTimes) if len(activities) > 0: if activities[0].isSleepTime(): return False audioSink.playStream(self.musicUrl, self.musicVolume) if None != self.timer: self.timer.cancel() durationInSeconds = self.playDurationInSeconds if None == durationInSeconds: durationInSeconds = random.randint(3 * 60, 10 * 60) self.timer = Timer(durationInSeconds, lambda: audioSink.pause()) self.timer.start() PE.logInfo( "Simulate daytime presence by playing music for {} seconds".format( durationInSeconds)) return True
def isTriggered(self): ''' :return: true if the gas sensor has detected a high level of concentration :rtype: bool ''' return PE.isInStateOn(self.stateItem.getState())
def _checkAndSendAlert(zoneManager, checkFunction, deviceTypeString, thresholdInSeconds): inactiveDevices = checkFunction(zm, thresholdInSeconds) if len(inactiveDevices) > 0: subject = "{} inactive {} devices".format( len(inactiveDevices), deviceTypeString) body = "The following devices haven't triggered "\ "in the last {} hours\r\n - ".format( thresholdInSeconds / 3600) body += "\r\n - ".join(inactiveDevices) alert = Alert.createInfoAlert(subject, body) if not AlertManager.processAdminAlert(alert): PE.logInfo('Failed to send inactive {} device alert'.format(deviceTypeString)) else: PE.logInfo("No inactive {} devices detected.".format(deviceTypeString))
def __init__(self, prefix, sinkName): ''' Ctor :param str prefix: the item name prefix, as defined in the .item file. By\ convention, other channels of this device will have this naming \ pattern: {prefix}Idling, {prefix}Title, {prefix}Player, and so on. :param str sinkName: the sink name for voice and audio play. The sink \ name can be retrieved by running "openhab-cli console" and then \ "smarthome:audio sinks". :raise ValueError: if any parameter is invalid ''' Device.__init__(self, PE.createStringItem( 'Chromecast-{}-{}'.format(prefix, sinkName))) self.sinkName = sinkName self.prefix = prefix self.streamUrl = None self.lastTtsMessage = None self._testMode = False self._testLastCommand = None self._lastCommandTimestamp = time.time() self._lastCommand = None
def testOnSwitchTurnedOff_withNonApplicableZone_returnsFalse(self): zone = Zone('ff', [self.light, self.motionSensor]) self.zm.addZone(zone) self.assertFalse( self.zm.onSwitchTurnedOff(scope.events, PE.createStringItem(INVALID_ITEM_NAME)))
def testOnMotionSensorTurnedOn_withNonApplicableZone_returnsFalse(self): zone = Zone('ff', [self.light, self.motionSensor]) self.zm.addZone(zone) self.assertFalse( self.zm.dispatchEvent(ZoneEvent.MOTION, scope.events, PE.createStringItem(INVALID_ITEM_NAME)))
def getIlluminanceLevel(self): ''' Returns an positive integer representing the LUX value. ''' if PE.isStateAvailable(self.getItem().getState()): return self.getItem().getState().intValue() else: return 0
def processAlert(alert, zoneManager=None): ''' Processes the provided alert. If the alert's level is WARNING or CRITICAL, the TTS subject will be played on the ChromeCasts. :param Alert alert: the alert to be processed :param ImmutableZoneManager zoneManager: used to retrieve the ActivityTimes :return: True if alert was processed; False otherwise. :raise: ValueError if alert is None ''' if None == alert: raise ValueError('Invalid alert.') PE.logInfo(u"Processing alert\n{}".format(alert.toString())) if AlertManager._isThrottled(alert): return False if not alert.isAudioAlertOnly(): AlertManager._emailAlert(alert, AlertManager._getOwnerEmailAddresses()) # Play an audio message if the alert is warning or critical. # Determine the volume based on the current zone activity. volume = 0 if alert.isCriticalLevel(): volume = 60 elif alert.isWarningLevel(): if None == zoneManager: volume = 60 else: activity = zoneManager.getDevicesByType(ActivityTimes)[0] if activity.isSleepTime(): volume = 0 elif activity.isQuietTime(): volume = 40 else: volume = 60 if volume > 0: casts = cast_manager.getAllCasts() cast_manager.playMessage(alert.getSubject(), casts, volume) return True
def getWattage(self): ''' :return: the current wattage of the plug :rtype: int or None if the plug has no power reading ''' if None == self.powerReadingItem: raise ValueError("Plug has no power reading capability") return PE.getIntegerStateValue(self.powerReadingItem, 0)
def onAction(self, eventInfo): zone = eventInfo.getZone() zoneManager = eventInfo.getZoneManager() currentEpoch = time.time() doorOpenPeriodInSeconds = 10 for door in zone.getDevicesByType(Door): if door.wasRecentlyActivated(doorOpenPeriodInSeconds): PE.logInfo( "A door was just open for zone {}; ignore motion event.". format(zone.getName())) return cameras = zone.getDevicesByType(Camera) if len(cameras) == 0: PE.logInfo("No camera found for zone {}".format(zone.getName())) return camera = cameras[0] if not camera.hasMotionEvent(): PE.logInfo( "Camera doesn't indicate motion event; likely a false positive PIR event." ) return time.sleep(10) # wait for a bit to retrieve more images offsetSeconds = 5 maxNumberOfSeconds = 15 attachmentUrls = camera.getSnapshotUrls(currentEpoch, maxNumberOfSeconds, offsetSeconds) if len(attachmentUrls) > 0: timeStruct = time.localtime() hour = timeStruct[3] msg = 'Activity detected at the {} area.'.format( zone.getName(), len(attachmentUrls)) if SM.isArmedAway(zm) or hour <= 6: alert = Alert.createWarningAlert(msg, None, attachmentUrls) else: alert = Alert.createAudioWarningAlert(msg) AlertManager.processAlert(alert) return True else: PE.logInfo("No images from {} camera.".format(zone.getName())) return False
def processAdminAlert(alert): ''' Processes the provided alert by sending an email to the administrator. :param Alert alert: the alert to be processed :return: True if alert was processed; False otherwise. :raise: ValueError if alert is None ''' if None == alert: raise ValueError('Invalid alert.') PE.logInfo(u"Processing admin alert\n{}".format(alert.toString())) if AlertManager._isThrottled(alert): return False AlertManager._emailAlert(alert, AlertManager._getAdminEmailAddresses()) return True
def retrieveSnapshots(itemPrefix, snapshotCount): ''' Retrieve the supplied number of snapshots. :param str itemPrefix: the camera item prefix; the items Image and\ UpdateImage are created from the prefix. :param int snapshotCount: the # of snapshot images to retrieve :return: list of snapshot URLs :rtype: list(str) ''' attachmentUrls = [] imageItemName = itemPrefix + '_Image' updateItemName = itemPrefix + '_UpdateImage' PE.logInfo('Retrieving {} snapshots'.format(snapshotCount)) previousRawBytes = [] for idx in range(snapshotCount): # Flip the state of the update channel to force retrieval of new image if scope.items[updateItemName] == scope.OnOffType.ON: scope.events.sendCommand(updateItemName, "OFF") else: scope.events.sendCommand(updateItemName, "ON") time.sleep(_WAIT_TIME_AFTER_FORCE_IMAGE_UPDATE_IN_SECONDS) imageState = scope.items[imageItemName] if scope.UnDefType.UNDEF != imageState and scope.UnDefType.NULL != imageState: rawBytes = imageState.getBytes() if rawBytes != previousRawBytes: fileName = '{}/{}-{}.jpg'.format(_SNAPSHOT_PATH, itemPrefix, idx) file = io.open(fileName, 'wb') file.write(rawBytes) file.close() attachmentUrls.append('file://' + fileName) previousRawBytes = rawBytes return attachmentUrls
def resume(self): ''' Resumes playing. ''' if self._testMode: self._testLastCommand = 'resume' return if PE.isInStateOn(scope.items[self.getIdleItemName()]): Audio.playStream(self.getSinkName(), self.getStreamUrl()) else: Audio.playStream(self.getSinkName(), self.getStreamUrl()) scope.events.sendCommand(self.getPlayerName(), "PLAY")
def resetFailedAutoReportDevices(): devices = [] for z in self.getZones(): [devices.append(d) for d in z.getDevices() \ if d.isAutoReport() and \ not d.wasRecentlyActivated(inactiveIntervalInSeconds)] if len(devices) > 0: itemNames = [] for d in devices: itemNames.append(d.getItemName()) d.resetValueStates() PE.logWarning( "AutoReport Watchdog: {} failed auto-report devices: {}". format(len(devices), itemNames)) else: PE.logDebug( "AutoReport Watchdog: no failed auto-report devices") # restart the timer self.autoReportWatchDogTimer = Timer(timerIntervalInSeconds, resetFailedAutoReportDevices) self.autoReportWatchDogTimer.start()
def __init__(self, timeRangeMap): ''' Ctor :param dictionary timeRangeMap: a map from activity string to time \ range string \ The supported activities are 'lunch', 'dinner', 'sleep', 'quiet', \ 'wakeup'. A time range string can be a single or multiple \ ranges in the 24-hour format.\ Example: '10-12', or '6-9, 7-7, 8:30 - 14:45', or '19 - 8' \ (wrap around) :raise ValueError: if any parameter is invalid ''' Device.__init__(self, PE.createStringItem('ActivityTimesItem')) acceptableKeys = ['lunch', 'dinner', 'sleep', 'quiet', 'wakeup'] for key in timeRangeMap.keys(): if key not in acceptableKeys: raise ValueError('Invalid time range key {}'.format(key)) self.timeRangeMap = timeRangeMap
def isOn(self): ''' Returns true if the motion sensor's state is on; false otherwise. ''' return PE.isInStateOn(self.getItem().getState())
self.assertFalse(self.light.isLowIlluminance(-1)) def testIsLowIlluminance_currentIlluminanceAboveThreshold_returnsFalse( self): self.light = Light(self.lightItem, 10, 50) self.assertFalse(self.light.isLowIlluminance(60)) def testIsLowIlluminance_currentIlluminanceBelowThreshold_returnsTrue( self): self.light = Light(self.lightItem, 10, 50) self.assertTrue(self.light.isLowIlluminance(10)) def testTimerTurnedOff_validParams_switchIsOff(self): zm = ZoneManager() self.light = Light(self.lightItem, 0.004) # makes it 0.24 sec self.light = self.light.setZoneManager(zm._createImmutableInstance()) zone = Zone('ff', [self.light]) zm.addZone(zone) self.lightItem.setState(scope.OnOffType.ON) self.light._startTimer(MockedEventDispatcher(scope.itemRegistry)) self.assertTrue(self.light._isTimerActive()) time.sleep(0.3) self.assertFalse(self.light._isTimerActive()) self.assertFalse(self.light.isOn()) PE.runUnitTest(LightTest)
def isOn(self): ''' Returns true if the TV is on; false otherwise. ''' return PE.isInStateOn(self.getItem().getState())
def isInVacation(items): ''' :param scope.items items: :return: True if the house is set to vacation mode. ''' return PE.isInStateOn(items['VT_In_Vacation'])
def isOn(self): ''' :return: True if the partition is in alarm; False otherwise :rtype: bool ''' return PE.isInStateOn(self.getItem().getState())