High-level Cloud functions¶
Module for handling Cozify Cloud highlevel operations.
-
cozify.cloud.
token_expiry
¶ timedelta object for duration how often cloud_token will be considered valid for refresh.
Type: datetime.timedelta
-
cozify.cloud.
authenticate
(trustCloud=True, trustHub=True, remote=False, autoremote=True, autorefresh=True, expiry=None)[source]¶ Authenticate with the Cozify Cloud and any Hubs found.
Interactive only when absolutely needed, mostly on the first run. By default authentication is run selectively only for the portions needed. Hub authentication lives in the Cloud module since the authentication is obtained from the cloud.
- Authentication is a multistep process:
- trigger sending OTP to email address
- perform email login with OTP to acquire cloud token
- acquire hub information and authenticate with hub with cloud token
- store hub token for further use
Parameters: - trustCloud (bool) – Trust current stored state of cloud auth. Default True.
- trustHub (bool) – Trust current stored state of hub auth. Default True.
- remote (bool) – Treat a hub as being outside the LAN, i.e. calls will be routed via the Cozify Cloud remote call system. Defaults to False.
- autoremote (bool) – Autodetect hub LAN presence and flip to remote mode if needed. Defaults to True.
- autorefresh (bool) – Autorefresh cloud and hub tokens if they’re no longer valid but can still be rescued. Defaults to True.
- expiry (datetime.timedelta) – timedelta object for duration how often cloud_token will be auto-refreshed. Defaults to cloud.token_expiry
Returns: True on authentication success. Failure will result in an exception.
Return type: bool
-
cozify.cloud.
email
(new_email=None)[source]¶ Get currently used cloud account email or set a new one.
Returns: Cloud user account email address. Return type: str
-
cozify.cloud.
ping
(autorefresh=True, expiry=None)[source]¶ Test cloud token validity. On success will also trigger a refresh if it’s needed by the current key expiry.
Parameters: - refresh (bool) – Wether to perform a autorefresh check after a successful ping. Defaults to True.
- expiry (datetime.timedelta) – timedelta object for duration how often cloud_token will be auto-refreshed when cloud.ping() is called. Defaults to cloud.token_expiry()
Returns: validity of stored token.
Return type: bool
-
cozify.cloud.
refresh
(force=False, expiry=None)[source]¶ Renew current cloud token and store new token in state.
This call will only succeed if the current cloud token is still valid. A new refreshed token is requested from the API only if sufficient time has passed since the previous refresh.
Parameters: - force (bool) – Set to True to always perform a refresh regardless of time passed since previous refresh.
- expiry (datetime.timedelta) – timedelta object for duration of refresh expiry. Defaults to cloud.token_expiry
Returns: Success of refresh attempt, True also when expiry wasn’t over yet even though no refresh was performed.
Return type: bool
-
cozify.cloud.
resetState
()[source]¶ Reset stored cloud state.
Any further authentication flow will start from a clean slate. Hub state is left intact.
-
cozify.cloud.
token
(new_token=None, commit=True)[source]¶ Get currently used cloud_token or set a new one.
Parameters: - new_token (str) – New cloud_token to store. Use cautiously since an invalid token means interactive recovery.
- commit (bool) – Wether to commit the new value to persistent storage immediately or not.
Returns: Cloud remote authentication token.
Return type: str
Low-level Cloud API calls¶
Module for handling Cozify Cloud API 1:1 functions
-
cozify.cloud_api.
emaillogin
(email, otp)[source]¶ Raw Cloud API call, request cloud token with email address & OTP.
Parameters: - email (str) – Email address connected to Cozify account.
- otp (int) – One time passcode.
Returns: cloud token
Return type: str
-
cozify.cloud_api.
hubkeys
(cloud_token)[source]¶ 1:1 implementation of user/hubkeys
Parameters: cloud_token (str) – Returns: Map of hub_id: hub_token pairs. Return type: dict
-
cozify.cloud_api.
lan_ip
()[source]¶ 1:1 implementation of hub/lan_ip
This call will fail with an APIError if the requesting source address is not the same as that of the hub, i.e. if they’re not in the same NAT network. The above is based on observation and may only be partially true.
Returns: List of Hub ip addresses. Return type: list
Low-level State functions¶
In general there is little need to touch these unless you want to alter some assumed defaults.
Module for handling consistent state storage.
-
cozify.config.
state_path
¶ file path where state storage is kept. By default XDG conventions are used. (Most likely ~/.config/python-cozify/python-cozify.cfg)
Type: str
-
cozify.config.
state
¶ State object used for in-memory state. By default initialized with _initState.
Type: configparser.ConfigParser
-
cozify.config.
latest_version
¶ Current up to date version number. Anything encountered lower than this will go through autoconversion.
Type: int
-
cozify.config.
commit
(tmpstate=None)[source]¶ Write current state to file storage.
Parameters: tmpstate (configparser.ConfigParser) – State object to store instead of default state.
-
cozify.config.
dump
()[source]¶ Print out current state file to stdout. Long values are truncated since this is only for visualization.
-
cozify.config.
set_state_path
(filepath=None, copy_current=False)[source]¶ Set state storage path. Useful for example for testing without affecting your normal state. Call with no arguments to reset back to autoconfigured location.
Parameters: - filepath (str) – file path to use as new storage location. Defaults to XDG defined path.
- copy_current (bool) – Instead of initializing target file, dump previous state into it.
Raised Exceptions¶
-
exception
cozify.Error.
APIError
(status_code, message)[source]¶ Error raised for non-200 API return codes
Parameters: - status_code (int) – HTTP status code returned by the API
- message (str) – Potential error message returned by the API
-
status_code
¶ HTTP status code returned by the API
Type: int
-
message
¶ Potential error message returned by the API
Type: str
Low-level HTTP functions¶
In general there is little need to touch these unless you want to do custom low-level calls.
Helper module for hub_api & cloud_api
-
cozify.http.
get
(call, *, token, headers=None, params=None, **kwargs)[source]¶ GET method for calling hub or cloud APIs.
Parameters: - call (str) – Full API URL.
- token (str) – Either hub_token or cloud_token depending on target of call.
- headers (dict) – Any additional headers to add to the call.
- params (dict) – Any additional URL parameters to pass.
- **remote (bool) – If call is to be local or remote (bounced via cloud).
- **cloud_token (str) – Cloud authentication token. Only needed if remote = True.
-
cozify.http.
post
(call, *, token, headers=None, payload=None, params=None, **kwargs)[source]¶ POST method for calling hub our cloud APIs.
Parameters: - call (str) – Full API URL.
- payload (str) – json string to push out as the payload.
- token (str) – Either hub_token or cloud_token depending on target of call.
- headers (dict) – Any additional headers to add to the call.
- params (dict) – Any additional URL parameters to pass.
- **remote (bool) – If call is to be local or remote (bounced via cloud).
- **cloud_token (str) – Cloud authentication token. Only needed if remote = True.
-
cozify.http.
put
(call, payload, *, token, headers=None, params=None, **kwargs)[source]¶ PUT method for calling hub or cloud APIs.
Parameters: - call (str) – Full API URL.
- payload (str) – json string to push out as the payload.
- token (str) – Either hub_token or cloud_token depending on target of call.
- headers (dict) – Any additional headers to add to the call.
- params (dict) – Any additional URL parameters to pass.
- **remote (bool) – If call is to be local or remote (bounced via cloud).
- **cloud_token (str) – Cloud authentication token. Only needed if remote = True.
High-level Hub functions¶
Module for handling highlevel Cozify Hub operations.
-
cozify.hub.
capability
¶ Enum of known device capabilities. Alphabetically sorted, numeric value not guaranteed to stay constant between versions if new capabilities are added.
Type: capability
-
cozify.hub.
autoremote
(hub_id=None, new_state=None, commit=True)[source]¶ Get autoremote status of hub or set a new value for it. Always returns current state at the end.
Parameters: - hub_id (str) – Id of hub to query. The id is a string of hexadecimal sections used internally to represent a hub. Defaults to hub.default()
- new_state (bool) – New autoremoteness state to set for hub. True means remote will be automanaged. Defaults to None when only the current value will be returned.
- commit (bool) – True to commit new state after set. Defaults to True.
Returns: True for a hub with autoremote enabled.
Return type: bool
-
class
cozify.hub.
capability
An enumeration.
-
cozify.hub.
default
()[source]¶ Return id of default Hub.
If default hub isn’t known an AttributeError will be raised.
-
cozify.hub.
device_eligible
(device_id, capability_filter, devs=None, state=None, **kwargs)[source]¶ Check if device matches a AND devices filter.
Parameters: - device_id (str) – ID of the device to check.
- capability_filter (hub.capability) – Single hub.capability or a list of them to match against.
- devs (dict) – Optional devices dictionary to use. If not defined, will be retrieved live.
- state (dict) – Optional state dictionary, will be updated with state of checked device if device is eligible. Previous data in the dict is preserved unless it’s overwritten by new values.
Returns: True if filter matches.
Return type: bool
-
cozify.hub.
device_exists
(device_id, devs=None, state=None, **kwargs)[source]¶ Check if device exists.
Parameters: - device_id (str) – ID of the device to check.
- devs (dict) – Optional devices dictionary to use. If not defined, will be retrieved live.
- state (dict) – Optional state dictionary, will be updated with state of checked device if device is eligible. Previous data in the dict is preserved unless it’s overwritten by new values.
Returns: True if filter matches.
Return type: bool
-
cozify.hub.
device_off
(device_id, **kwargs)[source]¶ Turn off a device that is capable of turning off. Eligibility is determined by the capability ON_OFF.
Parameters: device_id (str) – ID of the device to operate on.
-
cozify.hub.
device_on
(device_id, **kwargs)[source]¶ Turn on a device that is capable of turning on. Eligibility is determined by the capability ON_OFF.
Parameters: device_id (str) – ID of the device to operate on.
-
cozify.hub.
device_reachable
(device_id, devs=None, state=None, **kwargs)[source]¶ Check if device exists and is reachable.
Parameters: - device_id (str) – ID of the device to check.
- devs (dict) – Optional devices dictionary to use. If not defined, will be retrieved live.
- state (dict) – Optional state dictionary, will be updated with state of checked device if device is eligible. Previous data in the dict is preserved unless it’s overwritten by new values.
Returns: True if filter matches.
Return type: bool
-
cozify.hub.
device_state_replace
(device_id, state, **kwargs)[source]¶ Replace the entire state of a device with the provided state. Useful for example for returning to a stored state.
Parameters: - device_id (str) – ID of the device to toggle.
- state (dict) – State dictionary to push out.
- **hub_id (str) – optional id of hub to operate on. A specified hub_id takes presedence over a hub_name or default Hub.
- **hub_name (str) – optional name of hub to operate on.
- **remote (bool) – Remote or local query.
-
cozify.hub.
device_toggle
(device_id, **kwargs)[source]¶ Toggle power state of any device capable of it such as lamps. Eligibility is determined by the capability ON_OFF.
Parameters: - device_id (str) – ID of the device to toggle.
- **hub_id (str) – optional id of hub to operate on. A specified hub_id takes presedence over a hub_name or default Hub.
- **hub_name (str) – optional name of hub to operate on.
- **remote (bool) – Remote or local query.
-
cozify.hub.
devices
(*, capabilities=None, and_filter=False, devs=None, **kwargs)[source]¶ Get up to date full devices data set as a dict. Optionally can be filtered to only include certain devices.
Parameters: - capabilities (cozify.hub.capability) – Single or list of cozify.hub.capability types to filter by, for example: [ cozify.hub.capability.TEMPERATURE, cozify.hub.capability.HUMIDITY ]. Defaults to no filtering.
- and_filter (bool) – Multi-filter by AND instead of default OR. Defaults to False.
- devs (dict) – Optional devices dictionary to use. If not defined, will be retrieved live.
- **hub_name (str) – optional name of hub to query.
- **hub_id (str) – optional id of hub to query. A specified hub_id takes presedence over a hub_name or default Hub. Providing incorrect hub_id’s will create cruft in your state but it won’t hurt anything beyond failing the current operation.
- **remote (bool) – Remote or local query.
Returns: full live devices dictionary as returned by the API
Return type: dict
-
cozify.hub.
exists
(hub_id=None)[source]¶ Check for existance of hub in local state.
Parameters: hub_id (str) – Id of hub to query. The id is a string of hexadecimal sections used internally to represent a hub.
-
cozify.hub.
host
(hub_id=None)[source]¶ Get hostname of hub
Parameters: hub_id (str) – Id of hub to query. The id is a string of hexadecimal sections used internally to represent a hub. Returns: ip address of matching hub. Be aware that this may be empty if the hub is only known remotely and will still give you an ip address even if the hub is currently remote and an ip address was previously locally known. Return type: str
-
cozify.hub.
hub_id
(hub_name)[source]¶ Get hub id by it’s name.
Parameters: hub_name (str) – Name of hub to query. The name is given when registering a hub to an account. Returns: hub_id on success, raises an attributeerror on failure. Return type: str
-
cozify.hub.
light_brightness
(device_id, brightness, transition=0, **kwargs)[source]¶ Set brightness of a light.
Parameters: - device_id (str) – ID of the device to operate on.
- brightness (float) – Brightness in the range of [0, 1]. If outside the range a ValueError is raised.
- transition (int) – Transition length in milliseconds. Defaults to instant.
-
cozify.hub.
light_color
(device_id, hue, saturation=1.0, transition=0, **kwargs)[source]¶ Set color (hue & saturation) of a light.
Parameters: - device_id (str) – ID of the device to operate on.
- hue (float) – Hue in the range of [0, Pi*2]. If outside the range a ValueError is raised.
- saturation (float) – Saturation in the range of [0, 1]. If outside the range a ValueError is raised. Defaults to 1.0 (full saturation.)
- transition (int) – Transition length in milliseconds. Defaults to instant.
-
cozify.hub.
light_temperature
(device_id, temperature=2700, transition=0, **kwargs)[source]¶ Set temperature of a light.
Parameters: - device_id (str) – ID of the device to operate on.
- temperature (float) – Temperature in Kelvins. If outside the operating range of the device the extreme value is used. Defaults to 2700K.
- transition (int) – Transition length in milliseconds. Defaults to instant.
-
cozify.hub.
name
(hub_id=None)[source]¶ Get hub name
Parameters: hub_id (str) – Id of hub to query. The id is a string of hexadecimal sections used internally to represent a hub. Returns: Hub name or None if the hub wasn’t found. Return type: str
-
cozify.hub.
ping
(autorefresh=True, **kwargs)[source]¶ Perform a cheap API call to trigger any potential APIError and return boolean for success/failure. For optional kwargs see cozify.hub_api.get()
Parameters: - autorefresh (bool) – Wether to perform a autorefresh after an initially failed ping. If successful, will still return True. Defaults to True.
- **hub_id (str) – Hub to ping or default if neither id or name set.
- **hub_name (str) – Hub to ping by name.
Returns: True for a valid and working hub authentication state.
Return type: bool
-
cozify.hub.
remote
(hub_id=None, new_state=None, commit=True)[source]¶ Get remote status of hub or set a new value for it. Always returns current state at the end.
Parameters: - **hub_id (str) – Id of hub to query. The id is a string of hexadecimal sections used internally to represent a hub. Defaults to hub.default()
- new_state (bool) – New remoteness state to set for hub. True means remote. Defaults to None when only the current value will be returned.
- commit (bool) – True to commit new state after set. Defaults to True.
Returns: True for a hub considered remote.
Return type: bool
-
cozify.hub.
token
(hub_id=None, new_token=None, commit=True)[source]¶ Get hub_token or set a new value for it.
Parameters: - hub_id (str) – Id of hub to query. The id is a string of hexadecimal sections used internally to represent a hub.
- commit (bool) – True to commit new state after set. Defaults to True.
Returns: Hub authentication token.
Return type: str
-
cozify.hub.
tz
(**kwargs)[source]¶ Get timezone of given hub or default hub if no id is specified. For more optional kwargs see cozify.hub_api.get()
Parameters: **hub_id (str) – Hub to query, by default the default hub is used. Returns: Timezone of the hub, for example: ‘Europe/Helsinki’ Return type: str
Low-level Hub API calls¶
Module for all Cozify Hub API 1:1 calls
-
cozify.hub_api.
colors
(**kwargs)[source]¶ 1:1 implementation of /hub/colors API call. For kwargs see cozify.hub_api.get()
Returns: List of hexadecimal color codes of all defined custom colors. Return type: list
-
cozify.hub_api.
devices
(**kwargs)[source]¶ 1:1 implementation of /devices API call. For remaining kwargs see cozify.hub_api.get()
Parameters: **devs (dict) – If defined, returned as-is. Returns: Full live device state as returned by the API Return type: dict
-
cozify.hub_api.
devices_command
(command, **kwargs)[source]¶ 1:1 implementation of /devices/command. For kwargs see cozify.hub_api.put()
Parameters: command (dict) – dictionary of type DeviceData containing the changes wanted. Will be converted to json. Returns: What ever the API replied or raises an APIEerror on failure. Return type: str
-
cozify.hub_api.
devices_command_generic
(*, device_id, command=None, request_type, **kwargs)[source]¶ Command helper for CMD type of actions. No checks are made wether the device supports the command or not. For kwargs see cozify.hub_api.put()
Parameters: - device_id (str) – ID of the device to operate on.
- request_type (str) – Type of CMD to run, e.g. CMD_DEVICE_OFF
- command (dict) – Optional dictionary to override command sent. Defaults to None which is interpreted as { device_id, type }
Returns: What ever the API replied or raises an APIError on failure.
Return type: str
-
cozify.hub_api.
devices_command_off
(device_id, **kwargs)[source]¶ Command helper for CMD_DEVICE_OFF.
Parameters: device_id (str) – ID of the device to operate on. Returns: What ever the API replied or raises an APIException on failure. Return type: str
-
cozify.hub_api.
devices_command_on
(device_id, **kwargs)[source]¶ Command helper for CMD_DEVICE_ON.
Parameters: device_id (str) – ID of the device to operate on. Returns: What ever the API replied or raises an APIError on failure. Return type: str
-
cozify.hub_api.
devices_command_state
(*, device_id, state, **kwargs)[source]¶ Command helper for CMD type of actions. No checks are made wether the device supports the command or not. For kwargs see cozify.hub_api.put()
Parameters: - device_id (str) – ID of the device to operate on.
- state (dict) – New state dictionary containing changes.
Returns: What ever the API replied or raises an APIError on failure.
Return type: str
-
cozify.hub_api.
hub
(**kwargs)[source]¶ 1:1 implementation of /hub API call. For kwargs see cozify.hub_api.get()
Returns: Hub state dict. Return type: dict
python-cozify¶
Unofficial Python3 API bindings for the (unpublished) Cozify API. Includes high-level helpers for easier use of the APIs, for example an automatic authentication flow, and low-level 1:1 API functions.
Installation¶
Python3.4 is the current minimum supported version of Python. For example Ubuntu 14.04 LTS is still barely supported in theory but not explicitly tested for.
The recommended way is to install from PyPi:
sudo -H pip3 install cozify
To benefit from new features you’ll need to update the library (pip does not auto-update):
sudo -H pip3 install -U cozify
or clone the master branch of this repo (master stays at current release) and:
sudo python3 setup.py install
To develop python-cozify clone the devel branch and submit pull requests against the devel branch. New releases are cut from the devel branch as needed.
Basic usage¶
These are merely some simple examples, for the full documentation see: http://python-cozify.readthedocs.io/en/latest/
read devices by capability, print temperature data¶
from cozify import hub
devices = hub.devices(capabilities=hub.capability.TEMPERATURE)
for id, dev in devices.items():
print('{0}: {1}C'.format(dev['name'], dev['state']['temperature']))
only authenticate¶
from cozify import cloud
cloud.authenticate()
# authenticate() is interactive and usually triggered automatically
# authentication data is stored in ~/.config/python-cozify/python-cozify.cfg
authenticate with a non-default state storage¶
from cozify import cloud, config
config.set_state_path('/tmp/testing-state.cfg')
cloud.authenticate()
# authentication and other useful data is now stored in the defined location instead of ~/.config/python-cozify/python-cozify.cfg
# you could also use the environment variable XDG_CONFIG_HOME to override where config files are stored
On Capabilities¶
The most practical way to “find” devices for operating on is currently to filter the devices list by their capabilties. The most up to date list of recognized capabilities can be seen at cozify/hub.py
If the capability you need is not yet supported, open a bug to get it added. One way to compare your live hub device’s capabilities to those implemented is running the util/capabilities_list.py tool. It will list implemented and gathered capabilities from your live environment. To get all of your previously unknown capabilities implemented, just copy-paste the full output of the utility into a new bug.
In short capabilities are tags assigned to devices by Cozify that mostly guarantee the data related to that capability will be in the same format and structure. For example the capabilities based example code in this document filters all the devices that claim to support temperature and reads their name and temperature state. Multiple capabilities can be given in a filter by providing a list of capabilities. By default any capability in the list can match (OR filter) but it can be flipped to AND mode where every capability must be present on a device for it to qualify. For example, if you only want multi-sensors that support both temperature and humidity monitoring you could define a filter as:
devices = hub.devices(capabilities=[ hub.capability.TEMPERATURE, hub.capability.HUMIDITY ], and_filter=True)
Keeping authentication valid¶
If the cloud token expires, the only option to get a new one is an interactive prompt for an OTP. Since most applications will want to avoid that as much as possible there are a few tips to keep a valid token alive. At the time of writing tokens are valid for 28 days during which they can be seamlessly refreshed.
In most cases it isn’t necessary to directly call cloud.refresh() if you’re already using cloud.ping() to test token validity. cloud.ping() will also perform a refresh check after a successful ping unless explicitly told not to do so.
To refresh a token you can call as often as you want:
cloud.refresh()
By default keys older than a day will be re-requested and otherwise no refresh is performed. The refresh can be forced:
cloud.refresh(force=True)
And the expiry duration can be altered (also when calling cloud.ping()):
cloud.refresh(expiry=datetime.timedelta(days=20))
# or
cloud.ping(autorefresh=True, expiry=datetime.timedelta(days=20))
Working Remotely¶
By default queries to the hub are attempted via local LAN. Also by default “remoteness” autodetection is on and thus if it is determined during cloud.authentication() or a hub.ping() call that you seem to not be in the same network, the state is flipped. Both the remote state and autodetection can be overriden in most if not all funcions by the boolean keyword arguments ‘remote’ and ‘autoremote’. They can also be queried or permanently changed by the hub.remote() and hub.autoremote() functions.
Using Multiple Hubs¶
Everything has been designed to support multiple hubs registered to the same Cozify Cloud account. All hub operations can be targeted by setting the keyword argument ‘hub_id’ or ‘hub_name’. The developers do not as of yet have access to multiple hubs so proper testing of multi functionality has not been performed. If you run into trouble, please open bugs so things can be improved.
The remote state of hubs is kept separately so there should be no issues calling your home hub locally but operating on a summer cottage hub remotely at the same time.
Enconding Pitfalls¶
The hub provides data encoded as a utf-8 json string. Python-cozify transforms this into a Python dictionary where string values are kept as unicode strings. Normally this isn’t an issue, as long as your system supports utf-8. If not, you will run into trouble printing for example device names with non-ascii characters:
UnicodeEncodeError: ‘ascii’ codec can’t encode character ‘xe4’ in position 34: ordinal not in range(128)
The solution is to change your system locale to support utf-8. How this is done is however system dependant. As a first test try temporarily overriding your locale:
LC_ALL='en_US.utf8' python3 program.py
Sample projects¶
- github.com/Artanicus/cozify-temp - Store Multisensor data into InfluxDB
- Take a look at the util/ directory for some crude small tools using the library that have been useful during development.
- File an issue to get your project added here
Development¶
To develop python-cozify clone the devel branch and submit pull requests against the devel branch. New releases are cut from the devel branch as needed.
Tests¶
pytest is used for unit tests.
- Certain tests are marked as “live” tests and require an active authentication state and a real hub to query against. Live tests are non-destructive.
- Some tests are marked as “destructive” and will cause changes such as a light being turned on or tokens getting invalidated on purpose.
- A few tests are marked as “remote” and are only expected to succeed when testing remotely, i.e. outside the LAN of the hub.
- Most tests are marked as “logic” and do not require anything external. If no set is defined, only logic tests are run.
During development you can run the test suite right from the source directory:
pytest
# or run only live tests:
pytest -m live
# run everything except destructive tests:
pytest -m "not destructive"
To run the test suite on an already installed python-cozify (defining a set is mandatory, otherwise ALL sets are run including destructive):
pytest -v -m logic --pyargs cozify
Roadmap, aka. Current Limitations¶
- Authentication flow has been improved quite a bit but it would benefit a lot from real-world feedback.
- For now there are only read calls. Next up is implementing ~all hub calls at the raw level and then wrapping them for ease of use. If there’s something you want to use sooner than later file an issue so it can get prioritized!
- Device model is non-existant and the old implementations are bad and deprecated. Active work ongoing to filter by capability at a low level first, then perhaps a more object oriented model on top of that.