Commit 5c3b58f7 by source_reader

major refactor of module structure, added new nilmdb setup using uwsgi and nginx

parent bf54c189
Showing with 129 additions and 3323 deletions
source 'https://rubygems.org'
puppetversion = ENV.key?('PUPPET_VERSION') ? "= #{ENV['PUPPET_VERSION']}" : ['>= 3.3']
gem 'puppet', puppetversion
gem 'puppetlabs_spec_helper', '>= 0.1.0'
gem 'puppet-lint', '>= 0.3.2'
gem 'facter', '>= 1.7.0'
# apache
#### Table of Contents
1. [Overview](#overview)
2. [Module Description - What the module does and why it is useful](#module-description)
3. [Setup - The basics of getting started with apache](#setup)
* [What apache affects](#what-apache-affects)
* [Setup requirements](#setup-requirements)
* [Beginning with apache](#beginning-with-apache)
4. [Usage - Configuration options and additional functionality](#usage)
5. [Reference - An under-the-hood peek at what the module is doing and how](#reference)
5. [Limitations - OS compatibility, etc.](#limitations)
6. [Development - Guide for contributing to the module](#development)
## Overview
A one-maybe-two sentence summary of what the module does/what problem it solves.
This is your 30 second elevator pitch for your module. Consider including
OS/Puppet version it works with.
## Module Description
If applicable, this section should have a brief description of the technology
the module integrates with and what that integration enables. This section
should answer the questions: "What does this module *do*?" and "Why would I use
it?"
If your module has a range of functionality (installation, configuration,
management, etc.) this is the time to mention it.
## Setup
### What apache affects
* A list of files, packages, services, or operations that the module will alter,
impact, or execute on the system it's installed on.
* This is a great place to stick any warnings.
* Can be in list or paragraph form.
### Setup Requirements **OPTIONAL**
If your module requires anything extra before setting up (pluginsync enabled,
etc.), mention it here.
### Beginning with apache
The very basic steps needed for a user to get the module up and running.
If your most recent release breaks compatibility or requires particular steps
for upgrading, you may wish to include an additional section here: Upgrading
(For an example, see http://forge.puppetlabs.com/puppetlabs/firewall).
## Usage
Put the classes, types, and resources for customizing, configuring, and doing
the fancy stuff with your module here.
## Reference
Here, list the classes, types, providers, facts, etc contained in your module.
This section should include all of the under-the-hood workings of your module so
people know what the module is touching on their system but don't need to mess
with things. (We are working on automating this section!)
## Limitations
This is where you list OS compatibility, version compatibility, etc.
## Development
Since your module is awesome, other users will want to play with it. Let them
know what the ground rules for contributing are.
## Release Notes/Contributors/Etc **Optional**
If you aren't using changelog, put your release notes here (though you should
consider using changelog). You may also add any additional sections you feel are
necessary or important to include here. Please use the `## ` header.
# == Class: apache
#
# Install Apache Web Server
#
class apache {
$deps = ['apache2','libapache2-mod-wsgi','dirmngr', 'gnupg' ]
$https_pkgs = ['apt-transport-https', 'ca-certificates']
package{ $deps:
ensure => present,
}
#configure passenger for host rails applications
if $facts['platform'] == 'ubuntu' {
exec{ 'install-keys':
command => '/usr/bin/apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 561F9B9CAC40B2F7',
require => Package[$deps],
}
exec {'add-repo':
command => "/bin/sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger bionic main > /etc/apt/sources.list.d/passenger.list' && /usr/bin/apt-get update",
refreshonly => true
}
} else {
exec{ 'install-keys':
command => '/bin/true',
require => Package[$deps],
}
exec {'add-repo':
command => '/bin/true',
refreshonly => true
}
notice("omitting passenger key and repo on rpi platform")
}
package{'libapache2-mod-passenger':
ensure => present,
require => Exec['add-repo'],
notify => Exec['enable-passenger']
}
exec{'enable-passenger':
command => '/usr/sbin/a2enmod passenger',
require => Package['libapache2-mod-passenger'],
refreshonly => true,
notify => Exec['apache2ctl restart']
}
package {$https_pkgs:
ensure => present,
require => Exec['install-keys'],
notify => Exec['add-repo']
}
file { '/etc/apache2/sites-enabled/000-default.conf':
ensure => absent,
notify => Exec['apache2ctl restart']
}
exec{'enable-rewrite-engine':
command => '/usr/sbin/a2enmod rewrite',
require => Package[$deps],
notify => Exec['apache2ctl restart']
}
exec{'apache2ctl restart':
path => ['/usr/sbin','/usr/bin'],
require => Exec['enable-passenger', 'enable-rewrite-engine'],
refreshonly => true,
}
}
{
"name": "jdonnal-apache",
"version": "0.1.0",
"author": "John Donnal",
"summary": "install Apache",
"license": "Apache 2.0",
"source": "",
"project_page": null,
"issues_url": null,
"dependencies": [
{"name":"puppetlabs-stdlib","version_requirement":">= 1.0.0"}
]
}
......@@ -12,23 +12,6 @@ class common {
ensure => present
}
#install chromium policy file
file {['/etc/chromium',
'/etc/chromium/policies',
'/etc/chromium/policies/managed',
'/etc/chromium/policies/recommended']:
ensure => directory,
owner => root,
group => root,
}
file {'/etc/chromium/policies/managed/policy.json':
ensure => present,
owner => root,
group => root,
mode => '0644',
source => 'puppet:///modules/common/chromium_policy.json'
}
# Set up files on the Ubuntu Desktop (through /etc/skel)
if $facts['platform'] == 'ubuntu'{
file {'/etc/skel/Desktop':
......@@ -96,31 +79,4 @@ class common {
source => 'puppet:///modules/common/pi/first_boot_gui_notify.sh'
}
}
# first boot initialization scripts
file {'/usr/local/bin/first_boot':
ensure => present,
owner => root,
group => root,
mode => '0755',
source => 'puppet:///modules/common/first_boot.sh'
}
file { '/etc/systemd/system/first_boot.service':
ensure => present,
owner => root,
group => root,
mode => '0644',
source => 'puppet:///modules/common/first_boot.service'
}
service { "first_boot.service":
provider => systemd,
enable => true,
require => File['/etc/systemd/system/first_boot.service']
}
file { '/usr/local/bin/first_boot_status':
ensure => present,
owner => root,
group => root,
mode => '0755',
source => 'puppet:///modules/common/first_boot_status.sh'
}
}
source ENV['GEM_SOURCE'] || 'https://rubygems.org'
puppetversion = ENV.key?('PUPPET_VERSION') ? ENV['PUPPET_VERSION'] : ['>= 3.3']
gem 'metadata-json-lint'
gem 'puppet', puppetversion
gem 'puppetlabs_spec_helper', '>= 1.2.0'
gem 'puppet-lint', '>= 1.0.0'
gem 'facter', '>= 1.7.0'
gem 'rspec-puppet'
# rspec must be v2 for ruby 1.8.7
if RUBY_VERSION >= '1.8.7' && RUBY_VERSION < '1.9'
gem 'rspec', '~> 2.0'
gem 'rake', '~> 10.0'
else
# rubocop requires ruby >= 1.9
gem 'rubocop'
end
# docs
#### Table of Contents
1. [Description](#description)
1. [Setup - The basics of getting started with docs](#setup)
* [What docs affects](#what-docs-affects)
* [Setup requirements](#setup-requirements)
* [Beginning with docs](#beginning-with-docs)
1. [Usage - Configuration options and additional functionality](#usage)
1. [Reference - An under-the-hood peek at what the module is doing and how](#reference)
1. [Limitations - OS compatibility, etc.](#limitations)
1. [Development - Guide for contributing to the module](#development)
## Description
Start with a one- or two-sentence summary of what the module does and/or what
problem it solves. This is your 30-second elevator pitch for your module.
Consider including OS/Puppet version it works with.
You can give more descriptive information in a second paragraph. This paragraph
should answer the questions: "What does this module *do*?" and "Why would I use
it?" If your module has a range of functionality (installation, configuration,
management, etc.), this is the time to mention it.
## Setup
### What docs affects **OPTIONAL**
If it's obvious what your module touches, you can skip this section. For
example, folks can probably figure out that your mysql_instance module affects
their MySQL instances.
If there's more that they should know about, though, this is the place to mention:
* A list of files, packages, services, or operations that the module will alter,
impact, or execute.
* Dependencies that your module automatically installs.
* Warnings or other important notices.
### Setup Requirements **OPTIONAL**
If your module requires anything extra before setting up (pluginsync enabled,
etc.), mention it here.
If your most recent release breaks compatibility or requires particular steps
for upgrading, you might want to include an additional "Upgrading" section
here.
### Beginning with docs
The very basic steps needed for a user to get the module up and running. This
can include setup steps, if necessary, or it can be an example of the most
basic use of the module.
## Usage
This section is where you describe how to customize, configure, and do the
fancy stuff with your module here. It's especially helpful if you include usage
examples and code samples for doing things with your module.
## Reference
Users need a complete list of your module's classes, types, defined types providers, facts, and functions, along with the parameters for each. You can provide this list either via Puppet Strings code comments or as a complete list in this Reference section.
* If you are using Puppet Strings code comments, this Reference section should include Strings information so that your users know how to access your documentation.
* If you are not using Puppet Strings, include a list of all of your classes, defined types, and so on, along with their parameters. Each element in this listing should include:
* The data type, if applicable.
* A description of what the element does.
* Valid values, if the data type doesn't make it obvious.
* Default value, if any.
## Limitations
This is where you list OS compatibility, version compatibility, etc. If there
are Known Issues, you might want to include them under their own heading here.
## Development
Since your module is awesome, other users will want to play with it. Let them
know what the ground rules for contributing are.
## Release Notes/Contributors/Etc. **Optional**
If you aren't using changelog, put your release notes here (though you should
consider using changelog). You can also add any additional sections you feel
are necessary or important to include here. Please use the `## ` header.
require 'rubygems'
require 'puppetlabs_spec_helper/rake_tasks'
require 'puppet-lint/tasks/puppet-lint'
require 'metadata-json-lint/rake_task'
if RUBY_VERSION >= '1.9'
require 'rubocop/rake_task'
RuboCop::RakeTask.new
end
PuppetLint.configuration.send('disable_80chars')
PuppetLint.configuration.ignore_paths = ["spec/**/*.pp", "pkg/**/*.pp"]
PuppetLint.configuration.relative = true
PuppetLint.configuration.ignore_paths = ['spec/**/*.pp', 'pkg/**/*.pp']
desc "Validate manifests, templates, and ruby files"
desc 'Validate manifests, templates, and ruby files'
task :validate do
Dir['manifests/**/*.pp'].each do |manifest|
sh "puppet parser validate --noop #{manifest}"
end
Dir['spec/**/*.rb','lib/**/*.rb'].each do |ruby_file|
sh "ruby -c #{ruby_file}" unless ruby_file =~ /spec\/fixtures/
Dir['spec/**/*.rb', 'lib/**/*.rb'].each do |ruby_file|
sh "ruby -c #{ruby_file}" unless ruby_file =~ %r{spec/fixtures}
end
Dir['templates/**/*.erb'].each do |template|
sh "erb -P -x -T '-' #{template} | ruby -c"
end
end
desc 'Run lint, validate, and spec tests.'
task :test do
[:lint, :validate, :spec].each do |test|
Rake::Task[test].invoke
end
end
# The baseline for module testing used by Puppet Labs is that each manifest
# The baseline for module testing used by Puppet Inc. is that each manifest
# should have a corresponding test manifest that declares that class or defined
# type.
#
......@@ -7,6 +7,6 @@
# environment (to compare the resulting system state to the desired state).
#
# Learn more about module testing here:
# http://docs.puppetlabs.com/guides/tests_smoke.html
# https://docs.puppet.com/guides/tests_smoke.html
#
include apache
include ::docs
Configuration
=============
In most situations Wattsworth should work well with the default settings.
For more complex setups involving systems that may loose power, are connected
through networks, or other special cases the reference material in this section
shows how to fine tune the installation to meet these needs.
Storage
-------
Make sure you set up data journaling on the nilmdb partition. This will prevent
data corruption if the computer looses power without properly shutting down.
Edit ``/etc/fstab`` and add the ``data=journal`` option to the parition with the
nilmdb database.
.. code-block:: bash
# /etc/fstab: static file system information.
#
# <file system> <mount point> <type> <options> <dump> <pass>
UUID=XXX / ext4 errors=remount-ro,data=journal 0 1
# add this --^
If the partition is the root partition you must add this option directly to the volume
as well, otherwise the system will not boot properly. For example if your
root partition is on ``/dev/sda2``:
.. code-block:: bash
$> sudo tune2fs -o journal_data /dev/sda2
<span class="lead" id="storage">Storage Setup</span>
<hr/>
<p>
This section explains how to properly format and configure
an extra hard drive for a NILM system. It is usually a good idea to place the Nilm Database on a seperate hard drive. This prevents the database from filling the primary drive to the point
where the system become unusable and also makes it easy to retrieve collected data from an installation by simply swapping out the extra harddrive.
</p>
<p><b>Formatting a drive</b> After installing the drive and booting the system, the first step to use
the drive is to place a usable filesystem on it. There are many tools that can be used for this but one of the easiest is GParted. This program must be run as root. From the command line type type the following:
</p>
<div class="alert alert-danger"><i class="icon icon-warning-sign"></i> Be very careful with gparted, formatting the primary drive will destroy the installation</div>
<section class="indented cm-view ">
<textarea class="cm-textarea-sh">
$ sudo gparted </textarea>
</section>
<p>
Select the extra drive from the dropdown menu as shown. Generally the extra drive should be <b>/dev/sdb</b>
but this is not always the case. If the drive already has multiple partitions this most likely means it is the primary drive.
</p>
<div class="row">
<div class="col-sm-12 col-md-6">
<div class="thumbnail">
<%=image_tag "hdd_gparted_1.png"%>
<div class="caption">
Carefully select the device node for the extra drive
</div>
</div>
</div>
<div class="col-sm-12 col-md-6">
<div class="thumbnail">
<%=image_tag "hdd_gparted_2.png"%>
<div class="caption">
Create a new msdos partition table. This will erase the drive.
</div>
</div>
</div>
</div>
<p>
Select <b>Device</b> > <b> Create Partition Table</b> to bring up the Create Partition dialog. Select <b>msdos</b> and click Apply. Select <b>Partition</b> > <b>New</b> to bring up the New Partition dialog. Add a new <b>ext4</b> partition to the drive and assign it the full extents of the disk (this is the default). Click Add to close the dialog. Finally click <b>Apply</b> to format the disk and then close the program.
<div class="row">
<div class="col-sm-12 col-md-6 col-md-offset-3">
<div class="thumbnail">
<%=image_tag "hdd_gparted_3.png"%>
<div class="caption">
Add an <b>ext4</b> partition to fill the disk
</div>
</div>
</div>
</div>
<p>
To use the drive for the Nilm database it must be mounted to the correct location in the filesystem. Edit <b>/etc/fstab</b> in a word processor and add the following line where /dev/sdX1 is the name of the drive you just formatted. The number one refers to the first (and only) partition. Note that you will need to run the word processor as root (sudo) in order to edit this file.
<pre>
/dev/sdX1 /opt/data ext4 errors=remount-ro 0 1 </pre>
When using an external drive as the primary storage volume, drive letters cannot be used since they change. The UUID is a unique drive partition identifier that should be used instead. Running <span class="text-mono">sudo lsblk /dev/sdX1</span> will print out the UUID. The new line in <b>/etc/fstab</b> should resemble the following.
<pre>
UUID=XXXXXXXX-XXXXX-XXXX-XXXX-XXXXXXXXXXXX /opt/data ext4 errors=remount-ro 0 1 </pre>
Run the following commands to mount the drive and setup the permissions. Use <b>df</b> to verify the configuration:
<section class="indented cm-view ">
<textarea class="cm-textarea-sh">
$ sudo service nilm-capture stop #stop data capture if it is already running
$ sudo mount -a
$ sudo chown -R nilm:nilm /opt/data #assign the drive to the nilm user
$ df -h</textarea>
</section>
</p>
<p>
The output from <b>df</b> should look similar to that below:
</p>
<pre>
Filesystem Size Used Avail Use% Mounted on
/dev/sda1 1.8T 5.3G 1.7T 1% /
... other mount points ...
/dev/sdc1 917G 72M 871G 1% /opt/data</pre>
<p>If you have already configured your meters and want to start collecting data, run:</p>
<section class="indented cm-view ">
<textarea class="cm-textarea-sh">
$ sudo service nilm-capture start</textarea>
</section>
.. Wattsworth documentation master file, created by
sphinx-quickstart on Tue Aug 1 11:55:55 2017.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
.. highlight:: bash
The Wattsworth Project
======================
Collect, process, and visualize IoT sensor data.
.. NOTE::
* See `joule </joule>`_ for information on the backend interface (CLI)
* See `web </web>`_ for information on the frontend interface (user facing)
Unlike traditional IoT solutions, Wattsworth is a confederation of
self sufficient nodes. Each node collects, processes and stores its own data
and provides a web interface to visualize the data.
For many situations a single node can solve your problem. For more complex
sensor networks multiple nodes can be combined into a mesh or a client-server
architecture.
1. **Data Collection** Collect and store time series data
2. **Data Processing** Build flexible signal processing pipelines.
3. **Data Visualization** View data using an intuitive web interface
Follow the instructions below to set up a node and start using Wattsworth.
Step 0: Install
+++++++
On a fresh Ubuntu installation (>= 16.04) run the following to install
the Wattsworth software stack::
$> sudo apt-get update
$> sudo apt-get install puppet git
$> git clone https://git.wattsworth.net/wattsworth/puppet.git
$> cd puppet
$> sudo puppet apply --modulepath=./modules --verbose site.pp
Step 1: Generate Data
+++++++++++++++++++++
Wattsworth's data processing engine is called Joule. Joule works with
Streams and Modules. Streams are sets of time series data (eg temperatures,
voltages, accelerations, etc). Modules are executable programs that read and
write to streams. There are two types of modules: readers and filters. Readers
collect external data (eg sensor readings) and write it to a stream. Let's start by
creating a reader that produces a stream of random data.
Copy the following to ``/etc/joule/module_configs/random_reader.conf``::
[Main]
exec_cmd = joule reader random 2 10
name = Demo Reader
[Source]
# a reader has no inputs
[Destination]
output = /demo/random
This file tells Joule to create a new module called **Demo Reader** that is
started by the command ``joule reader random 2 10``. The random reader is a
built-in reader provided by Joule. It takes two command line arguments, the
number of elements to "read" and the range of random numbers. In this case we
are producing a two element stream of random numbers ranging from [0, 10]. Try
running the module from the command line::
# output columns: timestamp (us), element1, element2
$> joule reader random 2 10
Starting random stream: 2 elements @ 10.0Hz
1504275719114302 0.52250357475642772 0.27410492447391688
1504275719214302 0.3504811731191021 0.60611860137921958
1504275719314302 0.16871102294606499 0.26205382675613365
#... output continues, type Ctrl-C to stop
When Joule runs this program as a module this output is written to a stream. The
configuration file specifies this output stream is ``/demo/random``. Before
we can start writing data to this stream we have to create it. To do this copy
the following to ``/etc/joule/stream_configs/random.conf``
.. code-block:: ini
[Main]
name = Random Data
path = /demo/random
datatype = float32
keep = 1w
[Element1]
name = rand1
[Element2]
name = rand2
Every stream must have a unique ``path`` similar to a file on a filesystem. The
``keep`` parameter tells Joule to save only the most recent week of data. This
prevents our disk from filling up. Next we list the elements in this stream.
This stream has two elements named *rand1* and *rand2*.
Now we are ready to start collecting data.
From the terminal restart the Joule process to activate our new module::
$> sudo service jouled restart
$> joule modules # show the active modules
View the Data
+++++++++++++
Open a browser and navigate to http://wattsworth.local. Log in as
**admin@wattsworth.local** with password **password**. From the main window
click the gear icon next to the local installation. This opens the
administration view (see ``admin help`` for more details). Click ``[Refresh]``
and the new data stream should appear in the file tree on the left. Navigate
back to the main page (click the header logo or use the browser back button) and
expand the file tree until you see the new stream. Click ``[Plot]`` and it
should display in the main window. See ``web help`` for information on plotting
data.
Add a Filter Module
+++++++++++++++++++
Modules can be
connected together to build flexible data acquisition and signal processing
pipelines. There are two types of modules, readers and filters.
Reader modules collect data. This data can come from local sensors,
remote IoT nodes, third party API's or any other source. Let's start simple and
create a reader module that produces random data.
Filter modules process data generated by readers or by other filters. Filters
store their results into one or more output streams. Copy the
following to ``/etc/joule/stream_configs/averaged.conf`` to allocate
a new stream::
.. code-block:: ini
[Main]
name = Averaged Data
path = /demo/filtered
datatype = float32
keep = 1w
[Element1]
name = filtered1
[Element2]
name = filtered2
From the terminal restart the Joule process to activate the filter::
$> sudo service jouled restart
$> joule modules # show active modules
View the Data
+++++++++++++
From the web browser, go back to the administration view and refresh the
database. You should now see two streams. Back in the data view plot both
streams. The filtered stream should be the average of the random stream.
Contributing
------------
.. toctree::
:maxdepth: 2
:caption: Contents:
installation
configuration
plugins
Installation
============
All of the software repositories are available at
https://git.wattsworth.net/wattsworth. The software has been tested on
64 bit Ubuntu Linux. While it is possible to run on Arm-based Single
Board Computers (eg Raspberry Pi), the software works best on x86 systems
such as the Intel NUC.
Use the Puppet repository to install the complete Wattsworth stack
.. code-block:: bash
$> sudo apt-get update
$> sudo apt-get install puppet
$> git clone https://git.wattsworth.net/wattsworth/puppet.git
$> cd puppet
$> sudo puppet apply --modulepath=./modules --verbose site.pp
Plugins
=======
.. raw:: html
<p id="no-plugins"> No plugins are currently installed </p>
<div id="has-plugins">
<p> Click a plugin name below to view the documentation </p>
<table class="table">
<thead>
<tr><th>Name</th><th>Description</th></tr>
</thead>
<tbody id="plugins-table">
</tbody>
</table>
</div>
.. raw:: html
<script type="text/javascript">
//hide the plugins section by default (in case plugins.txt is missing)
$("#has-plugins").hide();
var plugins="";
var nonce=new Date().getTime(); //so we force the browser to get new data
$.ajax({url: `_static/plugins.txt?nonce=${nonce}`, success:
function(result){
plugins = result.split('\n')
.reduce(function(acc,line){
if(line==""||line[0]=="#")
return acc;
acc.push(line.split(',').map(function(x){return x.trim()}));
return acc;
},[])
if(plugins.length>0){
$("#has-plugins").show();
$("#no-plugins").hide();
}
plugins.map(function(plugin){
var link = plugin[2];
var name=`<a href="/${link}">${plugin[0]}</a>`;
var desc = plugin[1];
$("#plugins-table").append(`<tr><td>${name}</td><td>${desc}</td></tr>`)
})
}});
</script>
.alert-info {
color: #31708f;
background-color: #d9edf7;
border-color: #bce8f1;
font-size: larger;
line-height: 2;
}
.alert {
padding: 5px;
margin-bottom: 20px;
border: 1px solid;
border-radius: 4px;
}
/* Bootstrap table styling */
table {
background-color: transparent;
}
caption {
padding-top: 8px;
padding-bottom: 8px;
color: #777777;
text-align: left;
}
th {
text-align: left;
}
.table {
width: 100%;
max-width: 100%;
margin-bottom: 20px;
}
.table>thead>tr>th, .table>tbody>tr>th, .table>tfoot>tr>th, .table>thead>tr>td, .table>tbody>tr>td, .table>tfoot>tr>td {
padding: 8px;
line-height: 1.42857143;
vertical-align: top;
border-top: 1px solid #ddd;
}
.table>thead>tr>th {
vertical-align: bottom;
border-bottom: 2px solid #ddd;
}
.table>caption+thead>tr:first-child>th, .table>colgroup+thead>tr:first-child>th, .table>thead:first-child>tr:first-child>th, .table>caption+thead>tr:first-child>td, .table>colgroup+thead>tr:first-child>td, .table>thead:first-child>tr:first-child>td {
border-top: 0;
}
.table>tbody+tbody {
border-top: 2px solid #ddd;
}
.table .table {
background-color: #fff;
}
.table-condensed>thead>tr>th, .table-condensed>tbody>tr>th, .table-condensed>tfoot>tr>th, .table-condensed>thead>tr>td, .table-condensed>tbody>tr>td, .table-condensed>tfoot>tr>td {
padding: 5px;
}
.table-bordered {
border: 1px solid #ddd;
}
.table-bordered>thead>tr>th, .table-bordered>tbody>tr>th, .table-bordered>tfoot>tr>th, .table-bordered>thead>tr>td, .table-bordered>tbody>tr>td, .table-bordered>tfoot>tr>td {
border: 1px solid #ddd;
}
.table-bordered>thead>tr>th, .table-bordered>thead>tr>td {
border-bottom-width: 2px;
}
.table-striped>tbody>tr:nth-of-type(odd) {
background-color: #f9f9f9;
}
.table-hover>tbody>tr:hover {
background-color: #f5f5f5;
}
table col[class*="col-"] {
position: static;
float: none;
display: table-column;
}
table td[class*="col-"], table th[class*="col-"] {
position: static;
float: none;
display: table-cell;
}
.table>thead>tr>td.active, .table>tbody>tr>td.active, .table>tfoot>tr>td.active, .table>thead>tr>th.active, .table>tbody>tr>th.active, .table>tfoot>tr>th.active, .table>thead>tr.active>td, .table>tbody>tr.active>td, .table>tfoot>tr.active>td, .table>thead>tr.active>th, .table>tbody>tr.active>th, .table>tfoot>tr.active>th {
background-color: #f5f5f5;
}
.table-hover>tbody>tr>td.active:hover, .table-hover>tbody>tr>th.active:hover, .table-hover>tbody>tr.active:hover>td, .table-hover>tbody>tr:hover>.active, .table-hover>tbody>tr.active:hover>th {
background-color: #e8e8e8;
}
.table>thead>tr>td.success, .table>tbody>tr>td.success, .table>tfoot>tr>td.success, .table>thead>tr>th.success, .table>tbody>tr>th.success, .table>tfoot>tr>th.success, .table>thead>tr.success>td, .table>tbody>tr.success>td, .table>tfoot>tr.success>td, .table>thead>tr.success>th, .table>tbody>tr.success>th, .table>tfoot>tr.success>th {
background-color: #dff0d8;
}
.table-hover>tbody>tr>td.success:hover, .table-hover>tbody>tr>th.success:hover, .table-hover>tbody>tr.success:hover>td, .table-hover>tbody>tr:hover>.success, .table-hover>tbody>tr.success:hover>th {
background-color: #d0e9c6;
}
.table>thead>tr>td.info, .table>tbody>tr>td.info, .table>tfoot>tr>td.info, .table>thead>tr>th.info, .table>tbody>tr>th.info, .table>tfoot>tr>th.info, .table>thead>tr.info>td, .table>tbody>tr.info>td, .table>tfoot>tr.info>td, .table>thead>tr.info>th, .table>tbody>tr.info>th, .table>tfoot>tr.info>th {
background-color: #d9edf7;
}
.table-hover>tbody>tr>td.info:hover, .table-hover>tbody>tr>th.info:hover, .table-hover>tbody>tr.info:hover>td, .table-hover>tbody>tr:hover>.info, .table-hover>tbody>tr.info:hover>th {
background-color: #c4e3f3;
}
.table>thead>tr>td.warning, .table>tbody>tr>td.warning, .table>tfoot>tr>td.warning, .table>thead>tr>th.warning, .table>tbody>tr>th.warning, .table>tfoot>tr>th.warning, .table>thead>tr.warning>td, .table>tbody>tr.warning>td, .table>tfoot>tr.warning>td, .table>thead>tr.warning>th, .table>tbody>tr.warning>th, .table>tfoot>tr.warning>th {
background-color: #fcf8e3;
}
.table-hover>tbody>tr>td.warning:hover, .table-hover>tbody>tr>th.warning:hover, .table-hover>tbody>tr.warning:hover>td, .table-hover>tbody>tr:hover>.warning, .table-hover>tbody>tr.warning:hover>th {
background-color: #faf2cc;
}
.table>thead>tr>td.danger, .table>tbody>tr>td.danger, .table>tfoot>tr>td.danger, .table>thead>tr>th.danger, .table>tbody>tr>th.danger, .table>tfoot>tr>th.danger, .table>thead>tr.danger>td, .table>tbody>tr.danger>td, .table>tfoot>tr.danger>td, .table>thead>tr.danger>th, .table>tbody>tr.danger>th, .table>tfoot>tr.danger>th {
background-color: #f2dede;
}
.table-hover>tbody>tr>td.danger:hover, .table-hover>tbody>tr>th.danger:hover, .table-hover>tbody>tr.danger:hover>td, .table-hover>tbody>tr:hover>.danger, .table-hover>tbody>tr.danger:hover>th {
background-color: #ebcccc;
}
.table-responsive {
overflow-x: auto;
min-height: 0.01%;
}
/* This file intentionally left blank. */
/*
* doctools.js
* ~~~~~~~~~~~
*
* Sphinx JavaScript utilities for all documentation.
*
* :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
/**
* select a different prefix for underscore
*/
$u = _.noConflict();
/**
* make the code below compatible with browsers without
* an installed firebug like debugger
if (!window.console || !console.firebug) {
var names = ["log", "debug", "info", "warn", "error", "assert", "dir",
"dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace",
"profile", "profileEnd"];
window.console = {};
for (var i = 0; i < names.length; ++i)
window.console[names[i]] = function() {};
}
*/
/**
* small helper function to urldecode strings
*/
jQuery.urldecode = function(x) {
return decodeURIComponent(x).replace(/\+/g, ' ');
};
/**
* small helper function to urlencode strings
*/
jQuery.urlencode = encodeURIComponent;
/**
* This function returns the parsed url parameters of the
* current request. Multiple values per key are supported,
* it will always return arrays of strings for the value parts.
*/
jQuery.getQueryParameters = function(s) {
if (typeof s == 'undefined')
s = document.location.search;
var parts = s.substr(s.indexOf('?') + 1).split('&');
var result = {};
for (var i = 0; i < parts.length; i++) {
var tmp = parts[i].split('=', 2);
var key = jQuery.urldecode(tmp[0]);
var value = jQuery.urldecode(tmp[1]);
if (key in result)
result[key].push(value);
else
result[key] = [value];
}
return result;
};
/**
* highlight a given string on a jquery object by wrapping it in
* span elements with the given class name.
*/
jQuery.fn.highlightText = function(text, className) {
function highlight(node) {
if (node.nodeType == 3) {
var val = node.nodeValue;
var pos = val.toLowerCase().indexOf(text);
if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) {
var span = document.createElement("span");
span.className = className;
span.appendChild(document.createTextNode(val.substr(pos, text.length)));
node.parentNode.insertBefore(span, node.parentNode.insertBefore(
document.createTextNode(val.substr(pos + text.length)),
node.nextSibling));
node.nodeValue = val.substr(0, pos);
}
}
else if (!jQuery(node).is("button, select, textarea")) {
jQuery.each(node.childNodes, function() {
highlight(this);
});
}
}
return this.each(function() {
highlight(this);
});
};
/*
* backward compatibility for jQuery.browser
* This will be supported until firefox bug is fixed.
*/
if (!jQuery.browser) {
jQuery.uaMatch = function(ua) {
ua = ua.toLowerCase();
var match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
/(webkit)[ \/]([\w.]+)/.exec(ua) ||
/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
/(msie) ([\w.]+)/.exec(ua) ||
ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
[];
return {
browser: match[ 1 ] || "",
version: match[ 2 ] || "0"
};
};
jQuery.browser = {};
jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true;
}
/**
* Small JavaScript module for the documentation.
*/
var Documentation = {
init : function() {
this.fixFirefoxAnchorBug();
this.highlightSearchWords();
this.initIndexTable();
},
/**
* i18n support
*/
TRANSLATIONS : {},
PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; },
LOCALE : 'unknown',
// gettext and ngettext don't access this so that the functions
// can safely bound to a different name (_ = Documentation.gettext)
gettext : function(string) {
var translated = Documentation.TRANSLATIONS[string];
if (typeof translated == 'undefined')
return string;
return (typeof translated == 'string') ? translated : translated[0];
},
ngettext : function(singular, plural, n) {
var translated = Documentation.TRANSLATIONS[singular];
if (typeof translated == 'undefined')
return (n == 1) ? singular : plural;
return translated[Documentation.PLURALEXPR(n)];
},
addTranslations : function(catalog) {
for (var key in catalog.messages)
this.TRANSLATIONS[key] = catalog.messages[key];
this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')');
this.LOCALE = catalog.locale;
},
/**
* add context elements like header anchor links
*/
addContextElements : function() {
$('div[id] > :header:first').each(function() {
$('<a class="headerlink">\u00B6</a>').
attr('href', '#' + this.id).
attr('title', _('Permalink to this headline')).
appendTo(this);
});
$('dt[id]').each(function() {
$('<a class="headerlink">\u00B6</a>').
attr('href', '#' + this.id).
attr('title', _('Permalink to this definition')).
appendTo(this);
});
},
/**
* workaround a firefox stupidity
* see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075
*/
fixFirefoxAnchorBug : function() {
if (document.location.hash)
window.setTimeout(function() {
document.location.href += '';
}, 10);
},
/**
* highlight the search words provided in the url in the text
*/
highlightSearchWords : function() {
var params = $.getQueryParameters();
var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : [];
if (terms.length) {
var body = $('div.body');
if (!body.length) {
body = $('body');
}
window.setTimeout(function() {
$.each(terms, function() {
body.highlightText(this.toLowerCase(), 'highlighted');
});
}, 10);
$('<p class="highlight-link"><a href="javascript:Documentation.' +
'hideSearchWords()">' + _('Hide Search Matches') + '</a></p>')
.appendTo($('#searchbox'));
}
},
/**
* init the domain index toggle buttons
*/
initIndexTable : function() {
var togglers = $('img.toggler').click(function() {
var src = $(this).attr('src');
var idnum = $(this).attr('id').substr(7);
$('tr.cg-' + idnum).toggle();
if (src.substr(-9) == 'minus.png')
$(this).attr('src', src.substr(0, src.length-9) + 'plus.png');
else
$(this).attr('src', src.substr(0, src.length-8) + 'minus.png');
}).css('display', '');
if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) {
togglers.click();
}
},
/**
* helper function to hide the search marks again
*/
hideSearchWords : function() {
$('#searchbox .highlight-link').fadeOut(300);
$('span.highlighted').removeClass('highlighted');
},
/**
* make the url absolute
*/
makeURL : function(relativeURL) {
return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL;
},
/**
* get the current relative url
*/
getCurrentURL : function() {
var path = document.location.pathname;
var parts = path.split(/\//);
$.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() {
if (this == '..')
parts.pop();
});
var url = parts.join('/');
return path.substring(url.lastIndexOf('/') + 1, path.length - 1);
},
initOnKeyListeners: function() {
$(document).keyup(function(event) {
var activeElementType = document.activeElement.tagName;
// don't navigate when in search box or textarea
if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') {
switch (event.keyCode) {
case 37: // left
var prevHref = $('link[rel="prev"]').prop('href');
if (prevHref) {
window.location.href = prevHref;
return false;
}
case 39: // right
var nextHref = $('link[rel="next"]').prop('href');
if (nextHref) {
window.location.href = nextHref;
return false;
}
}
}
});
}
};
// quick alias for translations
_ = Documentation.gettext;
$(document).ready(function() {
Documentation.init();
});
\ No newline at end of file
This diff could not be displayed because it is too large.
# append plugins to the list below, separate entries with commas
# name, description, documentation_folder
LabJack, Acquire data from UE9 devices, labjack
NILM, Non-Intrusive Load Monitoring, nilm
SmartEE, Connect to Smart Plugs, smartee
# append plugins to the list below, separate entries with commas
# name, description, documentation_folder
#LabJack, Acquire data from UE9 devices, labjack
#NILM, Non-Intrusive Load Monitoring, nilm
#SmartEE, Connect to Smart Plugs, smartee
.highlight .hll { background-color: #ffffcc }
.highlight { background: #eeffcc; }
.highlight .c { color: #408090; font-style: italic } /* Comment */
.highlight .err { border: 1px solid #FF0000 } /* Error */
.highlight .k { color: #007020; font-weight: bold } /* Keyword */
.highlight .o { color: #666666 } /* Operator */
.highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */
.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */
.highlight .cp { color: #007020 } /* Comment.Preproc */
.highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */
.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */
.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #A00000 } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #FF0000 } /* Generic.Error */
.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
.highlight .gi { color: #00A000 } /* Generic.Inserted */
.highlight .go { color: #333333 } /* Generic.Output */
.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
.highlight .gt { color: #0044DD } /* Generic.Traceback */
.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #007020 } /* Keyword.Pseudo */
.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #902000 } /* Keyword.Type */
.highlight .m { color: #208050 } /* Literal.Number */
.highlight .s { color: #4070a0 } /* Literal.String */
.highlight .na { color: #4070a0 } /* Name.Attribute */
.highlight .nb { color: #007020 } /* Name.Builtin */
.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */
.highlight .no { color: #60add5 } /* Name.Constant */
.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */
.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */
.highlight .ne { color: #007020 } /* Name.Exception */
.highlight .nf { color: #06287e } /* Name.Function */
.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */
.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #bb60d5 } /* Name.Variable */
.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #208050 } /* Literal.Number.Bin */
.highlight .mf { color: #208050 } /* Literal.Number.Float */
.highlight .mh { color: #208050 } /* Literal.Number.Hex */
.highlight .mi { color: #208050 } /* Literal.Number.Integer */
.highlight .mo { color: #208050 } /* Literal.Number.Oct */
.highlight .sa { color: #4070a0 } /* Literal.String.Affix */
.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */
.highlight .sc { color: #4070a0 } /* Literal.String.Char */
.highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */
.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */
.highlight .s2 { color: #4070a0 } /* Literal.String.Double */
.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */
.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */
.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */
.highlight .sx { color: #c65d09 } /* Literal.String.Other */
.highlight .sr { color: #235388 } /* Literal.String.Regex */
.highlight .s1 { color: #4070a0 } /* Literal.String.Single */
.highlight .ss { color: #517918 } /* Literal.String.Symbol */
.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #06287e } /* Name.Function.Magic */
.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */
.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */
.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */
.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */
.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */
\ No newline at end of file
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Index &#8212; Wattsworth 1.0 documentation</title>
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/css/custom.css" type="text/css" />
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: './',
VERSION: '1.0',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
HAS_SOURCE: true,
SOURCELINK_SUFFIX: '.txt'
};
</script>
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/underscore.js"></script>
<script type="text/javascript" src="_static/doctools.js"></script>
<link rel="index" title="Index" href="#" />
<link rel="search" title="Search" href="search.html" />
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
</head>
<body>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body" role="main">
<h1 id="index">Index</h1>
<div class="genindex-jumpbox">
</div>
</div>
</div>
</div>
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<p class="logo">
<a href="index.html">
<img class="logo" src="_static/logo.png" alt="Logo"/>
</a>
</p>
<h3>Navigation</h3>
<p class="caption"><span class="caption-text">Contents:</span></p>
<ul>
<li class="toctree-l1"><a class="reference internal" href="installation.html">Installation</a></li>
<li class="toctree-l1"><a class="reference internal" href="configuration.html">Configuration</a></li>
<li class="toctree-l1"><a class="reference internal" href="plugins.html">Plugins</a></li>
</ul>
<div class="relations">
<h3>Related Topics</h3>
<ul>
<li><a href="index.html">Documentation overview</a><ul>
</ul></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3>Quick search</h3>
<form class="search" action="search.html" method="get">
<div><input type="text" name="q" /></div>
<div><input type="submit" value="Go" /></div>
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="footer">
&copy;2017, John Donnal, James Paris.
|
Powered by <a href="http://sphinx-doc.org/">Sphinx 1.6.2</a>
&amp; <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.10</a>
</div>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Installation &#8212; Wattsworth 1.0 documentation</title>
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/css/custom.css" type="text/css" />
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: './',
VERSION: '1.0',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
HAS_SOURCE: true,
SOURCELINK_SUFFIX: '.txt'
};
</script>
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/underscore.js"></script>
<script type="text/javascript" src="_static/doctools.js"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Configuration" href="configuration.html" />
<link rel="prev" title="The Wattsworth Project" href="index.html" />
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
</head>
<body>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body" role="main">
<div class="section" id="installation">
<h1>Installation<a class="headerlink" href="#installation" title="Permalink to this headline"></a></h1>
<p>All of the software repositories are available at
<a class="reference external" href="https://git.wattsworth.net/wattsworth">https://git.wattsworth.net/wattsworth</a>. The software has been tested on
64 bit Ubuntu Linux. While it is possible to run on Arm-based Single
Board Computers (eg Raspberry Pi), the software works best on x86 systems
such as the Intel NUC.</p>
<p>Use the Puppet repository to install the complete Wattsworth stack</p>
<div class="highlight-bash"><div class="highlight"><pre><span></span>$&gt; sudo apt-get update
$&gt; sudo apt-get install puppet
$&gt; git clone https://git.wattsworth.net/wattsworth/puppet.git
$&gt; <span class="nb">cd</span> puppet
$&gt; sudo puppet apply --modulepath<span class="o">=</span>./modules --verbose site.pp
</pre></div>
</div>
</div>
</div>
</div>
</div>
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<p class="logo">
<a href="index.html">
<img class="logo" src="_static/logo.png" alt="Logo"/>
</a>
</p>
<h3>Navigation</h3>
<p class="caption"><span class="caption-text">Contents:</span></p>
<ul class="current">
<li class="toctree-l1 current"><a class="current reference internal" href="#">Installation</a></li>
<li class="toctree-l1"><a class="reference internal" href="configuration.html">Configuration</a></li>
<li class="toctree-l1"><a class="reference internal" href="plugins.html">Plugins</a></li>
</ul>
<div class="relations">
<h3>Related Topics</h3>
<ul>
<li><a href="index.html">Documentation overview</a><ul>
<li>Previous: <a href="index.html" title="previous chapter">The Wattsworth Project</a></li>
<li>Next: <a href="configuration.html" title="next chapter">Configuration</a></li>
</ul></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3>Quick search</h3>
<form class="search" action="search.html" method="get">
<div><input type="text" name="q" /></div>
<div><input type="submit" value="Go" /></div>
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="footer">
&copy;2017, John Donnal, James Paris.
|
Powered by <a href="http://sphinx-doc.org/">Sphinx 1.6.2</a>
&amp; <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.10</a>
|
<a href="_sources/installation.rst.txt"
rel="nofollow">Page source</a>
</div>
</body>
</html>
\ No newline at end of file
.. _joule-concepts:
==============
Joule Concepts
==============
.. _streams:
Streams
"""""""
Streams are timestamped data flows that connect modules together.
Streams can represent primary measurements such as readings from a current
sensor or derived measurements such as harmonic content. A stream has
one or more elements and can be viewed as a database table:
========= ======== ======== === ========
timestamp element1 element2 ... elementN
========= ======== ======== === ========
1003421 0.0 10.5 ... 2.3
1003423 1.0 -8.0 ... 2.3
1003429 8.0 12.5 ... 2.3
1003485 4.0 83.5 ... 2.3
========= ======== ======== === ========
.. _modules:
Modules
"""""""
Modules process streams. A module may receive zero, one or more
input streams and may produce zero, one, or more output streams. While
Joule does not enforce any structure on modules, we suggest
structuring your data pipeline with two types of modules: Readers, and
Filters. Readers take no inputs. They directly manage a sensor (eg a
TTY USB device) and generate an output data stream with sensor
values. Filters take these streams as inputs and produce new outputs.
Filters can be chained to produce complex behavior from simple,
reusable building blocks.
Example
"""""""
Using a light sensor and temperature sensor to detect occupancy in a room:
.. image:: /images/joule_system.png
Pipes
"""""
Pipes connect streams to modules. Pipe read and writes are asynchronous
coroutines which allows modules to effeciently manage many pipe connections
at once. The animation below shows a producer module using a pipe to communicate
with a consume module. See reference section for details on the pipe API.
.. image:: /_static/joule_pipe.gif
.. _getting-started:
===============
Getting Started
===============
Before reading this section :ref:`install Joule <installing-joule>`
and make sure you are familiar with concepts of :ref:`streams
<streams>` and :ref:`modules <modules>`. Configurations shown here use
the system defaults which expect a NilmDB repository to be avaiable at
**\http://localhost/nilmdb**. If your setup is different see
:ref:`main.conf` for the full set configuration options.
A Reader Module
---------------
In this example we will create a simple data capture pipeline that
sends two random values to a stream. We will use the builtin **joule
reader** module to generate the values. See :ref:`writing_modules` for
details on building custom modules.
We start by creating a stream configuration file for the data. Copy
the following into a new file at
**/etc/joule/stream_configs/demo_random.conf**
.. code-block:: ini
[Main]
name = Random Data
path = /demo/random
datatype = float32
keep = 1w
[Element1]
name = rand1
[Element2]
name = rand2
This will allocate a new stream in NilmDB named **/demo/random** that
holds two **float32** elements. We name the first element **rand1**
and the second element **rand2**. **Note:** *If your database has an
existing stream with this name and a different layout (datatype
and/or number of elements) you must remove it before continuing.*
Next we will set up a module to write data to this stream. **joule
reader** is a multipurpose reader module provided with Joule. It can
read values from file objects, serial ports, and more. In this
demonstration we will use it to simply generate random values. When **joule
reader** is called from the command line it prints values to stdout:
.. code-block:: bash
$> joule reader
# ... list of reader modules
$> joule reader help random
# ... help with the random module
$> joule reader random 2 10
Starting random stream: 2 elements @ 10.0Hz
1485188853650944 0.32359053067687582 0.70028608966895545
1485188853750944 0.72139550945715136 0.39218791387411422
1485188853850944 0.40728044378612194 0.26446072057019654
1485188853950944 0.61021957330250398 0.27359526775709841
# ... more output ...
Copy the following into a file at **/etc/joule/module_configs/demo_reader.conf**
.. code-block:: ini
[Main]
exec_cmd = joule reader random 2 10
name = Demo Reader
[Source]
# a reader has no inputs
[Destination]
output = /demo/random
This will create a reader module that runs **joule reader random** and pipes
the output to **/demo/random**. That's all you need to do to set up
the capture pipeline. Restart joule and check that the new module is
running:
.. code-block:: bash
$> sudo systemctl restart joule.service
# check status using joule commands
$> joule modules
+-------------+---------+--------------+---------+-----+-------------+
| Module | Sources | Destinations | Status | CPU | mem |
+-------------+---------+--------------+---------+-----+-------------+
| Demo Reader | | /demo/random | running | 0% | 33 MB (42%) |
+-------------+---------+--------------+---------+-----+-------------+
$> joule logs "Demo Reader"
[27 Jan 2017 18:05:41] ---starting module---
[27 Jan 2017 18:05:41] Starting random stream: 2 elements @ 10.0Hz
# confirm data is entering NilmDB
$> nilmtool list -E /demo/random
/demo/random
interval extents: Fri, 27 Jan 2017 # ...
total data: 1559 rows, 155.700002 seconds
A Filter Module
---------------
In this example we will connect the reader we set up above to a filter module. We will
use the builtin **joule filter** to compute the moving average of our data.
See :ref:`writing_modules` for details on building custom modules.
Start by creating a stream configuration file for the data. Copy the
following into a new file at
**/etc/joule/stream_configs/demo_filtered.conf**
.. code-block:: ini
[Main]
name = Filtered Data
path = /demo/filtered
datatype = float32
keep = 1w
[Element1]
name = filtered1
[Element2]
name = filtered2
This will allocate a new stream at **/demo/filtered** that holds two
**float32** elements. We name the first element **filtered1** and the
second element **filtered2**
Next we will set up a module that computes the moving average of **/demo/random**
and stores the output in **/demo/filtered**. **joule filter**
is a multipurpose module that can compute several different types
of filters including median, moving average, and more. When called from the command line
it will display a description of the operations it will perform on the data
.. code-block:: bash
$> joule filter
# ... list of filter modules
$> joule filter help mean
# ... help with the mean module
$> joule filter mean 9
per-element moving average with a window size of 9
To add this filter to our pipeline copy the following into a file at
**/etc/joule/module_configs/demo_filter.conf**
.. code-block:: ini
[Main]
exec_cmd = joule filter mean 9
name = Demo Filter
[Source]
input = /demo/random
[Destination]
output = /demo/filtered
This will create a filter module that runs **joule filter** using
input from **/demo/random** and storing output in
**/demo/filtered**. Now our pipeline consists of two modules: a reader
and a filter. Restart joule and check that both modules are running:
.. code-block:: bash
$> sudo systemctl restart joule.service
# check status using joule commands
$> joule modules
+-------------+--------------+----------------+---------+-----+-------------+
| Module | Sources | Destinations | Status | CPU | mem |
+-------------+--------------+----------------+---------+-----+-------------+
| Demo Reader | | /demo/random | running | 0% | 33 MB (42%) |
| Demo Filter | /demo/random | /demo/filtered | running | 0% | 53 MB (68%) |
+-------------+--------------+----------------+---------+-----+-------------+
$> joule logs "Demo Reader"
[27 Jan 2017 18:22:48] ---starting module---
[27 Jan 2017 18:22:48] Starting random stream: 2 elements @ 10.0Hz
$> joule logs "Demo Filter"
[27 Jan 2017 18:22:48] ---starting module---
[27 Jan 2017 18:22:48] Starting moving average filter with window size 9
# confirm data is entering NilmDB
$> nilmtool list -E -n /demo/*
/demo/filtered
interval extents: Fri, 27 Jan 2017 # ...
total data: 132 rows, 13.100001 seconds
/demo/random
interval extents: Fri, 27 Jan 2017 # ...
total data: 147 rows, 14.600001 seconds
.. Joule documentation master file, created by
sphinx-quickstart on Fri Jan 6 17:16:21 2017.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Joule: Modular Data Processing
=================================
Joule is a data capture and signal processing engine. It allows you to
turn a single board computer like the Raspberry Pi into a robust
sensor platform. Joule uses modules to build complex acquisition and
signal processing workflows from simple building blocks. Modules are
user defined processes that are connected together by data streams.
Joule acts as a process manager, ensuring that modules start at system
boot and are restarted if they fail. Joule also collects runtime
statistics and logs for each module making it easy to detect
bugs and find bottlenecks in processing pipelines.
.. toctree::
:maxdepth: 3
concepts
getting_started
writing_modules
testing_modules
nilmdb
reference
Contributing & Running Tests
----------------------------
Contribution is always welcome. Please include tests with your pull request.
Unittests can be run using nose2, see **joule/htmlcov** for code coverage.
.. code-block:: bash
$> cd joule
$> nose2 # run all unittests
End to end tests are run from the **tests/e2e** directory and require
docker-compose and the NilmDB container. See
https://docs.docker.com/compose/install/ for details on installing
docker-compose. The NilmDB container is available by request on `Docker Hub`_.
.. code-block:: bash
$> cd test/e2e
$> ./runner.sh # run end-to-end tests
.. _Docker Hub: https://hub.docker.com/
.. _installing-joule:
============
Installation
============
Joule requires NilmDb for stream storage [#f1]_. The other dependency is Python 3.5. On distros
that do not ship with Python 3.5 such as Raspbian it must be built from source.
Install NilmDb
--------------------
NilmDb is accessible from the Wattsworth git repository. Contact
donnal@usna.edu to request access. From the terminal, run the
following commands to install and configure NilmDb.
Install dependencies
.. code-block:: bash
$> sudo apt-get update
$> sudo apt-get install cython git build-essential \
python2.7 python2.7-dev python-setuptools python-pip \
python-cherrypy3 python-decorator python-requests \
python-dateutil python-tz python-progressbar python-psutil \
python-simplejson apache2 libapache2-mod-wsgi -y
Install NilmDb
.. code-block:: bash
$> git clone https://git.wattsworth.net/wattsworth/nilmdb.git
$> cd nilmdb
$> sudo make install
*Configure WSGI Scripts*
Create a directory for NilmDB (eg **/opt/nilmdb**), in this directory
create a file **nilmdb.wsgi** as shown below:
.. code-block:: python
import nilmdb.server
application = nilmdb.server.wsgi_application("/opt/nilmdb/db","/nilmdb")
This will create a virtual host at **http://localhost/nilmdb** with data stored
in the directory **/opt/nilmdb/db**. Now create a user to run Nilmdb and give them
permissions on the directory:
.. code-block:: bash
$> adduser nilmdb
$> sudo chown -R nilmdb:nilmdb /opt/nilmdb
*Configure Apache*
Edit the default virtual host configuration at
**/etc/apache2/site-available/000-default** to add the following lines
within the ``<VirtualHost>`` directive:
.. code-block:: apache
<VirtualHost *:80>
# Add these 6 lines
WSGIScriptAlias /nilmdb /opt/nilmdb/nilmdb.wsgi
WSGIDaemonProcess nilmdb-procgroup threads=32 user=nilmdb group=nilmdb
<Location /nilmdb>
WSGIProcessGroup nilmdb-procgroup
WSGIApplicationGroup nilmdb-appgroup
Require all granted
</Location>
# (other existing configuration here)
</VirtualHost>
Note these values assume you followed the user creation and file
naming guidelines above.
*Test the Installation*
Restart Apache to start the NilmDB server, then check the database is
available using the **nilmtool** command.
.. code-block:: bash
$> sudo apache2ctl restart
$> nilmtool info
Client version: 1.10.3
#...more information
Docker
^^^^^^
NilmDb is also available as a container through
Docker Hub. E-mail donnal@usna.edu for access. See the `docker homepage
<https://www.docker.com/>`_ for
more information on containers.
.. code-block:: bash
$> docker pull jdonnal/nilmdb
Install Python 3.5
------------------
Joule requires Python 3.5 or greater. As of this writing many distros including
Raspbian ship with earlier versions. Check your version by running
the following command:
.. code-block:: bash
$> sudo apt-get install python3 python3-pip -y
$> python3 -V
Python 3.5.2 #<--- this version is ok
If your version is 3.5.2 or greater, skip ahead to installing Joule, otherwise you
must build Python 3.5 from source by following the instructions below:
Install Dependencies
.. code-block:: bash
$> sudo apt-get install build-essential tk-dev libssl-dev libblas-dev \
liblapack-dev libbz2-dev gfortran libsqlite3-dev
Download and Install Source
.. code-block:: bash
$> wget https://www.python.org/ftp/python/3.5.2/Python-3.5.2.tgz
$> tar -xvf Python-3.5.2.tgz
$> cd Python-3.5.2
$> ./configure
$> make
$> sudo make install
This will install python3.5 into **/usr/local/bin**
Install matplotlib, numpy and scipy. These must be built from source so you need to
get the dependencies before running pip. Edit ``/etc/apt/sources.list`` to include a
source entry like ``deb-src``. Then run the following commands:
.. code-block:: bash
$> sudo apt-get build-dep python3-scipy python3-numpy python3-matplotlib
$> sudo pip3 install numpy matplotlib scipy
VirtualEnv
^^^^^^^^^^
You may optionally install Joule into a virtual environment, this is
recommended if you expect Joule to conflict with other Python tools
you already have installed. The easiest way to work with virtual
environments is with *virtualenvwrapper*
.. code-block:: bash
$> pip2 install virtualenv virtualenvwrapper
$> export WORKON_HOME=~/Envs
$> source /usr/local/bin/virtualenvwrapper.sh
(`Full virtualenvwrapper install
instructions. <https://virtualenvwrapper.readthedocs.io/en/latest/install.html>`_)
Create a new virtual environment using Python 3.5
.. code-block:: bash
$> mkvirtualenv joule -p /usr/local/bin/python3.5 #<-- path to 3.5 installation
$> workon joule
Install Joule
-------------
Joule is accessible form the Wattsworth git repository. Contact
donnal@usna.edu to request access. From the terminal, run the
following commands to install and configure Joule.
.. code-block:: bash
# install dependencies
# if python3.5 was installed from source you must specify the correct pip
# (ie /usr/local/bin/pip3.5 instead of pip3)
$> pip3 install --upgrade pip # make sure pip is up to date
$> pip3 install python-datetime-tz
$> apt-get install python3-numpy python3-scipy python3-yaml -y
.. code-block:: bash
# install Joule
$> git clone https://git.wattsworth.net/wattsworth/joule.git
$> cd joule
$> python3 setup.py install
*Configure Joule*
By default joule looks for configuration files at **/etc/joule**. Run
the following commands to create the basic directory structure
.. code-block:: bash
$> sudo mkdir -p /etc/joule/module_configs
$> sudo mkdir -p /etc/joule/stream_configs
$> sudo touch /etc/joule/main.conf
*Create Startup Scripts*
To configure Joule to run automatically you must add a configuration script to systemd. Copy the following into **/etc/systemd/system/joule.service**
.. code-block:: ini
[Unit]
Description = "Joule Management Daemon"
After = syslog.target
[Service]
Type = simple
# **note: path will be different if joule is in a virtualenv**
ExecStart = /usr/local/bin/jouled
StandardOutput = journal
StandardError = journal
Restart = always
[Install]
WantedBy = multi-user.target
To enable and start the joule service run
.. code-block:: bash
$> sudo systemctl enable joule.service
$> sudo systemctl start joule.service
Verify Installation
-------------------
Check that joule is running:
.. code-block:: bash
$> sudo systemctl status joule.service
● joule.service - "Joule Management Daemon"
Loaded: loaded (/etc/systemd/system/joule.service; enabled)
Active: active (running) since Tue 2017-01-17 09:53:21 EST; 7s ago
Main PID: 2296 (jouled)
CGroup: /system.slice/joule.service
└─2296 /usr/local/bin/python3 /usr/local/bin/jouled
Joule is managed from the terminal using the **joule** command, on a fresh
installation there is nothing for Joule to do so these commands will not return
data. Check that they are available by printing the help output.
.. code-block:: bash
$> joule modules -h
# ... some help text
$> joule logs -h
# ... some help text
Your Joule installation should be ready to go, read
:ref:`getting-started` to configure your first module and start
capturing data.
.. [#f1] A local installation of NilmDb is not strictly necessary as all
communication between Joule and NilmDb occurs over HTTP but sending
all stream data over a network connection to a remote NilmDb instance
may impact performance.
.. [#f2] These commands assume **python3** points to a Python
3.5 or later instance. If your system **python3** is earlier than 3.5
work in a virtual environment or adjust your environment to reference
the python3.5 binaries in **/usr/local/bin/**
============
NILM Modules
============
NILM modules provides a suite of reader and filter modules for
non-intrusive power monitors. The system is designed to be run using
a YAML configuration file located at **/opt/configs/meters.yml** although
the modules can be configured to run independently.
Installation
------------
NILM Modules is accessible from the Wattsworth git repository. Contact
donnal@usna.edu to request access. From the terminal, run the
following commands to install and configure NILM Modules
.. code-block:: bash
$> git clone https://git.wattsworth.net/wattsworth/nilm.git
$> cd nilm
$> sudo python3 setup.py install
Configuration
-------------
Set up a **meters.yml** file according to the guidelines at wattsworth.net for a `contact meter`_
or a `noncontact meter`_. Then run the following from the command line
.. code-block:: bash
$> nilm configure
This will install stream and module configurations into **/etc/joule/**. Each NILM meter
will have four streams located in **/meter#**
.. code-block:: none
/meter#/sensor
float32 stream of raw ADC sensor values
/meter#/iv
current and voltage at the sample rate of the sensor
/meter#/sinefit
zero crossings of the voltage waveform (freq, amplitude, offset)
/meter#/prep-a
/meter#/prep-b
/meter#/prep-c
harmonic envelopes of real and reactive power for each phase
Each NILM has a reader module **meter#_capture** and a filter module
**meter#_process**. The modules read configuration values from the
**meters.yml** file.
Verify Operation
----------------
To begin using the newly installed modules restart the **jouled** service by
running the following command:
.. code-block:: bash
$> sudo systemctl restart joule.service
Verify that the modules are running using the **joule modules** command.
.. code-block:: bash
$> joule modules
+--------------------------------+----------------+-----------------+---------+-----+--------------+
| Module | Sources | Destinations | Status | CPU | mem |
+--------------------------------+----------------+-----------------+---------+-----+--------------+
| meter4 process: | /meter4/sensor | /meter4/prep-a | running | 39% | 30 MB (355%) |
| reconstruct -> sinefit -> prep | | /meter4/prep-b | | | |
| | | /meter4/prep-c | | | |
| | | /meter4/iv | | | |
| | | /meter4/sinefit | | | |
| meter4 capture: | | /meter4/sensor | running | 8% | 28 MB (336%) |
| serial data capture | | | | | |
+--------------------------------+----------------+-----------------+---------+-----+--------------+
Any errors will be reported in the log files for each module. Use the
**joule logs** command to print recent log entries. The logs are
automatically rotated: see the ProcDB:MaxLogLines parameter in :ref:`main.conf`
.. code-block:: bash
$> joule logs "meter4 capture"
[23 Jan 2017 16:14:56] ---starting module---
$> joule logs "meter4 process"
[23 Jan 2017 16:14:56] ---starting module---
Check that the data is entering NilmDB using the **nilmtool** command. Joule inserts data periodically, see NilmDB:InsertionPeriod in :ref:`main.conf`
.. code-block:: bash
$> nilmtool list -En /meter4/prep*
/meter4/prep-a
interval extents: Mon, 23 Jan 2017 16:11:01.833447 -0500 -> Mon, 23 Jan 2017 16:16:29.322283 -0500
total data: 18054 rows, 300.878769 seconds
/meter4/prep-b
interval extents: Mon, 23 Jan 2017 16:11:01.833447 -0500 -> Mon, 23 Jan 2017 16:16:29.322283 -0500
total data: 18054 rows, 300.878769 seconds
/meter4/prep-c /meter4/prep-a
interval extents: Mon, 23 Jan 2017 16:11:01.833447 -0500 -> Mon, 23 Jan 2017 16:16:29.322283 -0500
total data: 18054 rows, 300.878769 seconds
.. _contact meter: https://www.wattsworth.net/help/software#config-contact
.. _noncontact meter: https://www.wattsworth.net/help/software#config-noncontact
Reference
===============
.. contents:: :local:
Command Line Utilities
----------------------
joule
'''''
jouled
''''''
nilmtool
''''''''
Configuration Files
-------------------
.. _main.conf:
main.conf
'''''''''
Joule uses a set of default configurations that should work for most
cases. These defaults can be customized by editing
**/etc/joule/main.conf**. Start joule with the **--config** flag to use a configuration file at
an alternate location. The example **main.conf** below shows the
full set of options and their default settings:
.. code-block:: ini
[NilmDB]
url = http://localhost/nilmdb
InsertionPeriod = 5
[ProcDB]
DbPath = /tmp/joule-proc-db.sqlite
MaxLogLines = 100
[Jouled]
ModuleDirectory = /etc/joule/module_configs
StreamDirectory = /etc/joule/stream_configs
See the list below for information on each setting.
``NilmDB``
* ``url``: address of NilmDB server
* ``InsertionPeriod``: how often to send stream data to NilmDB (in seconds)
``ProcDB``
* ``DbPath``: path to sqlite database used internally by joule
* ``MaxLogLines``: max number of lines to keep in a module log file (automatically rolls)
``Jouled``
* ``ModuleDirectory``: folder with module configuration files (absolute path)
* ``StreamDirectory``: folder with stream configuration files (absolute path)
stream configs
''''''''''''''
.. code-block:: ini
[Main]
#required settings (examples)
path = /nilmdb/path/name
datatype = float32
keep = 1w
#optional settings (defaults)
decimate = yes
[Element1...ElementN]
#required settings (examples)
name = Element Name
#optional settings (defaults)
plottable = yes
discrete = no
offset = 0.0
scale_factor = 1.0
default_max = null
default_min = null
module configs
''''''''''''''
.. code-block:: ini
[Main]
#required
name = module name
exec_cmd = /path/to/executable --args
#optional
description = a short description
[Source]
path1 = /nilmdb/input/stream1
path2 = /nilmdb/input/stream2
# additional sources...
[Destination]
path1 = /nilmdb/output/stream1
path2 = /nilmdb/output/stream2
# additional destinations...
.. _numpy_pipes:
Numpy Pipes
-----------
Concepts
''''''''
Methods
'''''''
E2E Utilities
-------------
joule
'''''
nilmtool
''''''''
.. _writing_modules:
===============
Writing Modules
===============
Modules are standalone processes managed by Joule. They are
connected to eachother and to the backing NilmDB datastore by
streams. Modules can have zero or more input streams and one or more
output streams. Joule does not impose any additional constraints on
modules but we recommend structuring modules using the reader
and filter patterns. See :ref:`joule-concepts` for more details.
The sections below show you how to write a custom reader or
filter by extending the **ReaderModule** and **FilterModule**
built-ins. This is recommended for most use cases. Before continuing,
clone the starter repository:
.. code-block:: bash
$> git clone https://git.wattsworth.net/wattsworth/example_modules.git
$> cd example_modules
# install nose2 and asynctest module to run tests
$> sudo pip3 install nose2 asynctest
This repository contains an example reader and filter as well as unit
testing and end to end testing infrastructure. Proper testing is
critical to designing complex modules, especially filters.
Custom Readers
--------------
This section explains how to use the **ReaderModule** class to develop
a custom reader module. The code snippets in this section refer to the
**ReaderDemo** module defined in **reader.py** from the example_modules
repository.
A reader module should extend from the base class **ReaderModule**, and
provide custom functionality by overriding the parent methods:
.. code-block:: python
from joule.client import ReaderModule
class ReaderDemo(ReaderModule):
"Example reader: generates incrementing values at user specified rate"
#...your code here...
The module should provide a custom ``__init__`` function which calls
the parent, and may define two special properties: **description** and
**help**. The description property should be a short one line summary of the module
eg "reads data from serial port", and the help property should be a longer, multiline
string explaining what the module does and how to use it, preferably with a usage example.
These properties are used by the **joule reader** help system.
You may also add any additional initialization code your module requires.
.. code-block:: python
def __init__(self):
super(ReaderDemo, self).__init__("Demo Reader")
self.description = "one line: demo reader"
self.help = "a paragraph: this reader does x,y,z etc..."
#...your custom initialization...
The module must also provide an implementation of
``custom_args``. This function receives a parser object and may add
custom arguments to it. See the `argparse documentation
<https://docs.python.org/3/library/argparse.html>`_ for details on
adding arguments to the parser. These arguments will be available to
the ``run`` function.
.. code-block:: python
def custom_args(self, parser):
parser.add_argument("rate", type=float, help="period in seconds")
#...additional arguments as required...
# ... or, if your module does not require arguments
def custom_args(self, parser):
pass
Finally, the module must implement ``run``. This function performs the
actual work in the module. It is an asynchronous coroutine but you
can generally treat it as a normal function. See the `asyncio documenation
<https://docs.python.org/3/library/asyncio.html>`_ for details on
coroutines.
.. code-block:: python
async def run(self, parsed_args, output): #<-- this is a coroutine
count = 0
while(1):
await output.write(np.array([[time_now(), count]])) #<--note await syntax
await asyncio.sleep(parsed_args.rate) #can also use time.sleep()
count += 1
This function takes two parameters, **parsed_args** and
**output**. The parsed_args is a namespace object with values for the
arguments specified in **custom_args**. **output** is a NumpyPipe that
connects the module to the joule system (see :ref:`numpy_pipes`). The
pipe has a single function, **write** which accepts a numpy array.
The array should be a matrix of timestamps and values, if you are
inserting a single sample, enclose the matrix in double braces to
provide the correct dimension. Also note that the **write** method is
a coroutine and must be called with the **await** keyword.
.. code-block:: python
data = np.array([[ts, val, val, val, ...],
[ts, val, val, val, ...],
....])
await output.write(data)
If you run the filter from the command line it will print values to stdout. This can help
debug your code. Additionally it is best practice to provide unittests for your custom reader
modules. An example is provided in **test_reader.py**. See :ref:`unit_testing` for more details.
Custom Filters
--------------
This section explains how to use the **FilterModule** class to develop
a custom filter module. The code snippets in this section refer to the
**FilterDemo** module defined in **filter.py** from the example_modules
repository.
A filter module should extend from the base class **FilterModule**, and
provide custom functionality by overriding the parent methods:
.. code-block:: python
from joule.client import FilterModule
class FilterDemo(FilterModule):
" Example filter: applies a dc offset "
#...your code here...
The module should provide a custom ``__init__`` function which calls
the parent, and may define two special properties: **description** and
**help**. The description property should be a short one line summary of the module
eg "computes a moving average", and the help property should be a longer, multiline
string explaining what the module does and how to use it, preferably with a usage example.
These properties are used by the **joule filter** help system.
You may also add any additional initialization code your module requires.
.. code-block:: python
def __init__(self):
super(ReaderDemo, self).__init__("Demo Reader")
self.description = "one line: demo reader"
self.help = "a paragraph: this reader does x,y,z etc..."
#...your custom initialization...
The module must also provide an implementation of
``custom_args``. This function receives a parser object and may add
custom arguments to it. See the `argparse documentation
<https://docs.python.org/3/library/argparse.html>`_ for details on
adding arguments to the parser. These arguments will be available to
the ``run`` function.
.. code-block:: python
def custom_args(self, parser):
parser.add_argument("offset", type=float, default=0,
help="apply an offset")
#...additional arguments as required...
# ... or, if your module does not require arguments
def custom_args(self, parser):
pass
Finally, the module must implement ``run``. This function performs the
actual work in the module. It is an asynchronous coroutine but for the most part you
can treat it as a normal function. See the `asyncio documenation
<https://docs.python.org/3/library/asyncio.html>`_ for details on
coroutines.
.. code-block:: python
async def run(self, parsed_args, inputs, outputs): #<-- this is a coroutine
stream_in = inputs["input"] #<--access pipes by name
stream_out = outputs["output"]
while(1):
sarray = await stream_in.read() #<--note await syntax
sarray["data"] += parsed_args.offset
await stream_out.write(sarray) #<--note await syntax
stream_in.consume(len(sarray)) #<--indicates
This function takes three parameters, **parsed_args**, **inputs**, and
**outputs**. The parsed_args is a namespace object with values for the
arguments specified in **custom_args**. **inputs** and **outputs** are
dictionaries of NumpyPipes indexed the names specified in the module
configuration file. These pipes connect the module to the joule system.
.. code-block:: ini
[Main]
exec_cmd = python3 filter.py
name = Demo Filter
[Source]
input = /demo/raw #<--name used in inputs dictionary
[Destination]
output = /demo/filtered #<--name used in outputs dictionary
The input pipes have two functions, **read** and **consume**. Access
data in the pipe using the read function which is a coroutine. This
returns a structured Numpy array by default, if you would like a
flattened array, set the optional parameter flatten.
.. code-block:: python
values = await stream_in.read()
# returns a structured array
# values['timestamp'] = [ts, ts, ts, ..., ts]
# values['data'] = [[val1, val2, val3, ..., valN],
# [val1, val2, val3, ..., valN],...]
values = await stream_in.read(flatten=True)
# returns a flat array
# values = [[ts, val1, val2, val3, ..., valN],
[ts, val1, val2, val3, ..., valN],...]
Every call to **read** should followed by **consume** to indicate how
much of the data your module has used. The next call to **read** will
prepend any unconsumed data from the previous read. This allows you to
design filters which operate on only a portion of the input data such
as linear filters. See the built-in **mean** and **median** filters
for an example of using a portion of the input data.
The **ouput** pipes have a single function **write** which accepts
a Numpy array. See the ReaderModule section for more details on output pipes.
Unlike ReaderModules, modules derived from FilterModule cannot be run
from the command line because filters require an input stream provided
by the joule environment.You should always verify your modules using
unittests. The testing framework provides mock input streams to test
modules in isolation. An example is provided in **test_filter.py**. See
:ref:`unit_testing` for more details.
.command-title {
background: #DDD;
padding: 8px;
border-radius: 4px;
margin-top: 40px;
border-top: black;
border-style: solid;
border-width: 1px
}
.command-title>i {
font-style: normal;
font-weight: bold;
font-family: monospace;
}
.text-mono {
font-family: monospace;
}
.block-indent {
margin-left: 20px;
}
.text-grey {
color: #808080;
}
.arglist>dd {
margin: 0px;
margin-left: 30px;
}
.arglist>dt {
color: #808080;
font-weight: normal;
font-family: monospace;
}
.arglist {
margin-top: 0px;
margin-bottom: 0px;
}
.plainlist {
margin-bottom: 0px;
}
.plainlist>dd {
margin-left: 30px;
}
.plainlist>dt {
font-weight: normal;
font-family: monospace;
}
.plug-cli {
font-family: monospace;
background: #DDD;
display: block;
margin: 5px;
padding: 5px;
border-radius: 4px;
border-style: solid;
border-width: 1px;
border-color: #BBB
}
.mono-dl>dt {
font-family: monospace;
}
.arglist.dl-horizontal>dt {
width: inherit;
}
.arglist.dl-horizontal>dd {
margin-left: 130px;
}
.command {
font-family: monospace;
color: #808080
}
.command>em {
font-weight: bold;
font-style: normal;
color: black;
}
/* This file intentionally left blank. */
/*
* doctools.js
* ~~~~~~~~~~~
*
* Sphinx JavaScript utilities for all documentation.
*
* :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS.
* :license: BSD, see LICENSE for details.
*
*/
/**
* select a different prefix for underscore
*/
$u = _.noConflict();
/**
* make the code below compatible with browsers without
* an installed firebug like debugger
if (!window.console || !console.firebug) {
var names = ["log", "debug", "info", "warn", "error", "assert", "dir",
"dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace",
"profile", "profileEnd"];
window.console = {};
for (var i = 0; i < names.length; ++i)
window.console[names[i]] = function() {};
}
*/
/**
* small helper function to urldecode strings
*/
jQuery.urldecode = function(x) {
return decodeURIComponent(x).replace(/\+/g, ' ');
};
/**
* small helper function to urlencode strings
*/
jQuery.urlencode = encodeURIComponent;
/**
* This function returns the parsed url parameters of the
* current request. Multiple values per key are supported,
* it will always return arrays of strings for the value parts.
*/
jQuery.getQueryParameters = function(s) {
if (typeof s == 'undefined')
s = document.location.search;
var parts = s.substr(s.indexOf('?') + 1).split('&');
var result = {};
for (var i = 0; i < parts.length; i++) {
var tmp = parts[i].split('=', 2);
var key = jQuery.urldecode(tmp[0]);
var value = jQuery.urldecode(tmp[1]);
if (key in result)
result[key].push(value);
else
result[key] = [value];
}
return result;
};
/**
* highlight a given string on a jquery object by wrapping it in
* span elements with the given class name.
*/
jQuery.fn.highlightText = function(text, className) {
function highlight(node) {
if (node.nodeType == 3) {
var val = node.nodeValue;
var pos = val.toLowerCase().indexOf(text);
if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) {
var span = document.createElement("span");
span.className = className;
span.appendChild(document.createTextNode(val.substr(pos, text.length)));
node.parentNode.insertBefore(span, node.parentNode.insertBefore(
document.createTextNode(val.substr(pos + text.length)),
node.nextSibling));
node.nodeValue = val.substr(0, pos);
}
}
else if (!jQuery(node).is("button, select, textarea")) {
jQuery.each(node.childNodes, function() {
highlight(this);
});
}
}
return this.each(function() {
highlight(this);
});
};
/*
* backward compatibility for jQuery.browser
* This will be supported until firefox bug is fixed.
*/
if (!jQuery.browser) {
jQuery.uaMatch = function(ua) {
ua = ua.toLowerCase();
var match = /(chrome)[ \/]([\w.]+)/.exec(ua) ||
/(webkit)[ \/]([\w.]+)/.exec(ua) ||
/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) ||
/(msie) ([\w.]+)/.exec(ua) ||
ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) ||
[];
return {
browser: match[ 1 ] || "",
version: match[ 2 ] || "0"
};
};
jQuery.browser = {};
jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true;
}
/**
* Small JavaScript module for the documentation.
*/
var Documentation = {
init : function() {
this.fixFirefoxAnchorBug();
this.highlightSearchWords();
this.initIndexTable();
},
/**
* i18n support
*/
TRANSLATIONS : {},
PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; },
LOCALE : 'unknown',
// gettext and ngettext don't access this so that the functions
// can safely bound to a different name (_ = Documentation.gettext)
gettext : function(string) {
var translated = Documentation.TRANSLATIONS[string];
if (typeof translated == 'undefined')
return string;
return (typeof translated == 'string') ? translated : translated[0];
},
ngettext : function(singular, plural, n) {
var translated = Documentation.TRANSLATIONS[singular];
if (typeof translated == 'undefined')
return (n == 1) ? singular : plural;
return translated[Documentation.PLURALEXPR(n)];
},
addTranslations : function(catalog) {
for (var key in catalog.messages)
this.TRANSLATIONS[key] = catalog.messages[key];
this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')');
this.LOCALE = catalog.locale;
},
/**
* add context elements like header anchor links
*/
addContextElements : function() {
$('div[id] > :header:first').each(function() {
$('<a class="headerlink">\u00B6</a>').
attr('href', '#' + this.id).
attr('title', _('Permalink to this headline')).
appendTo(this);
});
$('dt[id]').each(function() {
$('<a class="headerlink">\u00B6</a>').
attr('href', '#' + this.id).
attr('title', _('Permalink to this definition')).
appendTo(this);
});
},
/**
* workaround a firefox stupidity
* see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075
*/
fixFirefoxAnchorBug : function() {
if (document.location.hash)
window.setTimeout(function() {
document.location.href += '';
}, 10);
},
/**
* highlight the search words provided in the url in the text
*/
highlightSearchWords : function() {
var params = $.getQueryParameters();
var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : [];
if (terms.length) {
var body = $('div.body');
if (!body.length) {
body = $('body');
}
window.setTimeout(function() {
$.each(terms, function() {
body.highlightText(this.toLowerCase(), 'highlighted');
});
}, 10);
$('<p class="highlight-link"><a href="javascript:Documentation.' +
'hideSearchWords()">' + _('Hide Search Matches') + '</a></p>')
.appendTo($('#searchbox'));
}
},
/**
* init the domain index toggle buttons
*/
initIndexTable : function() {
var togglers = $('img.toggler').click(function() {
var src = $(this).attr('src');
var idnum = $(this).attr('id').substr(7);
$('tr.cg-' + idnum).toggle();
if (src.substr(-9) == 'minus.png')
$(this).attr('src', src.substr(0, src.length-9) + 'plus.png');
else
$(this).attr('src', src.substr(0, src.length-8) + 'minus.png');
}).css('display', '');
if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) {
togglers.click();
}
},
/**
* helper function to hide the search marks again
*/
hideSearchWords : function() {
$('#searchbox .highlight-link').fadeOut(300);
$('span.highlighted').removeClass('highlighted');
},
/**
* make the url absolute
*/
makeURL : function(relativeURL) {
return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL;
},
/**
* get the current relative url
*/
getCurrentURL : function() {
var path = document.location.pathname;
var parts = path.split(/\//);
$.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() {
if (this == '..')
parts.pop();
});
var url = parts.join('/');
return path.substring(url.lastIndexOf('/') + 1, path.length - 1);
},
initOnKeyListeners: function() {
$(document).keyup(function(event) {
var activeElementType = document.activeElement.tagName;
// don't navigate when in search box or textarea
if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') {
switch (event.keyCode) {
case 37: // left
var prevHref = $('link[rel="prev"]').prop('href');
if (prevHref) {
window.location.href = prevHref;
return false;
}
case 39: // right
var nextHref = $('link[rel="next"]').prop('href');
if (nextHref) {
window.location.href = nextHref;
return false;
}
}
}
});
}
};
// quick alias for translations
_ = Documentation.gettext;
$(document).ready(function() {
Documentation.init();
});
\ No newline at end of file
This diff could not be displayed because it is too large.
.highlight .hll { background-color: #ffffcc }
.highlight { background: #eeffcc; }
.highlight .c { color: #408090; font-style: italic } /* Comment */
.highlight .err { border: 1px solid #FF0000 } /* Error */
.highlight .k { color: #007020; font-weight: bold } /* Keyword */
.highlight .o { color: #666666 } /* Operator */
.highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */
.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */
.highlight .cp { color: #007020 } /* Comment.Preproc */
.highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */
.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */
.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #A00000 } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #FF0000 } /* Generic.Error */
.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
.highlight .gi { color: #00A000 } /* Generic.Inserted */
.highlight .go { color: #333333 } /* Generic.Output */
.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
.highlight .gt { color: #0044DD } /* Generic.Traceback */
.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #007020 } /* Keyword.Pseudo */
.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #902000 } /* Keyword.Type */
.highlight .m { color: #208050 } /* Literal.Number */
.highlight .s { color: #4070a0 } /* Literal.String */
.highlight .na { color: #4070a0 } /* Name.Attribute */
.highlight .nb { color: #007020 } /* Name.Builtin */
.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */
.highlight .no { color: #60add5 } /* Name.Constant */
.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */
.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */
.highlight .ne { color: #007020 } /* Name.Exception */
.highlight .nf { color: #06287e } /* Name.Function */
.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */
.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #bb60d5 } /* Name.Variable */
.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #208050 } /* Literal.Number.Bin */
.highlight .mf { color: #208050 } /* Literal.Number.Float */
.highlight .mh { color: #208050 } /* Literal.Number.Hex */
.highlight .mi { color: #208050 } /* Literal.Number.Integer */
.highlight .mo { color: #208050 } /* Literal.Number.Oct */
.highlight .sa { color: #4070a0 } /* Literal.String.Affix */
.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */
.highlight .sc { color: #4070a0 } /* Literal.String.Char */
.highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */
.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */
.highlight .s2 { color: #4070a0 } /* Literal.String.Double */
.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */
.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */
.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */
.highlight .sx { color: #c65d09 } /* Literal.String.Other */
.highlight .sr { color: #235388 } /* Literal.String.Regex */
.highlight .s1 { color: #4070a0 } /* Literal.String.Single */
.highlight .ss { color: #517918 } /* Literal.String.Symbol */
.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #06287e } /* Name.Function.Magic */
.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */
.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */
.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */
.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */
.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */
\ No newline at end of file
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Joule Concepts &#8212; Joule 1.0.0 documentation</title>
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/css/custom.css" type="text/css" />
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: './',
VERSION: '1.0.0',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
HAS_SOURCE: true,
SOURCELINK_SUFFIX: '.txt'
};
</script>
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/underscore.js"></script>
<script type="text/javascript" src="_static/doctools.js"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="search.html" />
<link rel="next" title="Installation" href="install.html" />
<link rel="prev" title="Joule: Modular Data Processing" href="index.html" />
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
</head>
<body>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body" role="main">
<div class="section" id="joule-concepts">
<span id="id1"></span><h1>Joule Concepts<a class="headerlink" href="#joule-concepts" title="Permalink to this headline"></a></h1>
<div class="section" id="streams">
<span id="id2"></span><h2>Streams<a class="headerlink" href="#streams" title="Permalink to this headline"></a></h2>
<p>Streams are timestamped data flows that connect modules together.
Streams can represent primary measurements such as readings from a current
sensor or derived measurements such as harmonic content. A stream has
one or more elements and can be viewed as a database table:</p>
<blockquote>
<div><table border="1" class="docutils">
<colgroup>
<col width="25%" />
<col width="22%" />
<col width="22%" />
<col width="8%" />
<col width="22%" />
</colgroup>
<thead valign="bottom">
<tr class="row-odd"><th class="head">timestamp</th>
<th class="head">element1</th>
<th class="head">element2</th>
<th class="head"></th>
<th class="head">elementN</th>
</tr>
</thead>
<tbody valign="top">
<tr class="row-even"><td>1003421</td>
<td>0.0</td>
<td>10.5</td>
<td></td>
<td>2.3</td>
</tr>
<tr class="row-odd"><td>1003423</td>
<td>1.0</td>
<td>-8.0</td>
<td></td>
<td>2.3</td>
</tr>
<tr class="row-even"><td>1003429</td>
<td>8.0</td>
<td>12.5</td>
<td></td>
<td>2.3</td>
</tr>
<tr class="row-odd"><td>1003485</td>
<td>4.0</td>
<td>83.5</td>
<td></td>
<td>2.3</td>
</tr>
</tbody>
</table>
</div></blockquote>
</div>
<div class="section" id="modules">
<span id="id3"></span><h2>Modules<a class="headerlink" href="#modules" title="Permalink to this headline"></a></h2>
<p>Modules process streams. A module may receive zero, one or more
input streams and may produce zero, one, or more output streams. While
Joule does not enforce any structure on modules, we suggest
structuring your data pipeline with two types of modules: Readers, and
Filters. Readers take no inputs. They directly manage a sensor (eg a
TTY USB device) and generate an output data stream with sensor
values. Filters take these streams as inputs and produce new outputs.
Filters can be chained to produce complex behavior from simple,
reusable building blocks.</p>
</div>
<div class="section" id="example">
<h2>Example<a class="headerlink" href="#example" title="Permalink to this headline"></a></h2>
<p>Using a light sensor and temperature sensor to detect occupancy in a room:</p>
<img alt="_images/joule_system.png" src="_images/joule_system.png" />
</div>
<div class="section" id="pipes">
<h2>Pipes<a class="headerlink" href="#pipes" title="Permalink to this headline"></a></h2>
<p>Pipes connect streams to modules. Pipe read and writes are asynchronous
coroutines which allows modules to effeciently manage many pipe connections
at once. The animation below shows a producer module using a pipe to communicate
with a consume module. See reference section for details on the pipe API.</p>
<img alt="_images/joule_pipe.gif" src="_images/joule_pipe.gif" />
</div>
</div>
</div>
</div>
</div>
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<p class="logo">
<a href="index.html">
<img class="logo" src="_static/logo.png" alt="Logo"/>
</a>
</p>
<h3>Navigation</h3>
<ul class="current">
<li class="toctree-l1 current"><a class="current reference internal" href="#">Joule Concepts</a><ul>
<li class="toctree-l2"><a class="reference internal" href="#streams">Streams</a></li>
<li class="toctree-l2"><a class="reference internal" href="#modules">Modules</a></li>
<li class="toctree-l2"><a class="reference internal" href="#example">Example</a></li>
<li class="toctree-l2"><a class="reference internal" href="#pipes">Pipes</a></li>
</ul>
</li>
<li class="toctree-l1"><a class="reference internal" href="install.html">Installation</a></li>
<li class="toctree-l1"><a class="reference internal" href="getting_started.html">Getting Started</a></li>
<li class="toctree-l1"><a class="reference internal" href="writing_modules.html">Writing Modules</a></li>
<li class="toctree-l1"><a class="reference internal" href="testing_modules.html">Testing Modules</a></li>
<li class="toctree-l1"><a class="reference internal" href="nilmdb.html">Database Reference</a></li>
<li class="toctree-l1"><a class="reference internal" href="reference.html">Reference</a></li>
</ul>
<div class="relations">
<h3>Related Topics</h3>
<ul>
<li><a href="index.html">Documentation overview</a><ul>
<li>Previous: <a href="index.html" title="previous chapter">Joule: Modular Data Processing</a></li>
<li>Next: <a href="install.html" title="next chapter">Installation</a></li>
</ul></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3>Quick search</h3>
<form class="search" action="search.html" method="get">
<div><input type="text" name="q" /></div>
<div><input type="submit" value="Go" /></div>
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="footer">
&copy;2017, John Donnal.
|
Powered by <a href="http://sphinx-doc.org/">Sphinx 1.6.2</a>
&amp; <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.10</a>
|
<a href="_sources/concepts.rst.txt"
rel="nofollow">Page source</a>
</div>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Index &#8212; Joule 1.0.0 documentation</title>
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/css/custom.css" type="text/css" />
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: './',
VERSION: '1.0.0',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
HAS_SOURCE: true,
SOURCELINK_SUFFIX: '.txt'
};
</script>
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/underscore.js"></script>
<script type="text/javascript" src="_static/doctools.js"></script>
<link rel="index" title="Index" href="#" />
<link rel="search" title="Search" href="search.html" />
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
</head>
<body>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body" role="main">
<h1 id="index">Index</h1>
<div class="genindex-jumpbox">
</div>
</div>
</div>
</div>
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<p class="logo">
<a href="index.html">
<img class="logo" src="_static/logo.png" alt="Logo"/>
</a>
</p>
<h3>Navigation</h3>
<ul>
<li class="toctree-l1"><a class="reference internal" href="concepts.html">Joule Concepts</a></li>
<li class="toctree-l1"><a class="reference internal" href="getting_started.html">Getting Started</a></li>
<li class="toctree-l1"><a class="reference internal" href="writing_modules.html">Writing Modules</a></li>
<li class="toctree-l1"><a class="reference internal" href="testing_modules.html">Testing Modules</a></li>
<li class="toctree-l1"><a class="reference internal" href="nilmdb.html">Database Reference</a></li>
<li class="toctree-l1"><a class="reference internal" href="reference.html">Reference</a></li>
</ul>
<div class="relations">
<h3>Related Topics</h3>
<ul>
<li><a href="index.html">Documentation overview</a><ul>
</ul></li>
</ul>
</div>
<div id="searchbox" style="display: none" role="search">
<h3>Quick search</h3>
<form class="search" action="search.html" method="get">
<div><input type="text" name="q" /></div>
<div><input type="submit" value="Go" /></div>
<input type="hidden" name="check_keywords" value="yes" />
<input type="hidden" name="area" value="default" />
</form>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="footer">
&copy;2017, John Donnal.
|
Powered by <a href="http://sphinx-doc.org/">Sphinx 1.6.2</a>
&amp; <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.10</a>
</div>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Search &#8212; Joule 1.0.0 documentation</title>
<link rel="stylesheet" href="_static/alabaster.css" type="text/css" />
<link rel="stylesheet" href="_static/pygments.css" type="text/css" />
<link rel="stylesheet" href="_static/css/custom.css" type="text/css" />
<script type="text/javascript">
var DOCUMENTATION_OPTIONS = {
URL_ROOT: './',
VERSION: '1.0.0',
COLLAPSE_INDEX: false,
FILE_SUFFIX: '.html',
HAS_SOURCE: true,
SOURCELINK_SUFFIX: '.txt'
};
</script>
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/underscore.js"></script>
<script type="text/javascript" src="_static/doctools.js"></script>
<script type="text/javascript" src="_static/searchtools.js"></script>
<link rel="index" title="Index" href="genindex.html" />
<link rel="search" title="Search" href="#" />
<script type="text/javascript">
jQuery(function() { Search.loadIndex("searchindex.js"); });
</script>
<script type="text/javascript" id="searchindexloader"></script>
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
</head>
<body>
<div class="document">
<div class="documentwrapper">
<div class="bodywrapper">
<div class="body" role="main">
<h1 id="search-documentation">Search</h1>
<div id="fallback" class="admonition warning">
<script type="text/javascript">$('#fallback').hide();</script>
<p>
Please activate JavaScript to enable the search
functionality.
</p>
</div>
<p>
From here you can search these documents. Enter your search
words into the box below and click "search". Note that the search
function will automatically search for all of the words. Pages
containing fewer words won't appear in the result list.
</p>
<form action="" method="get">
<input type="text" name="q" value="" />
<input type="submit" value="search" />
<span id="search-progress" style="padding-left: 10px"></span>
</form>
<div id="search-results">
</div>
</div>
</div>
</div>
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
<div class="sphinxsidebarwrapper">
<p class="logo">
<a href="index.html">
<img class="logo" src="_static/logo.png" alt="Logo"/>
</a>
</p>
<h3>Navigation</h3>
<ul>
<li class="toctree-l1"><a class="reference internal" href="concepts.html">Joule Concepts</a></li>
<li class="toctree-l1"><a class="reference internal" href="getting_started.html">Getting Started</a></li>
<li class="toctree-l1"><a class="reference internal" href="writing_modules.html">Writing Modules</a></li>
<li class="toctree-l1"><a class="reference internal" href="testing_modules.html">Testing Modules</a></li>
<li class="toctree-l1"><a class="reference internal" href="nilmdb.html">Database Reference</a></li>
<li class="toctree-l1"><a class="reference internal" href="reference.html">Reference</a></li>
</ul>
<div class="relations">
<h3>Related Topics</h3>
<ul>
<li><a href="index.html">Documentation overview</a><ul>
</ul></li>
</ul>
</div>
</div>
</div>
<div class="clearer"></div>
</div>
<div class="footer">
&copy;2017, John Donnal.
|
Powered by <a href="http://sphinx-doc.org/">Sphinx 1.6.2</a>
&amp; <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.10</a>
</div>
</body>
</html>
\ No newline at end of file
This diff could not be displayed because it is too large.
No preview for this file type
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment