Package: apd.aggregation
APD Sensor aggregatorA programme that queries apd.sensor endpoints and aggregates their results.Generic single-database configuration.Database setupTo generate the required database tables you must create an alembic.ini file, as follows:[alembic]
script_location = apd.aggregation:alembic
sqlalchemy.url = postgresql+psycopg2://apd@localhost/apdand runalembic upgrade head. This should also be done after every upgrade of the software.Defining endpointsEndpoints to collect from are managed with thesensor_deploymentsCLI tool. After installation
there will be no deployments definedsensor_deployments add --db postgresql+psycopg2://apd@localhost/apd 
                       --api-key 97f6b3e5ceb64a6ba88968d7c3786b38
                       --colour xkcd:red
                       http://rpi4:8081
                       LoftThe optional colour argument is the colour to use when plotting charts with the built-in charting
tools. This uses matplotlib's colour specification system, documented athttps://matplotlib.org/tutorials/colors/colors.htmlThe sensors can then be listed withsensor_deployments list:Loft
ID 53998a5160de48aeb71a5c37cd1455f2
URI http://rpi4:8081
API key 97f6b3e5ceb64a6ba88968d7c3786b38
Colour xkcd:redThe ID is the deployment ID, as set by the endpoint. It is only possible to add endpoints if they can be
connected to at the time.Collating dataData can be collated from all defined endpoints with thecollect_sensor_datacommand line tool.
Although you can specify URLs and an API key to explicitly load data from a one-off endpoint, running
without specifying these will use the configured endpoints from the database.collect_sensor_data --db postgresql+psycopg2://apd@localhost/apdViewing dataYou can write scripts to visualise the data from the database. I recommend using Jupyter for this, as it
has good support for drawing charts and interactivity.All configured charts can be displayed with:from apd.aggregation.analysis import plot_multiple_charts
display(await plot_multiple_charts())More complex charting can be achieved by passingconfigs=to this function, consisting of configuration
objects as defined inapd.aggregation.analysis. Iteractivity can be achieved using theinteractable_plot_multiple_chartsfunction with Jupyter/IPyWidgets' existing interactivity support.More control can be achieved using other functions from this module, such as getting all data points from
a given sensor with:from apd.aggregation.query import with_database, get_data

with with_database("postgresql+psycopg2://apd@localhost/apd") as session:
    points = [(dp.collected_at, dp.data) async for dp in get_data() if dp.sensor_name=="RelativeHumidity"]These can be called from any Python code, not just Jupyter notebooksAnalysis and triggersThe aggregator allows for a long-running process that processes records as they are inserted to the database
and apply rules to them.This is configured with a Python-based configuration file, such as the following to log any time the
Temperature fluctuates above or below 18c:import operator

from apd.aggregation.actions.action import OnlyOnChangeActionWrapper, LoggingAction
from apd.aggregation.actions.runner import DataProcessor
from apd.aggregation.actions.trigger import ValueThresholdTrigger


handlers = [
    DataProcessor(
        name="TemperatureBelow18",
        action=OnlyOnChangeActionWrapper(LoggingAction()),
        trigger=ValueThresholdTrigger(
            name="TemperatureBelow18",
            threshold=18,
            comparator=operator.lt,
            sensor_name="Temperature",
        ),
    )
]This is run with:run_apd_actions --db postgresql+psycopg2://apd@localhost/apd sample_actions.pyThe optional--historicaloption causes the actions to be triggered for all events in the database.
If it's omitted then the default behaviour applies, which is to only analyse data that is added to the
database after the actions process has started.The possible actions are:apd.aggregation.actions.action.LoggingAction()- Log data pointsapd.aggregation.actions.action.SaveToDatabaseAction()- Save data points to the dbThese can be wrapped withOnlyOnChangeActionWrapper(subaction)to only trigger an action when
the underlying value changes and/or withOnlyAfterDateActionWrapper(subaction, min_date)to
only trigger if the date on the discovered objects is strictly aftermin_date.The possible triggers are:apd.aggregation.actions.trigger.ValueThresholdTrigger(...)- This compares the value of a sensor with threshold, using the specified comparator.
Any records that don't match thesensor_nameanddeployment_idparameters are excluded.TipsThe--dbargument to all command-line tools can be omitted and theAPD_DB_URIenvironment variable
set instead.Changes2.0.2 (2020-08-14)Fix error handling in collect CLI script2.0.1 (2020-08-14)Improve Python 3.7 compatibility, for easier use on RPis2.0.0 (2020-05-21)Remove tolerate failures option in favour of always tolerating failures1.1.0 (2020-05-21)Address technical review comments from APD book. Use this version over
the 1.0.0 release.Improve compatibility with newer versions of Pint1.0.0 (2020-01-27)Added management of known sensor endpointsAdded CLI script to collate dataAdded analysis tools for JupyterAdded long-running data synthesis and actions systemCopyright (c) 2019, Matthew WilkesAll rights reserved.Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.