Exemple #1
0
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
Exemple #2
0
                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)
Exemple #3
0
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')
Exemple #4
0
    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 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
Exemple #7
0
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 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 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
Exemple #10
0
    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 playMorningAnnouncement(event):
    msg = getMorningAnnouncement()
    PE.logInfo(u"{} Saying: {}".format(LOG_PREFIX, msg))
    cast_manager.playMessage(msg)
    events.sendCommand(event.itemName, 'OFF')
def pauseMorningMusic(event):
    global inMorningSession
    if isInMorningTimeRange() and inMorningSession:
        PE.logInfo('{} Pausing morning music.'.format(LOG_PREFIX))
        cast_manager.pause()
        inMorningSession = False
def initializeZoneManager():
    '''
    Creates a new instance of ZoneManager and populate the zones.

    :rtype: ZoneManager
    '''
    zm = ZoneManager()
    zones = ZoneParser().parse(items, itemRegistry, zm._createImmutableInstance())

    # actions
    externalZoneActions = [
        AlertOnEntraceActivity(),
        SimulateDaytimePresence("http://hestia2.cdnstream.com:80/1277_192"),
        AlertOnExternalDoorLeftOpen(),
        ArmAfterFrontDoorClosed(12 * 60), # arm after 12'
    ]

    fanActions = [PlayMusicDuringShower("http://hestia2.cdnstream.com:80/1277_192")]

    switchActions = [TurnOnSwitch(), TurnOffAdjacentZones()]

    # add virtual devices and actions
    for z in zones:
        if z.getName() == 'Virtual':
            timeMap = {
                'wakeup': '6 - 9',
                'lunch': '12:00 - 13:30',
                'quiet' : '14:00 - 16:00, 20:00 - 22:59',
                'dinner': '17:50 - 20:00',
                'sleep': '23:00 - 7:00' 
            }
            z = z.addDevice(ActivityTimes(timeMap))

        if len(z.getDevicesByType(Switch)) > 0:
            for a in switchActions:
                z = z.addAction(a)

        if z.isExternal():
            for a in externalZoneActions:
                z = z.addAction(a)

        if len(z.getDevicesByType(AlarmPartition)) > 0:
            z = z.addAction(TurnOffDevicesOnAlarmModeChange())

        if len(z.getDevicesByType(HumiditySensor)) > 0 and not z.isExternal():
            z = z.addAction(AlertOnHumidityOutOfRange())

        if len(z.getDevicesByType(GasSensor)) > 0:
            z = z.addAction(AlertOnHighGasLevel())

        # add the play music action if zone has a fan switch.
        fans = z.getDevicesByType(Fan)
        if len(fans) > 0:
            for a in fanActions:
                z = z.addAction(a)

        zm.addZone(z)

    PE.logInfo("Configured ZoneManager with {} zones.".format(len(zones)))

    zones = zm.getZones()
    output = "{} zones".format(len(zones))
    for z in zones:
        output += '\n' + str(z)
    PE.logInfo(output)

    zm.startAutoReporWatchDog()

    return zm
Exemple #15
0
    def onAction(self, eventInfo):
        events = eventInfo.getEventDispatcher()
        zone = eventInfo.getZone()
        zoneManager = eventInfo.getZoneManager()

        isProcessed = False
        canTurnOffAdjacentZones = True
        lightOnTime = zone.isLightOnTime()
        zoneIlluminance = zone.getIlluminanceLevel()

        for switch in zone.getDevicesByType(Switch):
            if switch.isOn():
                switch.turnOn(events) # renew the timer if a switch is already on
                isProcessed = True
                canTurnOffAdjacentZones = False
                continue

            if not switch.canBeTriggeredByMotionSensor():
                # A special case: if a switch is configured not to be
                # triggered by a motion sensor, it means there is already 
                # another switch sharing that motion sensor. In this case, we
                # don't want to turn off the other switch.
                canTurnOffAdjacentZones = False
                if DEBUG:
                    PE.logInfo("{}: rejected - can't be triggerred by motion sensor".format(
                            switch.getItemName()))

                continue

            # Break if switch was just turned off.
            if None != switch.getLastOffTimestampInSeconds():
                if (time.time() - switch.getLastOffTimestampInSeconds()) <= \
                    TurnOnSwitch.DELAY_AFTER_LAST_OFF_TIME_IN_SECONDS:
                    if DEBUG:
                        PE.logInfo("{}: rejected - switch was just turned off".format(
                            switch.getItemName()))
                    continue

            # Break if the switch of a neighbor sharing the motion sensor was
            # just turned off.
            openSpaceZones = [zoneManager.getZoneById(n.getZoneId()) \
                for n in zone.getNeighbors() if n.isOpenSpace()]
            sharedMotionSensorZones = [z for z in openSpaceZones 
                if zone.shareSensorWith(z, MotionSensor)]
            theirSwitches = reduce(lambda a, b : a + b,
                    [z.getDevicesByType(Switch) for z in sharedMotionSensorZones],
                    [])
            if any(time.time() - s.getLastOffTimestampInSeconds() <= \
                        TurnOnSwitch.DELAY_AFTER_LAST_OFF_TIME_IN_SECONDS \
                    for s in theirSwitches):
                if DEBUG:
                    PE.logInfo("{}: rejected - can't be triggerred by motion sensor".format(
                            switch.getItemName()))
                continue

            if isinstance(switch, Light):
                if lightOnTime or switch.isLowIlluminance(zoneIlluminance):
                    isProcessed = True
                    
                if isProcessed and None != zoneManager:
                    masterZones = [zoneManager.getZoneById(n.getZoneId()) \
                        for n in zone.getNeighbors() \
                        if NeighborType.OPEN_SPACE_MASTER == n.getType()]
                    if any(z.isLightOn() for z in masterZones):
                        isProcessed = False

                        # This scenario indicates that there is already 
                        # activity in the master zone, and thus such activity
                        # must not prematurely turns off the light in the
                        # adjacent zone.
                        canTurnOffAdjacentZones = False

                        if DEBUG:
                            PE.logInfo("{}: rejected - a master zone's light is on".format(
                                    switch.getItemName()))

                if isProcessed:
                    switch.turnOn(events)
            else:
                switch.turnOn(events)
                isProcessed = True

        # Now shut off the light in any shared space zones
        if canTurnOffAdjacentZones:
            if DEBUG:
                PE.logInfo("{}: turning off adjancent zone's light".format(
                        switch.getItemName()))
            offEventInfo = EventInfo(ZoneEvent.SWITCH_TURNED_ON,
                    eventInfo.getItem(), eventInfo.getZone(),
                    eventInfo.getZoneManager(), eventInfo.getEventDispatcher())
            TurnOffAdjacentZones().onAction(offEventInfo)
        
        return isProcessed