Exemplo n.º 1
0
class WeatherStation(WoTThing):
    def __init__(self, config):
        super(WeatherStation, self).__init__(config, "my weatherstation",
                                             "thing", "my weather station")
        # initialize the weather station
        seed(100)

    async def get_weather_data(self):
        # do whatever it takes to get current temperature, pressure and wind speed
        self.temperature = randint(40, 80)
        self.barometic_pressure = randint(290, 310) / 10.0
        self.wind_speed = randint(0, 25)
        logging.debug('new values fetched: %s, %s, %s', self.temperature,
                      self.barometric_pressure, self.wind_speed)

    temperature = WoTThing.wot_property(name='temperature',
                                        initial_value=0.0,
                                        description='the temperature in ℉',
                                        value_source_fn=get_weather_data,
                                        units='℉')
    barometric_pressure = WoTThing.wot_property(
        name='barometric_pressure',
        initial_value=30.0,
        description='the air pressure in inches',
        units='in')
    wind_speed = WoTThing.wot_property(name='wind_speed',
                                       initial_value=30.0,
                                       description='the wind speed in mph',
                                       units='mph')
Exemplo n.º 2
0
class TwilioSMS(WoTThing):
    required_config = Namespace()
    required_config.add_option(
        'twilio_account_sid',
        doc='the Twilio Account SID',
        default="NOT A REAL SID",
    )
    required_config.add_option(
        'twilio_auth_token',
        doc='the Twilio Auth Token',
        default="NOT A REAL AUTH TOKEN",
    )
    required_config.add_option(
        'from_number',
        doc="the user's Twilio phone number (+1XXXYYYZZZZ)",
        default="+1XXXYYYZZZZ",
    )

    def __init__(self, config):
        super(TwilioSMS, self).__init__(config, "Twilio SMS", "thing",
                                        "a gateway for sending SMS")

    def set_to_number(self, to_number):
        self.send_twilio_sms(to_number, None)

    def set_message(self, message):
        self.send_twilio_sms(None, message)

    def send_twilio_sms(self, to_number, message):
        #         logging.debug('args %s, kwargs: %s', args, kwargs)
        if to_number is not None:
            self.to_number = to_number
        if message is not None:
            self.message = message
        if self.to_number is None or self.message is None:
            logging.debug('NOT READY: to_number: %s, message: %s',
                          self.to_number, self.message)
            return
        logging.debug('sending SMS')
        client = Client(self.config.twilio_account_sid,
                        self.config.twilio_auth_token)
        message = client.messages.create(body=self.message,
                                         from_=self.config.from_number,
                                         to=self.to_number)
        self.to_number = None
        self.message = None

    to_number = WoTThing.wot_property(name='to_number',
                                      initial_value=None,
                                      type='string',
                                      description='the number to send to',
                                      value_forwarder=set_to_number)
    message = WoTThing.wot_property(name='message',
                                    initial_value=None,
                                    type='string',
                                    description='the body of the SMS message',
                                    value_forwarder=set_message)
Exemplo n.º 3
0
class ExampleDimmableLight(WoTThing):

    def __init__(self, config, lamp_hardware):
        super(ExampleDimmableLight, self).__init__(
            config,
            'My Lamp',
            'dimmableLight',
            'A web connected lamp'
        )
        self._lamp_hardware = lamp_hardware

    async def _get_illumination_state(self):
        """this method will be run at a configurable interval to poll for changes of state
        of the lamp.  For example, to detect if a meddlesome child were to be randomly
        turning the light on/off and adjusting the brightness independently of this
        program."""
        self.on = self._lamp_hardware.get_lamp_state()
        self.level = self._lamp_hardware.get_lamp_level()

    def _set_hardware_illumination_state(self, boolean_value):
        # do whatever it takes to set the state of the lamp by
        # talking to the hardware
        self._lamp_hardware.set_lamp_state(boolean_value)

    def _set_hardware_level(self, new_level):
        # do whatever it takes to change the level of the lamp by
        # talking to the hardware
        self._lamp_hardware.set_lamp_level(new_level)

    on = WoTThing.wot_property(
        name='on',
        initial_value=True,
        description="is the light illuminated?",
        value_source_fn=_get_illumination_state,
        value_forwarder=_set_hardware_illumination_state
    )
    level = WoTThing.wot_property(
        name='level',
        initial_value=0,
        description="lamp brightness level",
        value_forwarder=_set_hardware_level,
        minimum=0,
        maximum=100
    )
Exemplo n.º 4
0
class BitcoinTrend(WoTThing):
    required_config = Namespace()
    required_config.add_option(
        'name',
        doc='the name of this Bitcoin Trend Monitor',
        default="bitcoin trend",
    )
    required_config.add_option(
        'target_url',
        doc='the URL for json data',
        default="https://api.coindesk.com/v1/bpi/currentprice/USD.json")
    required_config.add_option(
        'seconds_for_timeout',
        doc='the number of seconds to allow for fetching bitcoin data',
        default=10)

    @staticmethod
    def sign(x):
        if x < 0:
            return -1
        if x > 0:
            return 1
        return 0

    def __init__(self, config):
        super(BitcoinTrend, self).__init__(config, config.name, "thing",
                                           "my bitcoin trend monitor")
        self.previous_value = 0

    async def get_bitcoin_value(self):
        async with aiohttp.ClientSession() as session:
            async with async_timeout.timeout(self.config.seconds_for_timeout):
                async with session.get(self.config.target_url) as response:
                    self.bitcoin_data = json.loads(await response.text())
        current_observation = self.bitcoin_data['bpi']['USD']['rate_float']
        self.trend = self.sign(current_observation - self.previous_value)
        self.previous_value = current_observation
        logging.debug(
            'new value fetched: %s, trend: %s',
            current_observation,
            self.trend,
        )

    trend = WoTThing.wot_property(
        name='trend',
        initial_value=0,
        description='the trend positive or negative',
        value_source_fn=get_bitcoin_value,
    )
Exemplo n.º 5
0
class Thumper(WoTThing):
    def __init__(self, config):
        super(Thumper, self).__init__(config, "the thumper", "thing",
                                      "a thumper")

    async def get_next_value(self):
        self.thump = not self.thump
        logging.debug('fetched new value: %s', self.thump)

    thump = WoTThing.wot_property(
        name='thump',
        initial_value=True,
        description='thump',
        value_source_fn=get_next_value,
    )
Exemplo n.º 6
0
class SceneThing(WoTThing):
    required_config = Namespace()
    required_config.add_option(
        'all_things_url',
        doc='a URL for fetching all things data',
        default="http://gateway.local/things",
    )
    required_config.add_option(
        'thing_state_url_template',
        doc='a URL for fetching the current state of a thing',
        default="http://gateway.local/things/{}/properties/{}",
    )
    required_config.add_option(
        'seconds_for_timeout',
        doc='the number of seconds to allow for fetching enphase data',
        default=10)

    def __init__(self, config):
        super(SceneThing, self).__init__(config, "Scene Thing", "thing",
                                         "A controller for scenes")
        self.state_file_name = '{}.json'.format(self.name)
        try:
            with open(self.state_file_name) as state_file:
                self.participants = json.load(state_file)
        except FileNotFoundError:
            logging.info('no scene state file found for %s',
                         self.state_file_name)
            self.participants = {}
        except json.decoder.JSONDecodeError:
            logging.info('bad file format for %s', self.state_file_name)
            self.participants = {}

        self.listeners = []
        self.preserved_state = {}

    on_off = WoTThing.wot_property(
        name='on',
        initial_value=False,
        description='on/off status',
        value_forwarder=scene_on_off,
    )
    learn = WoTThing.wot_property(
        name='learn',
        initial_value=False,
        description='learn mode',
        value_forwarder=learn_on_off,
    )

    async def get_all_things(self):
        async with aiohttp.ClientSession() as session:
            async with async_timeout.timeout(self.config.seconds_for_timeout):
                async with session.get(
                        self.config.all_things_url,
                        headers={
                            'Accept':
                            'application/json',
                            'Authorization':
                            'Bearer {}'.format(
                                self.config.things_gateway_auth_key),
                            'Content-Type':
                            'application/json'
                        }) as response:
                    all_things = json.loads(await response.text())
                    print(json.dumps(all_things))
                    return all_things

    @staticmethod
    def quote_strings(a_value):
        if isinstance(a_value, str):
            return '"{}"'.format(a_value)
        return a_value

    async def change_property(self, a_thing_id, a_property, a_value):
        while True:
            try:
                async with aiohttp.ClientSession() as session:
                    async with async_timeout.timeout(
                            self.config.seconds_for_timeout):
                        async with session.put(
                                "http://gateway.local/things/{}/properties/{}/"
                                .format(a_thing_id, a_property),
                                headers={
                                    'Accept':
                                    'application/json',
                                    'Authorization':
                                    'Bearer {}'.format(
                                        self.config.things_gateway_auth_key),
                                    'Content-Type':
                                    'application/json'
                                },
                                data='{{"{}": {}}}'.format(
                                    a_property,
                                    str(self.quote_strings(
                                        a_value)).lower())) as response:
                            logging.debug(
                                'change_property: sent %s to %s',
                                '{{"{}": {}}}'.format(
                                    a_property,
                                    str(self.quote_strings(a_value)).lower()),
                                a_thing_id)
                            return await response.text()
            except aiohttp.client_exceptions.ClientConnectorError as e:
                logging.error(
                    'change_property: problem contacting http:/gateway.local: {}'
                    .format(e))
                logging.info('change_property: retrying after 20 second pause')
                await asyncio.sleep(20.0)

    async def change_properties_from_a_change_set(self, a_thing_id,
                                                  a_change_set):
        # it seems that some devices cannot have properties changed if they are
        # on in their 'on' state.  If the change_set contains a property to turn
        # the thing on, do that first.
        if 'on' in a_change_set and a_change_set['on'] is True:
            await self.change_property(a_thing_id, 'on', True)
        await asyncio.gather(
            *(self.change_property(a_thing_id, a_property, a_value)
              for a_property, a_value in a_change_set.items()
              if a_property != 'on'))
        # if the change_set has an 'on' property to turn something off, ensure that
        # turning the thing off is the last thing done.
        if 'on' in a_change_set and a_change_set['on'] is False:
            await self.change_property(a_thing_id, 'on', False)

    async def monitor_state(self, a_thing_id):
        async with websockets.connect(
                'ws://gateway.local/things/{}?jwt={}'.format(
                    a_thing_id,
                    self.config.things_gateway_auth_key), ) as websocket:
            async for message in websocket:
                raw = json.loads(message)
                if raw['messageType'] == 'propertyStatus':
                    if a_thing_id in self.participants:
                        self.participants[a_thing_id].update(raw["data"])
                    else:
                        self.participants[a_thing_id] = raw["data"]

    async def learn_changes(self):
        # connect to Things Gateway and create listener for every object and property
        all_things = await self.get_all_things()
        for a_thing in all_things:
            if a_thing['name'] == self.name:
                logging.debug('skipping thing %s', a_thing['name'])
                continue
            a_thing_id = a_thing['href'].replace('/things/', '')
            self.listeners.append(
                asyncio.ensure_future(self.monitor_state(a_thing_id)))

    async def stop_learning(self):
        # close all listeners
        asyncio.gather(*self.listeners, return_exceptions=True).cancel()
        logging.info('stop_learing:  this is what I learned:')
        for a_thing_id, a_change_set in self.participants.items():
            logging.info('    {}: {}'.format(a_thing_id, a_change_set))
        with open(self.state_file_name, 'w') as state_file:
            json.dump(self.participants, state_file)

    async def capture_current_state(self, a_thing_id, a_change_set):
        self.preserved_state[a_thing_id] = {}
        for a_property in a_change_set.keys():
            # as of 0.4, the 'properties' resource has not been implemented in the Things API
            # this means that rather than fetching all the properties for a thing in one call
            # we've got to do it one property at a time.
            async with aiohttp.ClientSession() as session:
                async with async_timeout.timeout(
                        self.config.seconds_for_timeout):
                    logging.debug(
                        self.config.thing_state_url_template.format(
                            a_thing_id, a_property))
                    async with session.get(
                            self.config.thing_state_url_template.format(
                                a_thing_id, a_property),
                            headers={
                                'Accept':
                                'application/json',
                                'Authorization':
                                'Bearer {}'.format(
                                    self.config.things_gateway_auth_key),
                                'Content-Type':
                                'application/json'
                            }) as response:
                        state_snapshot = json.loads(await response.text())
                        self.preserved_state[a_thing_id].update(state_snapshot)

    async def turn_on_participants(self):
        if self.learn == ON:
            self.learn = OFF
        # go through participants, capturing their current state
        self.preserved_state = {}
        await asyncio.gather(
            *(self.capture_current_state(a_thing, a_change_set)
              for a_thing, a_change_set in self.participants.items()))
        # go through participtants setting their state
        logging.debug('start turn_on_participants')
        await asyncio.gather(
            *(self.change_properties_from_a_change_set(a_thing, change_set)
              for a_thing, change_set in self.participants.items()))

    async def restore_participants(self):
        # go through participants and turn off
        if self.learn == ON:
            self.learn = OFF
        await asyncio.gather(
            *(self.change_properties_from_a_change_set(a_thing, change_set)
              for a_thing, change_set in self.preserved_state.items()))
Exemplo n.º 7
0
class WeatherStation(WoTThing):
    required_config = Namespace()
    required_config.add_option(
        'weather_underground_api_key',
        doc='the api key to access Weather Underground data',
        short_form="K",
        default="not a real key")
    required_config.add_option(
        'state_code',
        doc='the two letter state code',
        default="OR",
    )
    required_config.add_option(
        'city_name',
        doc='the name of the city',
        default="Corvallis",
    )
    required_config.add_option(
        'name',
        doc='the name of this weather station',
        default="my weather station",
    )
    required_config.add_aggregation('target_url', function=create_url)
    required_config.add_option(
        'seconds_for_timeout',
        doc='the number of seconds to allow for fetching weather data',
        default=10)

    def __init__(self, config):
        super(WeatherStation, self).__init__(
            config, config.name, "thing",
            "my weather station with data for {}, {}".format(
                config.city_name, config.state_code))
        self.weather_data = {
            'current_observation': {
                'temp_f': self.temperature,
                'pressure_in': self.barometric_pressure,
                'wind_mph': self.wind_speed,
            }
        }

    async def get_weather_data(self):
        async with aiohttp.ClientSession() as session:
            async with async_timeout.timeout(self.config.seconds_for_timeout):
                async with session.get(self.config.target_url) as response:
                    self.weather_data = json.loads(await response.text())
        current_observation = self.weather_data['current_observation']
        self.temperature = current_observation['temp_f']
        self.barometric_pressure = current_observation['pressure_in']
        self.wind_speed = current_observation['wind_mph']
        logging.debug('new values fetched: %s, %s, %s', self.temperature,
                      self.barometric_pressure, self.wind_speed)

    temperature = WoTThing.wot_property(name='temperature',
                                        initial_value=0.0,
                                        description='the temperature in ℉',
                                        value_source_fn=get_weather_data,
                                        units='℉')
    barometric_pressure = WoTThing.wot_property(
        name='barometric_pressure',
        initial_value=30.0,
        description='the air pressure in inches',
        units='in')
    wind_speed = WoTThing.wot_property(name='wind_speed',
                                       initial_value=30.0,
                                       description='the wind speed in mph',
                                       units='mph')
Exemplo n.º 8
0
class EnphaseEnergyMonitor(WoTThing):
    required_config = Namespace()
    required_config.add_option(
        'enphase_address',
        doc='local area network address ',
        default="10.0.0.101",
    )
    required_config.add_aggregation('target_url', function=create_url)
    required_config.add_option(
        'seconds_for_timeout',
        doc='the number of seconds to allow for fetching enphase data',
        default=10)

    _LIFETIME_GENERATION = 1
    _CURRENTLY_GENERATING = 3
    _MICROINVERTER_TOTAL = 7
    _MICROINVERTERS_ONLINE = 9

    def __init__(self, config):
        super(EnphaseEnergyMonitor,
              self).__init__(config, "Enphase Solar Panels", "thing",
                             "Data for my solar panels")

    _multiplicative_factor = {
        'Wh': 0.001,
        'W': 0.001,
        'kWh': 1.0,
        'kW': 1.0,
        'MWh': 1000.0,
        'GWh': 1000000.0
    }

    @staticmethod
    def _scale_based_on_units(raw_string):
        number_as_str, units = raw_string.split()
        return float(
            number_as_str) * EnphaseEnergyMonitor._multiplicative_factor[
                units.strip()]

    async def get_enphase_data(self):
        async with aiohttp.ClientSession() as session:
            async with async_timeout.timeout(self.config.seconds_for_timeout):
                async with session.get(self.config.target_url) as response:
                    enphase_home_page_raw = await response.text()
        enphase_page = BeautifulSoup(enphase_home_page_raw, 'html.parser')
        # this is stupidly fragile - we're assuming this page format never
        # changes from fetch to fetch - observation has shown this to be ok
        # but don't know if that will hold over Enphase software updates.
        td_elements = enphase_page.find_all('table')[2].find_all('td')
        self.lifetime_generation = self._scale_based_on_units(
            td_elements[self._LIFETIME_GENERATION].contents[0])
        self.generating_now = self._scale_based_on_units(
            td_elements[self._CURRENTLY_GENERATING].contents[0])
        self.microinverter_total = td_elements[
            self._MICROINVERTER_TOTAL].contents[0]
        self.microinverters_online = td_elements[
            self._MICROINVERTERS_ONLINE].contents[0]
        logging.debug('new values fetched: %s, %s, %s, %s',
                      self.lifetime_generation, self.generating_now,
                      self.microinverter_total, self.microinverters_online)

    lifetime_generation = WoTThing.wot_property(
        name='lifetime_generation',
        initial_value=0.0,
        description='Total lifetime generation in KWh',
        value_source_fn=get_enphase_data,
        units='KWh')
    generating_now = WoTThing.wot_property(
        name='generating_now',
        initial_value=0.0,
        description='currently generating in KWh',
        units='KW')
    microinverter_total = WoTThing.wot_property(
        name='microinverter_total',
        initial_value=0,
        description='the number of microinverters installed')
    microinverters_online = WoTThing.wot_property(
        name='microinverters_online',
        initial_value=0,
        description='the number of micro inverters online',
    )
Exemplo n.º 9
0
class PelletStove(WoTThing):
    required_config = Namespace()
    required_config.add_option(
        name='startup_level',
        doc='the stove intensity level used on startup',
        default='high',  # low, medium, high
    )
    required_config.add_option(
        name='medium_linger_time_in_minutes',
        doc='the time in minutes of lingering on medium during shutdown',
        default=5.0,
    )
    required_config.add_option(
        name='low_linger_time_in_minutes',
        doc='the time in minutes of lingering on low during shutdown',
        default=5.0,
    )
    required_config.add_option(
        'controller_implementation_class',
        doc='a fully qualified name of a class that can control the stove',
        default='stove_controller.StoveControllerImplementation',
        from_string_converter=class_converter)

    def __init__(self, config):
        super(PelletStove, self).__init__(config, "Pellet Stove Controller",
                                          "thing", "pellet stove automation")
        self.set_medium_linger(config.medium_linger_time_in_minutes)
        self.set_low_linger(config.low_linger_time_in_minutes)

        self._controller = config.controller_implementation_class()
        self.lingering_shutdown_task = None

        self.logging_count = 0

    async def get_thermostat_state(self):
        previous_thermostat_state = self.thermostat_state
        self.thermostat_state = self._controller.get_thermostat_state()
        self.logging_count += 1
        if self.logging_count % 300 == 0:
            logging.debug('still monitoring thermostat')
            self.logging_count = 0
        if previous_thermostat_state != self.thermostat_state:
            if self.thermostat_state:
                logging.info('start heating')
                await self.set_stove_mode_to_heating()
            else:
                logging.info('start lingering shutdown')
                self.lingering_shutdown_task = asyncio.get_event_loop(
                ).create_task(self.set_stove_mode_to_lingering())

    def set_medium_linger(self, value_in_minutes):
        self.medium_linger_time_in_seconds = value_in_minutes * 60
        logging.debug('medium_linger_time set to %s seconds',
                      self.medium_linger_time_in_seconds)

    def set_low_linger(self, value_in_minutes):
        self.low_linger_time_in_seconds = value_in_minutes * 60
        logging.debug('low_linger_time set to %s seconds',
                      self.low_linger_time_in_seconds)

    thermostat_state = WoTThing.wot_property(
        name='thermostat_state',
        description='the on/off state of the thermostat',
        initial_value=False,
        value_source_fn=get_thermostat_state,
    )
    stove_state = WoTThing.wot_property(
        name='stove_state',
        description='the stove intensity level',
        initial_value='off',  # off, low, medium, high
    )
    stove_automation_mode = WoTThing.wot_property(
        name='stove_automation_mode',
        description='the current operating mode of the stove',
        initial_value=
        'off',  # off, heating, lingering_in_medium, lingering_in_low, overridden
    )
    medium_linger_minutes = WoTThing.wot_property(
        name='medium_linger_minutes',
        description=
        'how long should the medium level last during lingering shutdown',
        initial_value=5.0,
        value_forwarder=set_medium_linger)
    low_linger_minutes = WoTThing.wot_property(
        name='low_linger_minutes',
        description=
        'how long should the low level last during lingering shutdown',
        initial_value=5.0,
        value_forwarder=set_low_linger)

    async def set_stove_mode_to_heating(self):
        if self.lingering_shutdown_task:
            logging.debug(
                'canceling lingering shutdown to turn stove back to high')
            self.lingering_shutdown_task.cancel()
            with contextlib.suppress(asyncio.CancelledError):
                await self.lingering_shutdown_task
            self.lingering_shutdown_task = None
        self._controller.set_on_high()
        self.stove_state = 'high'
        self.stove_automation_mode = 'heating'

    async def set_stove_mode_to_lingering(self):
        self._controller.set_on_medium()
        self.stove_state = 'medium'
        self.stove_automation_mode = 'lingering_in_medium'
        logging.debug('stove set to medium for %s seconds',
                      self.medium_linger_time_in_seconds)
        await asyncio.sleep(self.medium_linger_time_in_seconds)

        self._controller.set_on_low()
        self.stove_state = 'low'
        self.stove_automation_mode = 'lingering_in_low'
        logging.debug('stove set to low for %s seconds',
                      self.low_linger_time_in_seconds)
        await asyncio.sleep(self.low_linger_time_in_seconds)

        self._controller.set_off()
        self.stove_state = 'off'
        self.stove_automation_mode = 'off'
        logging.info('stove turned off')
        self.lingering_shutdown_task = None

    def shutdown(self):
        if self.lingering_shutdown_task:
            logging.debug('lingering_shutdown_task is pending - canceling')
            self.lingering_shutdown_task.cancel()
            with contextlib.suppress(asyncio.CancelledError):
                asyncio.get_event_loop().run_until_complete(
                    self.lingering_shutdown_task)
        self._controller.shutdown()