Nota Bene : The online service that provides weather data for this project no longer exists. You can never depend on a online service, even if you pay for it. The company that runs it (in this case, WeatherUnderground) may get purchased by another company (in this case, IBM) and they go and wreck everything.
The Virtual Weather Station was written using Things Framework, a new communication protocol to connect devices with controllers based on Web technology. The Things Framework consists of a set libraries and modules written in various languages. Each library implements a server that offers the Web Thing API on behalf of the device running the server. The protocol is HTTP, so the server offers a Web interface by embedding a Web Server. That interface contains all the mechanisms to query or control the device and is, therefore, the embodiment of the Web Thing API .
webthing-python
The webthing Python 3 package is an implementation of the Things Framework. When creating the code for a physical or virtual device to speak the Web Thing API, this module supplies the required classes and functions. Like all the webthing libraries in various languages, it creates a server called WebThingServer . It's really a container for two other servers. It employs an instance of tornado , an asynchronous Web server, as well as a zeroconf , a multi-thread hot Multicast DNS discovery service.The tornado Web server is the window to the wider world for a device. It allows the device to report state to the Things Gateway. The Things Gateway can also manipulate the state of the device through this interface.
The zeroconf server allows the Things Gateway to discover the device's tornado Web server on the local network by announcing its existence via Multicast DNS. This means that you don't have to know the IP address of the things that you have on your network, the Things Gateway can find them on its own.
When writing an application with WoTServer class, a collection of Thing objects are passed to the WoTServer . The WoTServer iterates through the collection and creates the URL-to-handler-method associations required by the tornado Web server. It accomplishes this with the use of the Thing and Property public classes in cooperation with a set of not-as-public helper classes. The Thing and Property classes implement the GET and PUT equivalents that the Web Server needs to implement its services. Therefore, the Thing and Property classes are tightly bound to the Web Server and HTTP.
You may notice that several examples of Things Framework code also refer to Action and Event classes. The Things Gateway doesn’t currently provide a way to use actions or display events, but those are in the works. To simplify my examples, I'm going to omit the use of these classes until they are supported within the Things Gateway.
Writing Code with webthing
The webthing-python code tracks a reference implementation written in Javascript. As such, webthing Python package carries some baggage that is not particularly Pythonic. It's quite functional, but just doesn't quite fit the idioms and style of Python. There are some particular standouts:- Instances of Property are added at initialization time rather than at class load time. This means they are difficult to represent using Python's property descriptor system, which seems like it should be a natural fit.
- The "read/write" versus "read only" aspect of a Property is not specified in the Property but, instead, by the presence or absence of a method passed into the constructor of an entirely different class, Value .
- The arguments list for the Property constructor have a final catch-all parameter called metadata as a free-form dict instance. Python already has a native method of handling this situation with the kwargs argument.
class ExampleDimmableLight(Thing):
def __init__(self):
Thing.__init__('a lamp', 'dimmable light', 'a Web connected lamp')
self.add_property(
Property(
self,
'level',
Value(0.0, lambda l: print('New light level is', l)),
metadata={
'type': 'number',
'description': 'The level of light from 0-100',
'minimum': 0,
'maximum': 100,
}))
(this example is a derivation of the
ExampleDimmableLight
from the
webthing
examples
)
What would be the ideal interface in Python to represent the concepts of the Things Framework? Creating my vision of an ideal interface was my goal in making my pywot wrapper around webthing . I addressed the issues above and automated some of the boilerplate asynchronous details. The goal was to simplify the coding to this:
class ExampleDimmableLight(WoTThing):
def __init__(self, config, lamp_hardware):
super(ExampleDimmableLight, self).__init__(
config, 'a lamp', 'dimmableLight', 'a Web connected lamp')
level = WoTThing.wot_property(
name='level',
initial_value=0,
description="lamp brightness level",
value_forwarder=_set_hardware_level,
minimum=0,
maximum=100
)
(See
the full
pywot
ExampleDimmableLight
example for more detail)
How pywot works
I started by creating a class WoTThing that derives from the webthing . Thing class. This new class has a fancy class method called wot_property that combines the webthing . Property with a Python descriptor.class WoTThing(Thing, RequiredConfig):
@classmethod
def wot_property(
kls,
*,
name,
initial_value,
description,
value_source_fn=None,
value_forwarder=None,
**kwargs
):
kls.wot_property_functions[name] = partial(
create_wot_property,
name=name,
initial_value=initial_value,
description=description,
value_source_fn=value_source_fn,
value_forwarder=value_forwarder,
**kwargs
)
(see
class WoTThing
in the
pywot
code)
The parameters ( name , initial_value , description , ... ) get saved and used later to instantiate Property objects during initialization time for any derived class. Each time wot_property is called, the method gathers the parameters required to create a Property and squirrels them away in a partial application of a function (see functools.partial ). That partial function is then stored in a class scoped list called wot_property_functions . The partial functions stored in that list will be called later during class initialization time where they will create the actual Property instances.
if value_source_fn is not None:
async def a_property_fetching_coroutine(thing_instance):
while True:
try:
await value_source_fn(thing_instance)
except CancelledError:
logging.debug('cancel detected')
break
except Exception as e:
logging.error('loading data fails: %s', e)
await sleep(thing_instance.config.seconds_between_polling)
a_property_fetching_coroutine.property_name = name
kls.property_fetching_coroutines.append(a_property_fetching_coroutine)
def get_value_fn(thing_instance):
return thing_instance.properties[name].value.get()
def set_value_fn(thing_instance, new_value):
thing_instance.properties[name].value.notify_of_external_update(new_value)
return property(get_value_fn, set_value_fn)
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']
current_observation = self.weather_data['current_observation']
self.properties['temperature'].value.notify_of_external_update(
current_observation['temp_f']
)
self.properties['barometric_pressure'].value.notify_of_external_update(
current_observation['pressure_in']
)
self.properties['wind_speed'].value.notify_of_external_update(
current_observation['wind_mph']
)
The Virtual Weather Station uses three properties:
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'
)
(see
the full code for the Virtual Weather Station
in the
demo directory for
pywot
)
async def get_weather_data(self):
async with aiohttp.ClientSession() as session:
async with async_timeout.timeout(config.seconds_for_timeout):
async with session.get(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']
The pywot package also provides a derived class of the webthing WebThingServer class called WoTServer . The only extra functionality it adds is the management of the asynchronous polling methods for the Thing properties. Rather than just use the start/stop methods of the base class, it provides a run method. This allows the class to control the orderly shutdown of the asynchronous polling tasks and exception handling.
To my eyes, the pywot package makes creating a virtual Web Thing pretty simple. Though, as I've said many times, "complexity is in the eye of the beholder". In my next posting, I'm going to talk about more Virtual Web Things that I've created. If you want to jump ahead without me, see the demo directory of my github repo for this project .
Nota bene: pywot is experimental software to prove a concept. I've not uploaded it to create an installable package as it is not stable: as the Things Framework evolves, this module will evolve, too. It has no tests. Use it at your own risk.