├── .gitignore
├── .gitmodules
├── INSTALL.md
├── LICENSE
├── METADATA.md
├── README.md
├── UCB-openBAS-intro.docx
├── demo-original
├── app.py
├── static
│ ├── bootstrap.min.js
│ ├── cal.jpg
│ ├── config.json
│ ├── css
│ │ ├── bootstrap-theme.css
│ │ ├── bootstrap-theme.min.css
│ │ ├── bootstrap.css
│ │ ├── bootstrap.css~
│ │ ├── bootstrap.min.css
│ │ ├── buttons.css
│ │ ├── font-awesome.css
│ │ ├── jumbotron-narrow.css
│ │ └── slider.css
│ ├── fonts
│ │ ├── glyphicons-halflings-regular.eot
│ │ ├── glyphicons-halflings-regular.svg
│ │ ├── glyphicons-halflings-regular.ttf
│ │ └── glyphicons-halflings-regular.woff
│ ├── heater.png
│ ├── heater.xcf
│ ├── heatlamp.png
│ ├── heatlamp.xcf
│ ├── jquery.min.js
│ ├── js
│ │ ├── bootstrap-slider.js
│ │ ├── bootstrap.js
│ │ ├── bootstrap.min.js
│ │ └── buttons.js
│ ├── lamp.png
│ ├── light_off.png
│ ├── light_on.png
│ ├── off.png
│ ├── on.png
│ ├── pcfan.png
│ ├── pcfan.xcf
│ ├── scss
│ │ ├── buttons.scss
│ │ └── partials
│ │ │ ├── _buttons.scss
│ │ │ ├── _danger.scss
│ │ │ ├── _glow.scss
│ │ │ └── _options.scss
│ ├── sdb.png
│ ├── thermo.png
│ └── thermo2.png
└── templates
│ └── index.html
├── install.sh
├── install_virtual.sh
├── introPics.pptx
├── maketarball.sh
├── monitor
├── README.md
├── example.txt
├── report
│ ├── app.py
│ ├── report.py
│ ├── static
│ │ ├── debut-light.png
│ │ ├── report.css
│ │ └── report.js
│ └── templates
│ │ └── index.html
└── rtu_load.py
├── openbas
├── .meteor
│ ├── .finished-upgraders
│ ├── .gitignore
│ ├── .id
│ ├── cordova-plugins
│ ├── packages
│ ├── release
│ └── versions
├── LICENSE
├── building.ini
├── client
│ ├── actuate.js
│ ├── building.html
│ ├── building.js
│ ├── compatibility
│ │ ├── bootstrap-slider.js
│ │ └── buttons.js
│ ├── css
│ │ ├── buttons.css
│ │ └── slider.css
│ ├── dashboard.css
│ ├── dashboard.html
│ ├── dashboard.js
│ ├── openbas.css
│ ├── openbas.html
│ ├── openbas.js
│ ├── plot.html
│ ├── plot.js
│ ├── point.css
│ ├── point.html
│ ├── point.js
│ ├── schedule.html
│ ├── schedule.js
│ ├── status.html
│ └── status.js
├── demo.ini
├── followcontroller.py
├── lib
│ ├── collections.js
│ ├── methods.js
│ └── router.js
├── packages
│ ├── .gitignore
│ ├── anytime
│ ├── col-resizable
│ ├── jquery-migrate
│ ├── jquery-simplecolorpicker
│ ├── newd3
│ ├── s3ui
│ ├── timezone-js
│ └── vakata-jstree
├── private
│ ├── rooms.json
│ ├── schedules.json
│ └── testrooms.json
├── public
│ ├── floorplans
│ │ └── .DS_Store
│ └── img
│ │ ├── CIEE-floorplan.png
│ │ ├── ajax-loader.gif
│ │ └── debut-light.png
├── server
│ └── dashboard.js
└── settings.json
├── smap-control-example
├── control.ini
├── control.py
├── controllogic.py
├── oat.ini
├── oat.py
├── room.ini
├── room.py
└── startdrivers.py
├── smap-control-tutorial
├── control-demo.ini
├── coolControllerService.py
├── oat.ini
├── oat.py
├── pseudoOATdriver.py
├── room.ini
├── roommodeldriver.py
├── schedule-demo.ini
├── schedule.py
├── schedulerService.py
├── schedulerZoneDemo.ini
├── startdrivers.py
├── temperatureDriver.py
├── virtualthermostatDriver.py
└── zoneControllerService.py
└── zone-controller-tutorial
├── README.md
├── building.ini
├── schedules.json
├── zone1controller.py
└── zone2controller.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | monitor/report/report.json
3 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "upmu-plotter"]
2 | path = upmu-plotter
3 | url = https://github.com/SoftwareDefinedBuildings/upmu-plotter.git
4 |
--------------------------------------------------------------------------------
/INSTALL.md:
--------------------------------------------------------------------------------
1 | Installing OpenBAS
2 | ===============
3 |
4 | Welcome to the installation instructions for OpenBAS! This guide is intended to
5 | walk you through setting up your local OpenBAS server.
6 |
7 | ## System Requirements
8 |
9 | OpenBAS requires either Ubuntu Server 14.04 or Ubuntu Desktop 14.04 (Trusty Tahr). We recommend having at least:
10 |
11 | * 1 GHz processor
12 | * 2 GB RAM
13 | * 5 GB disk space
14 |
15 | Any relatively recent consumer-grade machine from the past 5 years should have no problem running Ubuntu and OpenBAS. If you want to do a real deployment, we recommend installing OpenBAS on a dedicated Ubuntu machine. If you are interested in sampling the install process, you can achieve this by using a virtual machine.
16 |
17 | ### Installing Ubuntu 14.04
18 |
19 | Ubuntu 14.04 is available for free download from
20 | [http://www.ubuntu.com/download/desktop/](http://www.ubuntu.com/download/desktop/).
21 | The easiest installation methods are either from DVD or from a USB thumbstick
22 | (instructions for how to do this from Ubuntu, Windows or Mac OS X are all
23 | available [here](http://www.ubuntu.com/download/desktop/)).
24 |
25 | Once you have your DVD or your USB drive with Ubuntu 14.04 loaded, follow the
26 | directions at
27 | [http://www.ubuntu.com/download/desktop/install-ubuntu-desktop](http://www.ubuntu.com/download/desktop/install-ubuntu-desktop)
28 | to install. The instructions are for the Ubuntu Desktop edition, which includes
29 | a graphical interface that is familiar and easier to use for users newer to
30 | Ubuntu. However, because OpenBAS provides a web-based interface, running a
31 | graphical environment on your OpenBAS server is not necessary. The Ubuntu
32 | Server edition will work just as well, if not better, than the Desktop edition.
33 |
34 | **NOTE: do not forget the username and password that you input during the installation process!**
35 |
36 | ### Using a Virtual Machine
37 |
38 | A virtual machine allows you to emulate another operating system on your computer, regardless of which operating system you are running. [VirtualBox](https://www.virtualbox.org/wiki/Downloads) is a free virtual machine. Download the appropriate VirtualBox for your operating system (~128 MB) and download Ubuntu using the links above. When you start up VirtualBox, press "New" to create a new virtual machine, and follow the installation wizard along with the Ubuntu installation instructions (linked above) to create a new Ubuntu installation.
39 |
40 | ## Installing
41 |
42 | 1. **Open the Terminal**: If you are on Ubuntu Desktop, open up the Terminal application by pressing
43 | `Ctl-Alt-T` (pressing the 'Control', 'Alt' and the 't' keys at the same time).
44 | If you are on Ubuntu Server, you should already be looking at a terminal. If
45 | you aren't, turn your computer on.
46 |
47 | **NOTE: when you begin the installation, make sure you are in your home directory. If you are unsure, run the command `cd` in the terminal -- this will place you in your home directory.**
48 |
49 | 2. **Make sure you have `curl` installed**: type the following command into the Terminal and press 'Enter':
50 |
51 | ```
52 | which curl
53 | ```
54 |
55 | If this gives you output, like printing out `/usr/bin/curl`, then you are fine! If not, you will need to install
56 | it:
57 |
58 | ```
59 | sudo apt-get install -y curl
60 | ```
61 |
62 | You will probably need to type in your password.
63 |
64 | 3. **Install OpenBAS:** We've made this part very easy! Simply type into the terminal the following:
65 |
66 | ```
67 | curl http://install.openbas.cal-sdb.org/ | sudo bash -
68 | ```
69 |
70 | which will install and configure your system to run OpenBAS. It will tell you exactly what it is doing,
71 | and by the end of the process, your computer should have OpenBAS installed and running.
72 |
73 | 4. **Configure the sMAP Archiver** (this part will soon be integrated into step 3)
74 |
75 | ```
76 | sudo apt-get install -y powerdb2
77 | ```
78 |
79 | This will ask if you want to create a superuser (type 'yes' and press enter). Enter a username,
80 | an optional email, and a password.
81 |
82 | 5. After `powerdb2` is installed, go to http://servername/admin and type in the username and password you created in the last step. If you are installing on a machine locally, this would be [http://localhost/admin](http://localhost/admin). Click '+Add' next to 'Subscriptions', enter a description (e.g. 'OpenBAS deployment') and replace the 'Key' field with the default key:
83 | ```
84 | lVzBMDpnkXApJmpjUDSvm4ceGfpbrLLSd9cq
85 | ```
86 | Then click 'Save'.
87 |
88 | 6. At this point, you have now installed all the needed OpenBAS software. You can now proceed to configuration your installation for this building.
89 |
90 | ## Getting Started
91 |
92 | **Note:** if you are running anti-virus software, it can misclassify OpenBAS's discovery service as a malignant process and block your access to OpenBAS. Please disable any anti-virus on your personal computer when installing OpenBAS.
93 |
94 | Links generated:
95 |
96 | * OpenBAS Building Dashboard: http://servername:3000 e.g [http://localhost:3000](http://localhost:3000)
97 | * Plotting Interace: http://servername e.g [http://localhost](http://localhost)
98 | * Admin Interface: http://servername/admin e.g [http://localhost/admin](http://localhost/admin)
99 |
100 | When first bringing up OpenBAS for your installation, follow the following steps:
101 |
102 | 1. Create rooms: visit http://servername/building e.g [http://localhost:3000/building](http://localhost:3000/building) and create some rooms, making sure to place markers on the map.
103 | 2. Plug in devices!
104 | 3. Configure devices on the status page http://servername/status e.g [http://localhost:3000/status](http://localhost:3000/status)
105 |
106 | ## Running a virtual building
107 |
108 | If you do not have devices to plug in, or are simply wanting to evaluate the system in a virtual machine, you can also utilise our support for *virtual devices*. A small representative set of preconfigured virtual devices can be added to the system by executing:
109 |
110 | ```
111 | curl http://install.openbas.cal-sdb.org/vbuilding | sudo bash -
112 | ```
113 |
114 | ## Troubleshooting
115 |
116 | 1. When I curl into sudo bash, nothing happens after the curl output.
117 |
118 | In some cases, the prompt for sudo can be hidden. The system is waiting for you to enter your password before proceeding. We recommend running a simple sudo command beforehand to cache the password for sudo so that this does not happen, for example:
119 | ```
120 | sudo echo
121 | ```
122 | before running a curl into sudo.
123 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, Regents of the University of California
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
--------------------------------------------------------------------------------
/METADATA.md:
--------------------------------------------------------------------------------
1 | Metadata
2 | ========
3 |
4 | Here, we define a structure for metadata for components of a building as described by sMAP.
5 |
6 | ## Root-level Metadata
7 |
8 | This metadata is specified at `[/]` in the sMAP configuration. As such, this metadata
9 | applies to everything in the configuration file.
10 |
11 |
12 | Example:
13 | ```
14 | [/]
15 | uuid = 06d634a6-1c03-11e4-965d-6003089ed1d0
16 | Metadata/SourceName = Demo Driver
17 | Metadata/Building = Soda Hall
18 | Metadata/Site = 0273d18f-1c03-11e4-a490-6003089ed1d0
19 | Metadata/configured = True
20 | ```
21 |
22 | #### `Metadata/SourceName`
23 |
24 | This is the human-readable name for this configuration file. No specific semantic meaning, but this
25 | is a handy way to refer to the sources contained in this file when you query the archiver.
26 |
27 | #### `Metadata/Building`
28 |
29 | The name of the building where this is installed. This should be unique to the installation, just as
30 | `Metadata/Site` below.
31 |
32 | #### `Metadata/Site`
33 |
34 | A UUID that uniquely identifies this installation. All configuration files for all sources for this
35 | installation should have the same `Metadata/Site` at the top level of their configuration.
36 |
37 | #### `Metadata/configured`
38 |
39 | This is an optional key. If `Metadata/configured = True`, then OpenBAS will assume that the Metadata
40 | specified in all sources in this file is complete, and will not have to be automapped. Typically, this
41 | is false (or unspecified, meaning false) for discovered sources
42 |
43 | ## Collection-level Metadata
44 |
45 | More specific than the global metadata specified by the root collection, collection-level metadata
46 | adds more structure to the hierarchy of metadata in the installation. There are 3 collections that are
47 | expected:
48 |
49 | * `/buildinglighting/` -- for lighting-related sources
50 | * `/buildinghvac/` -- for HVAC-related sources
51 | * `/monitoring/` -- for monitoring sources
52 |
53 | When defining sources, it is best to stick to this nomenclature, but it is imperative to correct
54 | metadata, described below
55 |
56 | #### `Metadata/System`
57 | This describes what function the source performs in the building.
58 |
59 | There are 3 valid systems:
60 |
61 | * `HVAC`
62 | * `Lighting`
63 | * `Monitoring`
64 |
65 | All sources running in a building should belong to one of these systems.
66 |
67 | #### `Metadata/Role`
68 |
69 | This indicates what this source does within the context of its system. Examples are:
70 |
71 | * `Building Lighting`
72 | * `Task Lighting`
73 | * `Building HVAC`
74 | * `Monitoring`
75 |
76 | #### `Metadata/Floor`
77 |
78 | Which floor the collection is on. This is simply a number. No `4th` or `first floor`. This may also be
79 | device-level metadata.
80 |
81 | #### `Metadata/Name`
82 |
83 | Human-readable name for this collection
84 |
85 | ## Device-level Metadata
86 |
87 | Device-level metadata is often for a single sMAP driver instantiation, that is,
88 | a single actuatable light or thermostat.
89 |
90 | #### `Metadata/Group`
91 | For `Lighting` system devices, a lighting group is the smallest actuatable unit of light. If you have a single
92 | binary switch for a bank of lights, that bank of lights is a 'group'. If we have a driver that controls a single
93 | desk lamp, then that desk lamp is a lighting group.
94 |
95 | #### `Metadata/LightingZone`
96 | A lighting zone is defined as the group of lights in a contiguous physical space. Generally, Lighting zones
97 | are room names, or are based on room names, e.g. '410 Soda' or '420 Soda West'.
98 |
99 | #### `Metadata/HVACZone`
100 |
101 | An HVAC zone is defined as a space or collections of spaces that are controlled by a single RTU or thermostat. In the case of multiple RTUs and thermostats, an HVAC zone can more generally be thought of as the collection of spaces that are all affected when settings are changed on a single thermostat.
102 |
103 | ## Timeseries-level Metadata
104 |
105 | #### `Metadata/Type`
106 |
107 | These should be defined for each timeseries within the driver:
108 |
109 | * `SP`: setpoint -- does not actually change anything, but serves to direct the device
110 | * `Command`: changes some aspect of the device, e.g. 'on' to 'off'
111 | * `Reading`: reads an aspect or state of the device, e.g. "what is my light's brightness level?"
112 | * `Sensor`: reports an aspect of the physical world
113 |
114 | #### `Metadata/Sensor`
115 |
116 | For a sensor, we define what the sensor measures. This is to avoid dependence on any specific timeseries endpoint:
117 |
118 | * `Occupancy`: this sensor measures occupancy
119 | * `Humidity`: relative humidity
120 | * `Temperature`: temperature. Units should be obtained from `Properties/UnitofMeasure`
121 | * `Illumination`: illumination
122 | * `CO2`: carbon-dioxide
123 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | OpenBAS
2 | ===============
3 |
4 | ## Deployment Setup
5 |
6 | See `INSTALL.md` in this directory
7 |
8 | ## Development Setup
9 |
10 | ### Getting Started
11 |
12 | Install [Meteorite](http://atmospherejs.com/docs/installing).
13 |
14 | To install all the dependencies and run the app:
15 |
16 | ```
17 | cd openbas
18 | mrt --settings settings.json
19 | ```
20 |
21 | When cloning this for the first time, make sure that you have the `upmu-plotter` submodule updated. To do this,
22 | run the following command from the same directory as this README:
23 |
24 | ```
25 | git submodule update --init
26 | git submodule foreach git pull origin master
27 | ```
28 |
29 | #### Problems Being up to date?
30 |
31 | 1. from clean openbas install
32 | 2. `mrt migrate-app` -- this will fail
33 | 3. remove accounts-ui-bootstrap-3 and collectionfs from smart.json, remove trailing comma from line 8
34 | 4. `mrt install`
35 | 5. `mrt migrate-app`
36 | 6. remove `cfs-*`, `collectionfs` from packages, .meteor/packages
37 | 7. `meteor remove cmather:iron-router`
38 | 8. `meteor add iron:router`
39 | 9. `meteor add mizzao:jquery-ui`
40 | 10. `meteor remove mizzao:jqueryui`
41 | 11. `meteor remove mrt:bootstrap-3`
42 | 12. `meteor add mizzao:bootstrap-3`
43 | 13. `sed -e 's/^[a-zA-Z0-9]/meteor remove &/' .meteor/packages | sed 's/\@[0-9\.]*//g' > packages-rm.sh`
44 | 14. `sed -e 's/ remove / add /' packages-rm.sh > packages-add.sh`
45 | 15. `bash packages-rm.sh`
46 | 16. `meteor list`
47 | 17. `meteor update`
48 | 18. `bash packages-add.sh`
49 | 19. `meteor add cfs:standard-packages`
50 | 20. `meteor add cfs:filesystem`
51 | 21. `meteor --settings settings.json` should work
52 |
53 |
54 | #### If on Ubuntu system:
55 |
56 | Install Meteor
57 |
58 | ```
59 | curl https://install.meteor.com | sh
60 | ```
61 |
62 | Put Meteor's node on the path
63 | ```
64 | export PATH=~/.meteor/tools/latest/bin:$PATH
65 | ```
66 |
67 | Install NPM, Node's package manager
68 | ```
69 | sudo apt-get install npm
70 | ```
71 |
72 | Install meteorite
73 |
74 | ```
75 | sudo -H npm install -g meteorite
76 | ```
77 |
78 | From within the openbas folder, run the following to install dependencies
79 | and run the app
80 |
81 | ```
82 | mrt --settings settings.json
83 | ```
84 |
85 |
86 | #### Running OpenBAS
87 |
88 | * change subscription key in building.ini for your local archiver
89 | * change archiver location in settings.json for local archiver
90 | * change public.site in settings.json to be Metadata/Site for building.ini
91 | * install pymongo:
92 | ```
93 | sudo pip install pymongo
94 | ```
95 | OR
96 | ```
97 | sudo apt-get install python-pymongo
98 | ```
99 |
100 | then run the source:
101 |
102 | ```
103 | twistd -n smap building.ini
104 | ```
105 |
--------------------------------------------------------------------------------
/UCB-openBAS-intro.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/UCB-openBAS-intro.docx
--------------------------------------------------------------------------------
/demo-original/app.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, render_template, jsonify, url_for, request
2 | import json
3 | import traceback
4 | import requests
5 | import time
6 | from smap.client import SmapClient
7 | from bosswave import BossWave
8 | from twisted.internet import reactor, task
9 | fanstate = 0
10 |
11 | tempsensors = [0]*4
12 | currenttick = -1
13 |
14 | c_url = 'http://128.32.37.43:8080/data'
15 | c = SmapClient('http://128.32.37.43:8080')
16 | print c.contents()
17 |
18 | other_url = 'http://128.32.37.43:8081/data'
19 | otherclient = SmapClient('http://128.32.37.43:8081')
20 | print otherclient.contents()
21 |
22 | acts = {'spaceheater': c_url+'/Raritan/outlet2/state_act',
23 | 'tasklight': c_url+'/TCPLighting/216544666156982628/state_act',
24 | 'buildinglight': c_url+'/hue/workbench/state/on_act',
25 | 'heatspt': c_url+'/imt550c0/temp_heat_act',
26 | 'coolspt': c_url+'/imt550c0/temp_cool_act',
27 | }
28 |
29 | objects = {
30 | 'spaceheater': {
31 | 'status': 'http://128.32.37.43:8080/data/Raritan/outlet2/state',
32 | 'actuate': 'http://128.32.37.43:8080/data/Raritan/outlet2/state_act',
33 | 'cssid': 'spaceheater'},
34 | 'rtuheat': {
35 | 'status': 'http://128.32.37.43:8080/data/Raritan/outlet3/state',
36 | 'actuate': 'http://128.32.37.43:8080/data/Raritan/outlet3/state_act',
37 | 'cssid': 'rtuheat'},
38 | 'tasklight': {
39 | 'status': 'http://128.32.37.43:8080/data/TCPLighting/216544666156982628/state',
40 | 'actuate': 'http://128.32.37.43:8080/data/TCPLighting/216544666156982628/state_act',
41 | 'cssid': 'tasklight'},
42 | 'buildinglight': {
43 | 'status': 'http://128.32.37.43:8080/data/hue/workbench/state/on',
44 | 'actuate': 'http://128.32.37.43:8080/data/hue/workbench/state/on_act',
45 | 'cssid': 'buildinglight'},
46 | 'buildinglighthue': {
47 | 'status': 'http://128.32.37.43:8080/data/hue/workbench/state/hue',
48 | 'actuate': 'http://128.32.37.43:8080/data/hue/workbench/state/hue_act',
49 | 'cssid': 'buildinghue'},
50 | 'buildinglightbri': {
51 | 'status': 'http://128.32.37.43:8080/data/hue/workbench/state/bri',
52 | 'actuate': 'http://128.32.37.43:8080/data/hue/workbench/state/bri_act',
53 | 'cssid': 'buildingbri'},
54 | 'heatspt': {
55 | 'status': 'http://128.32.37.43:8080/data/imt550c0/temp_heat',
56 | 'actuate': 'http://128.32.37.43:8080/data/imt550c0/temp_heat_act',
57 | 'cssid': 'heatspt'},
58 | 'coolspt': {
59 | 'status': 'http://128.32.37.43:8080/data/imt550c0/temp_cool',
60 | 'actuate': 'http://128.32.37.43:8080/data/imt550c0/temp_cool_act',
61 | 'cssid': 'coolspt'},
62 | 'thermostat': {
63 | 'status': 'http://128.32.37.43:8080/data/imt550c0/temp',
64 | 'cssid': 'temp'},
65 | 'rtucool': {
66 | 'status': 'http://128.32.37.43:8081/data/sensors/fan',
67 | 'actuate' : 'http://128.32.37.43:8081/data/sensors/fan_act',
68 | 'cssid': 'rtucool'},
69 | 'room1temp': {
70 | 'status': 'http://128.32.37.43:8081/data/sensors/temp1',
71 | 'cssid': 'room1temp'},
72 | 'room2temp': {
73 | 'status': 'http://128.32.37.43:8081/data/sensors/temp2',
74 | 'cssid': 'room2temp'},
75 | 'room1rh': {
76 | 'status': 'http://128.32.37.43:8081/data/sensors/rh1',
77 | 'cssid': 'room1rh'},
78 | 'room2rh': {
79 | 'status': 'http://128.32.37.43:8081/data/sensors/rh2',
80 | 'cssid': 'room2rh'},
81 | }
82 |
83 | def actuate_fan(state):
84 | try:
85 | if int(state) == 1:
86 | otherclient.set_state('/sensors/fan_act',1)
87 | else:
88 | otherclient.set_state('/sensors/fan_act',0)
89 | except Exception as e:
90 | print e
91 |
92 | def c_to_f(temp):
93 | return temp * (9./5) + 32
94 |
95 | def runschedule(f):
96 | global currenttick
97 | current_states = map(lambda x: json.loads(requests.get(x[:-4]).content)['Readings'][0][1], acts.itervalues())
98 | for i in range(0,6):
99 | print i
100 | currenttick = i
101 | todo = [(acts[k[:-1]],v) for k,v in f.iteritems() if k.endswith(str(i))]
102 | for url, val in todo:
103 | if url:
104 | print requests.put(url+'?state='+val)
105 | time.sleep(10)
106 | currenttick = -1
107 | for url, val in zip(acts.values(), current_states):
108 | print requests.put(url+'?state='+str(val))
109 |
110 | app = Flask(__name__)
111 |
112 | @app.route("/")
113 | def index():
114 | return render_template('index.html')
115 |
116 | @app.route('/schedule', methods=['GET','POST'])
117 | def handleform():
118 | reactor.callInThread(runschedule, request.form)
119 | return jsonify({'ok': 0})
120 |
121 | @app.route('/scheduletick', methods=['GET'])
122 | def gettick():
123 | global currenttick
124 | return jsonify({'tick': currenttick})
125 |
126 | @app.route('/smap', methods=['GET'])
127 | def getsmap():
128 | path = request.args.get('smappath')+'/'
129 | actpath = request.args.get('smappath')+'_act/'
130 | state = c.get_state(path)[1]
131 | act_state = request.args.get('state',None)
132 | toggle = request.args.get('toggle',None)
133 | try:
134 | if toggle:
135 | if int(state) == 1:
136 | c.set_state(actpath,0)
137 | else:
138 | c.set_state(actpath,1)
139 | elif act_state:
140 | resp = requests.get('http://128.32.37.43:8080/data'+path)
141 | if json.loads(resp.content)['Readings'][0][1] == int(act_state):
142 | print 'already okay',act_state,actpath
143 | else:
144 | print 'http://128.32.37.43:8080/data'+actpath[:-1]+'?state='+act_state
145 | print requests.put('http://128.32.37.43:8080/data'+actpath+'?state='+act_state)
146 | #c.set_state(actpath, int(act_state))
147 | except Exception as e:
148 | print e#, traceback.format_exc()
149 | resp = jsonify({'Reading': c.get_state(path)[1]})
150 | resp.headers['Access-Control-Allow-Origin']='*'
151 | return resp
152 |
153 | @app.route('/fan', methods=['GET'])
154 | def fan():
155 | state = -1
156 | try:
157 | state = int(request.args.get('state'))
158 | except:
159 | pass
160 | global fanstate
161 | if (state == 0 or state == 1):
162 | actuate_fan(state)
163 | fanstate = state
164 | return jsonify({'Readings': [[1, state]]})
165 |
166 | if fanstate == 0:
167 | actuate_fan(1)
168 | fanstate = 1
169 | return jsonify({'Readings': [[1, 1]]})
170 | else:
171 | actuate_fan(0)
172 | fanstate = 0
173 | return jsonify({'Readings': [[0, 0]]})
174 |
175 | @app.route('/objectmap')
176 | def getobjectmap():
177 | return json.dumps(objects)
178 |
179 | if __name__ == '__main__':
180 | from twisted.internet import reactor
181 | from threading import Thread
182 |
183 | Thread(target=reactor.run, args=(False,)).start()
184 | app.run(host='0.0.0.0',port=5000,debug=True)
185 |
186 |
--------------------------------------------------------------------------------
/demo-original/static/cal.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/demo-original/static/cal.jpg
--------------------------------------------------------------------------------
/demo-original/static/css/jumbotron-narrow.css:
--------------------------------------------------------------------------------
1 | /* Space out content a bit */
2 | body {
3 | padding-top: 20px;
4 | padding-bottom: 20px;
5 | }
6 |
7 | /* Everything but the jumbotron gets side spacing for mobile first views */
8 | .header,
9 | .marketing,
10 | .footer {
11 | padding-right: 15px;
12 | padding-left: 15px;
13 | }
14 |
15 | /* Custom page header */
16 | .header {
17 | border-bottom: 1px solid #e5e5e5;
18 | }
19 | /* Make the masthead heading the same height as the navigation */
20 | .header h3 {
21 | padding-bottom: 19px;
22 | margin-top: 0;
23 | margin-bottom: 0;
24 | line-height: 40px;
25 | }
26 |
27 | /* Custom page footer */
28 | .footer {
29 | padding-top: 19px;
30 | color: #777;
31 | border-top: 1px solid #e5e5e5;
32 | }
33 |
34 |
35 | /* Main marketing message and sign up button */
36 | .jumbotron {
37 | text-align: center;
38 | border-bottom: 1px solid #e5e5e5;
39 | }
40 | .jumbotron .btn {
41 | padding: 14px 24px;
42 | font-size: 21px;
43 | }
44 |
45 | .box {
46 | border-style: solid;
47 | border-width: 1px;
48 | min-height: 200px;
49 | height: 50%;
50 | }
51 |
52 | /* Supporting marketing content */
53 | .marketing {
54 | margin: 40px 0;
55 | }
56 | .marketing p + h4 {
57 | margin-top: 28px;
58 | }
59 |
60 | .physspace {
61 | border-bottom: 1px solid;
62 | border-left: 1px solid;
63 | padding: 10px;
64 | }
65 |
66 | .borderleft {
67 | border-left: 1px solid;
68 | }
69 |
70 | .borderright {
71 | border-right: 1px solid;
72 | }
73 |
74 | tr.spaceUnder > td
75 | {
76 | padding-bottom: 1em;
77 | }
78 |
79 | td {
80 | text-align: center;
81 | }
82 |
83 | th {
84 | text-align: center;
85 | }
86 |
87 | .schedule {
88 | padding-top: 1em;
89 | padding-bottom: 1em;
90 | }
91 |
92 | #schedulebox {
93 | margin-left: auto;
94 | margin-right: auto;
95 | width: 75%;
96 | }
97 |
98 | .hasslider {
99 | margin-left:auto;
100 | margin-right: auto;
101 | }
102 |
--------------------------------------------------------------------------------
/demo-original/static/css/slider.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Slider for Bootstrap
3 | *
4 | * Copyright 2012 Stefan Petre
5 | * Licensed under the Apache License v2.0
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | */
9 | .slider {
10 | display: inline-block;
11 | vertical-align: middle;
12 | position: relative;
13 | }
14 | .slider.slider-horizontal {
15 | width: 210px;
16 | height: 20px;
17 | }
18 | .slider.slider-horizontal .slider-track {
19 | height: 10px;
20 | width: 100%;
21 | margin-top: -5px;
22 | top: 50%;
23 | left: 0;
24 | }
25 | .slider.slider-horizontal .slider-selection {
26 | height: 100%;
27 | top: 0;
28 | bottom: 0;
29 | }
30 | .slider.slider-horizontal .slider-handle {
31 | margin-left: -10px;
32 | margin-top: -5px;
33 | }
34 | .slider.slider-horizontal .slider-handle.triangle {
35 | border-width: 0 10px 10px 10px;
36 | width: 0;
37 | height: 0;
38 | border-bottom-color: #0480be;
39 | margin-top: 0;
40 | }
41 | .slider.slider-vertical {
42 | height: 210px;
43 | width: 20px;
44 | }
45 | .slider.slider-vertical .slider-track {
46 | width: 10px;
47 | height: 100%;
48 | margin-left: -5px;
49 | left: 50%;
50 | top: 0;
51 | }
52 | .slider.slider-vertical .slider-selection {
53 | width: 100%;
54 | left: 0;
55 | top: 0;
56 | bottom: 0;
57 | }
58 | .slider.slider-vertical .slider-handle {
59 | margin-left: -5px;
60 | margin-top: -10px;
61 | }
62 | .slider.slider-vertical .slider-handle.triangle {
63 | border-width: 10px 0 10px 10px;
64 | width: 1px;
65 | height: 1px;
66 | border-left-color: #0480be;
67 | margin-left: 0;
68 | }
69 | .slider input {
70 | display: none;
71 | }
72 | .slider .tooltip-inner {
73 | white-space: nowrap;
74 | }
75 | .slider-track {
76 | position: absolute;
77 | cursor: pointer;
78 | background-color: #f7f7f7;
79 | background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9);
80 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));
81 | background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9);
82 | background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9);
83 | background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9);
84 | background-repeat: repeat-x;
85 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);
86 | -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
87 | -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
88 | box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
89 | -webkit-border-radius: 4px;
90 | -moz-border-radius: 4px;
91 | border-radius: 4px;
92 | }
93 | .slider-selection {
94 | position: absolute;
95 | background-color: #f7f7f7;
96 | background-image: -moz-linear-gradient(top, #f9f9f9, #f5f5f5);
97 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f9f9f9), to(#f5f5f5));
98 | background-image: -webkit-linear-gradient(top, #f9f9f9, #f5f5f5);
99 | background-image: -o-linear-gradient(top, #f9f9f9, #f5f5f5);
100 | background-image: linear-gradient(to bottom, #f9f9f9, #f5f5f5);
101 | background-repeat: repeat-x;
102 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9', endColorstr='#fff5f5f5', GradientType=0);
103 | -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
104 | -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
105 | box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
106 | -webkit-box-sizing: border-box;
107 | -moz-box-sizing: border-box;
108 | box-sizing: border-box;
109 | -webkit-border-radius: 4px;
110 | -moz-border-radius: 4px;
111 | border-radius: 4px;
112 | }
113 | .slider-handle {
114 | position: absolute;
115 | width: 20px;
116 | height: 20px;
117 | background-color: #0e90d2;
118 | background-image: -moz-linear-gradient(top, #149bdf, #0480be);
119 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));
120 | background-image: -webkit-linear-gradient(top, #149bdf, #0480be);
121 | background-image: -o-linear-gradient(top, #149bdf, #0480be);
122 | background-image: linear-gradient(to bottom, #149bdf, #0480be);
123 | background-repeat: repeat-x;
124 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);
125 | -webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
126 | -moz-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
127 | box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
128 | opacity: 0.8;
129 | border: 0px solid transparent;
130 | }
131 | .slider-handle.round {
132 | -webkit-border-radius: 20px;
133 | -moz-border-radius: 20px;
134 | border-radius: 20px;
135 | }
136 | .slider-handle.triangle {
137 | background: transparent none;
138 | }
--------------------------------------------------------------------------------
/demo-original/static/fonts/glyphicons-halflings-regular.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/demo-original/static/fonts/glyphicons-halflings-regular.eot
--------------------------------------------------------------------------------
/demo-original/static/fonts/glyphicons-halflings-regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/demo-original/static/fonts/glyphicons-halflings-regular.ttf
--------------------------------------------------------------------------------
/demo-original/static/fonts/glyphicons-halflings-regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/demo-original/static/fonts/glyphicons-halflings-regular.woff
--------------------------------------------------------------------------------
/demo-original/static/heater.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/demo-original/static/heater.png
--------------------------------------------------------------------------------
/demo-original/static/heater.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/demo-original/static/heater.xcf
--------------------------------------------------------------------------------
/demo-original/static/heatlamp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/demo-original/static/heatlamp.png
--------------------------------------------------------------------------------
/demo-original/static/heatlamp.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/demo-original/static/heatlamp.xcf
--------------------------------------------------------------------------------
/demo-original/static/js/buttons.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Project: Buttons
3 | * Description: A highly customizable CSS button library built with Sass and Compass
4 | * Author: Alex Wolfe
5 | * License: Apache License v2.0
6 | */
7 |
8 | // the semi-colon before function invocation is a safety net against concatenated
9 | // scripts and/or other plugins which may not be closed properly.
10 | ;(function ( $, window, document, undefined ) {
11 | 'use strict';
12 |
13 | // undefined is used here as the undefined global variable in ECMAScript 3 is
14 | // mutable (ie. it can be changed by someone else). undefined isn't really being
15 | // passed in so we can ensure the value of it is truly undefined. In ES5, undefined
16 | // can no longer be modified.
17 |
18 | // window and document are passed through as local variable rather than global
19 | // as this (slightly) quickens the resolution process and can be more efficiently
20 | // minified (especially when both are regularly referenced in your plugin).
21 |
22 | // Create the defaults once
23 | var pluginName = "menuButton";
24 | var menuClass = ".button-dropdown";
25 | var defaults = {
26 | propertyName: "value"
27 | };
28 |
29 | // The actual plugin constructor
30 | function Plugin( element, options ) {
31 |
32 | //SET OPTIONS
33 | this.options = $.extend( {}, defaults, options );
34 | this._defaults = defaults;
35 | this._name = pluginName;
36 |
37 | //REGISTER ELEMENT
38 | this.$element = $(element);
39 |
40 | //INITIALIZE
41 | this.init();
42 | }
43 |
44 | Plugin.prototype = {
45 | constructor: Plugin,
46 |
47 | init: function() {
48 | // WE DON'T STOP PROPGATION SO CLICKS WILL AUTOMATICALLY
49 | // TOGGLE AND REMOVE THE DROPDOWN & OVERLAY
50 | this.toggle();
51 | },
52 |
53 | toggle: function(el, options) {
54 | if(this.$element.data('dropdown') === 'show') {
55 | this.hideMenu();
56 | }
57 | else {
58 | this.showMenu();
59 | }
60 | },
61 |
62 | showMenu: function() {
63 | this.$element.data('dropdown', 'show');
64 | this.$element.find('ul').show();
65 |
66 | if(this.$overlay) {
67 | this.$overlay.show();
68 | }
69 | else {
70 | this.$overlay = $('
');
71 | this.$element.append(this.$overlay);
72 | }
73 | },
74 |
75 | hideMenu: function() {
76 | this.$element.data('dropdown', 'hide');
77 | this.$element.find('ul').hide();
78 | this.$overlay.hide();
79 | }
80 | };
81 |
82 | // A really lightweight plugin wrapper around the constructor,
83 | // preventing against multiple instantiations
84 | $.fn[pluginName] = function ( options ) {
85 | return this.each(function () {
86 |
87 | // TOGGLE BUTTON IF IT EXISTS
88 | if ($.data(this, "plugin_" + pluginName)) {
89 | $.data(this, "plugin_" + pluginName).toggle();
90 | }
91 | // OTHERWISE CREATE A NEW INSTANCE
92 | else {
93 | $.data(this, "plugin_" + pluginName, new Plugin( this, options ));
94 | }
95 | });
96 | };
97 |
98 |
99 | //DELEGATE CLICK EVENT FOR DROPDOWN MENUS
100 | $(document).on('click', '[data-buttons=dropdown]', function(e) {
101 | var $dropdown = $(e.currentTarget);
102 | $dropdown.menuButton();
103 | });
104 |
105 | //IGNORE CLICK EVENTS FROM DISPLAY BUTTON IN DROPDOWN
106 | $(document).on('click', '[data-buttons=dropdown] > a', function(e) {
107 | e.preventDefault();
108 | });
109 |
110 | })( jQuery, window, document);
--------------------------------------------------------------------------------
/demo-original/static/lamp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/demo-original/static/lamp.png
--------------------------------------------------------------------------------
/demo-original/static/light_off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/demo-original/static/light_off.png
--------------------------------------------------------------------------------
/demo-original/static/light_on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/demo-original/static/light_on.png
--------------------------------------------------------------------------------
/demo-original/static/off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/demo-original/static/off.png
--------------------------------------------------------------------------------
/demo-original/static/on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/demo-original/static/on.png
--------------------------------------------------------------------------------
/demo-original/static/pcfan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/demo-original/static/pcfan.png
--------------------------------------------------------------------------------
/demo-original/static/pcfan.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/demo-original/static/pcfan.xcf
--------------------------------------------------------------------------------
/demo-original/static/scss/buttons.scss:
--------------------------------------------------------------------------------
1 | @import 'partials/buttons'
2 |
3 |
--------------------------------------------------------------------------------
/demo-original/static/scss/partials/_danger.scss:
--------------------------------------------------------------------------------
1 | //////////////////////////////////////////////////////////
2 | // BUILD TYPES (don't edit this!) ///////////////////////
3 | //////////////////////////////////////////////////////////
4 | // Purpose of this file is to set global flags for which
5 | // types we'll include in our final css build or not.
6 | $unicorn-btn-build-glow: false;
7 | $unicorn-btn-build-dropdown: false;
8 | $unicorn-btn-build-rounded: false;
9 | $unicorn-btn-build-pill: false;
10 | $unicorn-btn-build-circle: false;
11 | $unicorn-btn-build-flat: false;
12 | $unicorn-btn-build-3d: false;
13 | $unicorn-btn-build-border: false;
14 |
15 | // For all types provided by user we mark true
16 | @each $unicorn-btn-type in $unicorn-btn-types {
17 | @if $unicorn-btn-type == 'glow' {
18 | $unicorn-btn-build-glow: true;
19 | }
20 | @else if $unicorn-btn-type == 'dropdown' {
21 | $unicorn-btn-build-dropdown: true;
22 | }
23 | @else if $unicorn-btn-type == 'rounded' {
24 | $unicorn-btn-build-rounded: true;
25 | }
26 | @else if $unicorn-btn-type == 'pill' {
27 | $unicorn-btn-build-pill: true;
28 | }
29 | @else if $unicorn-btn-type == 'circle' {
30 | $unicorn-btn-build-circle: true;
31 | }
32 | @else if $unicorn-btn-type == 'flat' {
33 | $unicorn-btn-build-flat: true;
34 | }
35 | @else if $unicorn-btn-type == 'border' {
36 | $unicorn-btn-build-border: true;
37 | }
38 | @else if $unicorn-btn-type == '3d' {
39 | $unicorn-btn-build-3d: true;
40 | }
41 | }
42 | //@debug "$unicorn-btn-build-glow is #{$unicorn-btn-build-glow}";
--------------------------------------------------------------------------------
/demo-original/static/scss/partials/_glow.scss:
--------------------------------------------------------------------------------
1 | @import 'options';
2 |
3 | // Only include all this junk if $unicorn-btn-build-types list had 'glow'
4 | // We looked in to conditional imports but no go in Sass today
5 | @if $unicorn-btn-build-glow {
6 | //////////////////////////////////////////////////////////
7 | // GLOWING BUTTON STYLES ////////////////////////////////////////
8 | //////////////////////////////////////////////////////////
9 | @-webkit-keyframes glowing,
10 | {
11 | from { @include box-shadow(0px 0px 0px rgba($unicorn-btn-glow-color, 0.3), 0px 1px 2px rgba(0, 0, 0, .20));}
12 | 50% { @include box-shadow(0px 0px 16px rgba($unicorn-btn-glow-color, 0.8), 0px 1px 2px rgba(0, 0, 0, .20));}
13 | to { @include box-shadow(0px 0px 0px rgba($unicorn-btn-glow-color, 0.3), 0px 1px 2px rgba(0, 0, 0, .20));}
14 | }
15 | @-moz-keyframes glowing,
16 | {
17 | from { @include box-shadow(0px 0px 0px rgba($unicorn-btn-glow-color, 0.3), 0px 1px 2px rgba(0, 0, 0, .20));}
18 | 50% { @include box-shadow(0px 0px 16px rgba($unicorn-btn-glow-color, 0.8), 0px 1px 2px rgba(0, 0, 0, .20));}
19 | to { @include box-shadow(0px 0px 0px rgba($unicorn-btn-glow-color, 0.3), 0px 1px 2px rgba(0, 0, 0, .20));}
20 | }
21 | @-o-keyframes glowing,
22 | {
23 | from { @include box-shadow(0px 0px 0px rgba($unicorn-btn-glow-color, 0.3), 0px 1px 2px rgba(0, 0, 0, .20));}
24 | 50% { @include box-shadow(0px 0px 16px rgba($unicorn-btn-glow-color, 0.8), 0px 1px 2px rgba(0, 0, 0, .20));}
25 | to { @include box-shadow(0px 0px 0px rgba($unicorn-btn-glow-color, 0.3), 0px 1px 2px rgba(0, 0, 0, .20));}
26 | }
27 | @keyframes glowing,
28 | {
29 | from { @include box-shadow(0px 0px 0px rgba($unicorn-btn-glow-color, 0.3), 0px 1px 2px rgba(0, 0, 0, .20));}
30 | 50% { @include box-shadow(0px 0px 16px rgba($unicorn-btn-glow-color, 0.8), 0px 1px 2px rgba(0, 0, 0, .20));}
31 | to { @include box-shadow(0px 0px 0px rgba($unicorn-btn-glow-color, 0.3), 0px 1px 2px rgba(0, 0, 0, .20));}
32 | }
33 | }
34 |
35 | @mixin glow {
36 | -webkit-animation-duration: 3s;
37 | -moz-animation-duration: 3s;
38 | -ms-animation-duration: 3s;
39 | -o-animation-duration: 3s;
40 | animation-duration: 3s;
41 | -webkit-animation-iteration-count: infinite;
42 | -khtml-animation-iteration-count: infinite;
43 | -moz-animation-iteration-count: infinite;
44 | -ms-animation-iteration-count: infinite;
45 | -o-animation-iteration-count: infinite;
46 | animation-iteration-count: infinite;
47 | -webkit-animation-name: glowing;
48 | -khtml-animation-name: glowing;
49 | -moz-animation-name: glowing;
50 | -ms-animation-name: glowing;
51 | -o-animation-name: glowing;
52 | animation-name: glowing;
53 | }
54 |
55 | @mixin no_animation {
56 | -webkit-animation-name: none;
57 | -moz-animation-name: none;
58 | -ms-animation-name: none;
59 | -o-animation-name: none;
60 | animation-name: none;
61 | }
--------------------------------------------------------------------------------
/demo-original/static/scss/partials/_options.scss:
--------------------------------------------------------------------------------
1 |
2 | //////////////////////////////////////////////////////////
3 | // BASE DEFAULTS /////////////////////////////////////////
4 | //////////////////////////////////////////////////////////
5 | $unicorn-btn-namespace: ".button"; //prefix for all classes
6 | $unicorn-btn-glow-namespace: ".glow";
7 | $unicorn-btn-glow-color: #2c9adb;
8 | $unicorn-btn-bgcolor: #EEE;
9 | $unicorn-btn-height: 32px;
10 |
11 |
12 | //////////////////////////////////////////////////////////
13 | // TYPOGRAPHY ////////////////////////////////////////////
14 | //////////////////////////////////////////////////////////
15 | $unicorn-btn-font-color: #666;
16 | $unicorn-btn-font-size: 14px;
17 | $unicorn-btn-font-weight: 300;
18 | $unicorn-btn-font-family: "Helvetica Neue Light", "Helvetica Neue", "Helvetica", "Arial", "Lucida Grande", sans-serif;
19 |
20 | //////////////////////////////////////////////////////////
21 | // BUTTON OPTIONS ////////////////////////////////////////
22 | //////////////////////////////////////////////////////////
23 |
24 | // (name background-color font-color) add as many as you like.
25 | $unicorn-btn-actions: ('primary' #00A1CB #FFFFFF) ('action' #7db500 #FFFFFF) ('highlight' #F18D05 #FFFFFF) ('caution' #E54028 #FFFFFF) ('royal' #87318C #FFFFFF) ;
26 | // Remove any type to omit from final CSS build
27 | $unicorn-btn-types: 'flat' 'glow' 'rounded' '3d' 'border' 'pill' 'circle' 'dropdown';
28 | $unicorn-btn-sizes: 'jumbo' 'large' 'small' 'tiny';
29 | $unicorn-btn-circle-size: 120px; //radius for circle buttons, circles only have one size
30 |
31 |
32 | //////////////////////////////////////////////////////////
33 | // DROPDOWN OPTIONS //////////////////////////////////////
34 | //////////////////////////////////////////////////////////
35 | $unicorn-btn-dropdown-background: #fcfcfc;
36 | $unicorn-btn-dropdown-link-color: #333;
37 | $unicorn-btn-dropdown-link-hover: #FFF;
38 | $unicorn-btn-dropdown-link-hover-background: #3c6ab9;
39 |
--------------------------------------------------------------------------------
/demo-original/static/sdb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/demo-original/static/sdb.png
--------------------------------------------------------------------------------
/demo-original/static/thermo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/demo-original/static/thermo.png
--------------------------------------------------------------------------------
/demo-original/static/thermo2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/demo-original/static/thermo2.png
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # This is the install script for OpenBAS
4 |
5 | if [ $(whoami) != "root" ]
6 | then
7 | echo "You need to curl this script into 'sudo bash' not just 'bash'"
8 | exit 1
9 | fi
10 |
11 | export DEBIAN_FRONTEND=noninteractive
12 |
13 | function notify() {
14 | msg=$1
15 | length=$((${#msg}+4))
16 | buf=$(printf "%-${length}s" "#")
17 | echo ${buf// /#}
18 | echo "# "$msg" #"
19 | echo ${buf// /#}
20 | sleep 2
21 | }
22 |
23 | # display on stderr
24 | exec 1>&2
25 |
26 | UNAME=$(uname)
27 | if [ "$UNAME" != "Linux" ] ; then
28 | echo "Requires Ubuntu 14.04"
29 | exit 1
30 | fi
31 |
32 | ISUBUNTU=$(lsb_release -is)
33 | UBUNTUVERSION=$(lsb_release -rs)
34 | if [ "$ISUBUNTU" != "Ubuntu" -o "$UBUNTUVERSION" != "14.04" ] ; then
35 | echo "Requires Ubuntu 14.04"
36 | exit 1
37 | fi
38 |
39 | notify "Installing APT packages... (this will take a few minutes)"
40 | apt-get update
41 | apt-get install -y expect software-properties-common python-pip mongodb npm libssl-dev git-core pkg-config build-essential nmap dhcpdump arp-scan 2>&1 > /tmp/install.0.log
42 |
43 | if [ $? != 0 ] ; then
44 | echo "There was an unexpected error installing the first set of packages"
45 | exit 1
46 | fi
47 |
48 | notify "Installing Meteor..."
49 | curl https://install.meteor.com | sh
50 | if [ $? != 0 ] ; then
51 | echo "There was an error installing meteor"
52 | exit 1
53 | fi
54 | echo "export PATH=~/.meteor/tools/latest/bin:\$PATH" >> ~/.profile
55 | export PATH=~/.meteor/tools/latest/bin:$PATH
56 | npm install -g meteorite
57 |
58 | notify "Fetching latest node..."
59 | curl -sL https://deb.nodesource.com/setup | sudo bash -
60 | if [ $? != 0 ]; then
61 | echo "There was an error installing node"
62 | exit 1
63 | fi
64 | apt-get install -y nodejs nodejs-legacy 2>&1 > /tmp/install.1.log
65 | if [ $? != 0 ]; then
66 | echo ""
67 | fi
68 |
69 | notify "Adding cal-sdb package repository..."
70 | add-apt-repository ppa:cal-sdb/smap
71 | if [ $? != 0 ] ; then
72 | echo "There was an error adding the repository"
73 | exit 1
74 | fi
75 |
76 | notify "Updating APT for latest packages..."
77 | apt-get update
78 | if [ $? != 0 ] ; then
79 | echo "There was an error updating the package index"
80 | exit 1
81 | fi
82 |
83 | notify "Installing sMAP and sMAP dependencies... (this will take a few minutes)"
84 | apt-get install -y python-smap readingdb 2>&1 > /tmp/install.2.log
85 | if [ $? != 0 ] ; then
86 | echo "There was an error installing smap packages"
87 | exit 1
88 | fi
89 | pip install pymongo netifaces
90 | if [ $? != 0 ] ; then
91 | echo "There was an error updating installing python packages"
92 | exit 1
93 | fi
94 | mkdir -p /var/run/smap
95 | mkdir /var/smap
96 | chown -R $SUDO_USER /var/smap
97 | chown -R smap /var/run/smap
98 |
99 | notify "Downloading OpenBAS..."
100 | curl -O http://install.openbas.cal-sdb.org/openbas.tgz
101 | tar xzf openbas.tgz
102 |
103 | cat < openbas.conf
104 | [program:openbas]
105 | command = meteor --settings settings.json
106 | user = $SUDO_USER
107 | directory = /home/$SUDO_USER/openbas
108 | priority = 2
109 | environment = HOME = "/home/$SUDO_USER"
110 | autorestart = true
111 | stdout_logfile = /var/log/openbas.stdout.log
112 | stdout_logfile_maxbytes = 50MB
113 | stdout_logfile_backups = 5
114 | stderr_logfile = /var/log/openbas.stderr.log
115 | stderr_logfile_maxbytes = 50MB
116 | stderr_logfile_backups = 5
117 | EOF
118 |
119 | mv openbas.conf /etc/supervisor/conf.d/openbas.conf
120 |
121 | cat < discovery.ini
122 | [/]
123 | uuid = 85d97cac-9345-11e3-898b-0001c009bf3f
124 |
125 | [/discovery]
126 | type = smap.services.discovery.DiscoveryDriver
127 | dhcp_iface = eth0
128 | supervisord_conf_file = supervisord.conf
129 | dhcpdump_path = /usr/sbin/dhcpdump
130 | nmap_path = /usr/bin/nmap
131 | config_repo = /etc/smap
132 | scripts_path = /usr/lib/python2.7/dist-packages/smap/services/scripts
133 | EOF
134 |
135 | mv discovery.ini /etc/smap/.
136 |
137 | cat < discovery.conf
138 | [program:discovery]
139 | command = twistd --pidfile=discovery.pid -n smap --port=7979 /etc/smap/discovery.ini
140 | directory = /var/smap
141 | environment=PYTHONPATH="/home/$SUDO_USER/smap"
142 | priority = 2
143 | autorestart = true
144 | user = root
145 | stdout_logfile = /var/log/discovery.stdout.log
146 | stderr_logfile = /var/log/discovery.stderr.log
147 | stdout_logfile_maxbytes = 50MB
148 | stdout_logfile_backups = 5
149 | stderr_logfile_maxbytes = 50MB
150 | stderr_logfile_backups = 5
151 | EOF
152 |
153 | mv discovery.conf /etc/supervisor/conf.d/discovery.conf
154 |
155 | cat < scheduler.ini
156 | [/]
157 | uuid = 6d39e9ba-28b3-11e4-a7d9-e4ce8f4229ee
158 |
159 | [report 1]
160 | ReportDeliveryLocation = http://localhost:8079/add/lVzBMDpnkXApJmpjUDSvm4ceGfpbrLLSd9cq
161 |
162 | [server]
163 | port = 8080
164 |
165 | [/scheduler]
166 | type = smap.services.scheduler.Scheduler
167 | pollrate = 1
168 | publishrate = 1
169 | source = mongodb://localhost:3001/meteor
170 | Metadata/Building = Soda Hall
171 | Metadata/Site = 0273d18f-1c03-11e4-a490-6003089ed1d0
172 |
173 | [/scheduler/temp_heat]
174 | Metadata/Description = Master Heating setpoint
175 | Metadata/System = HVAC
176 | Metadata/Type = Setpoint
177 |
178 | [/scheduler/temp_cool]
179 | Metadata/Description = Master Cooling setpoint
180 | Metadata/System = HVAC
181 | Metadata/Type = Setpoint
182 |
183 | [/scheduler/hvac_state]
184 | Metadata/Description = Master HVAC State control
185 | Metadata/System = HVAC
186 | Metadata/Type = Command
187 |
188 | [/scheduler/on]
189 | Metadata/Description = Master Lighting control
190 | Metadata/System = Lighting
191 | Metadata/Type = Command
192 | EOF
193 |
194 | mv scheduler.ini /etc/smap/.
195 |
196 | cat < scheduler.conf
197 | [program:scheduler]
198 | command = twistd --pidfile=scheduler.pid -n smap /etc/smap/scheduler.ini
199 | directory = /var/smap
200 | environment=PYTHONPATH="/home/$SUDO_USER/smap"
201 | priority = 2
202 | autorestart = true
203 | user = $SUDO_USER
204 | stdout_logfile = /var/log/scheduler.stdout.log
205 | stderr_logfile = /var/log/scheduler.stderr.log
206 | stdout_logfile_maxbytes = 50MB
207 | stdout_logfile_backups = 5
208 | stderr_logfile_maxbytes = 50MB
209 | stderr_logfile_backups = 5
210 | EOF
211 |
212 | mv scheduler.conf /etc/supervisor/conf.d/scheduler.conf
213 |
214 | cat < writeback.ini
215 | [/]
216 | uuid = f8b523d6-5a53-11e4-b74e-0cc47a0f7eea
217 |
218 | [server]
219 | port = 7979
220 |
221 | [/writeback]
222 | type = smap.services.writeback.WriteBack
223 | archiver = http://localhost:8079
224 | smap_dir = /tmp
225 | rate = 60
226 | EOF
227 |
228 | mv writeback.ini /etc/smap/.
229 |
230 | cat < writeback.conf
231 | [program:writeback]
232 | command = twistd --pidfile=writeback.pid -n smap /etc/smap/writeback.ini
233 | directory = /var/smap
234 | environment=PYTHONPATH="/home/$SUDO_USER/smap"
235 | priority = 2
236 | autorestart = true
237 | user = $SUDO_USER
238 | stdout_logfile = /var/log/writeback.stdout.log
239 | stderr_logfile = /var/log/writeback.stderr.log
240 | stdout_logfile_maxbytes = 50MB
241 | stdout_logfile_backups = 5
242 | stderr_logfile_maxbytes = 50MB
243 | stderr_logfile_backups = 5
244 | EOF
245 |
246 | mv writeback.conf /etc/supervisor/conf.d/writeback.confg
247 |
248 | supervisorctl update
249 |
250 | npm install -g spin
251 | chown -R $SUDO_USER .npm
252 | chown -R $SUDO_USER tmp
253 | chown -R $SUDO_USER .meteor
254 | mkdir -p .meteorite
255 | chown -R $SUDO_USER .meteorite
256 | chown -R $SUDO_USER openbas
257 | addgroup smap
258 | adduser $SUDO_USER smap
259 | adduser smap smap
260 | chgrp -R smap /var/smap
261 | chmod g+rwx /var/smap
262 |
--------------------------------------------------------------------------------
/install_virtual.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # This is the install script for the virtual building drivers
4 |
5 | if [ $(whoami) != "root" ]
6 | then
7 | echo "You need to curl this script into 'sudo bash' not just 'bash'"
8 | exit 1
9 | fi
10 |
11 | curl http://install.openbas.cal-sdb.org/vbuilding.ini > /etc/smap/vbuilding.ini
12 |
13 | cat < vbuilding.conf
14 | [program:vbuilding]
15 | command = /usr/bin/twistd --pidfile=/var/run/smap/vbuilding.pid -n smap /etc/smap/vbuilding.ini
16 | priority = 2
17 | autorestart = true
18 | user = smap
19 | directory = /var/smap/
20 | stdout_logfile = /var/log/vbuilding.stdout.log
21 | stdout_logfile_maxbytes = 50MB
22 | stdout_logfile_backups = 5
23 | stderr_logfile = /var/log/vbuilding.stderr.log
24 | stderr_logfile_maxbytes = 50MB
25 | stderr_logfile_backups = 5
26 | EOF
27 |
28 | mv vbuilding.conf /etc/supervisor/conf.d/
29 |
30 | echo "Configuration files added, updating supervisor"
31 | supervisorctl update
32 |
--------------------------------------------------------------------------------
/introPics.pptx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/introPics.pptx
--------------------------------------------------------------------------------
/maketarball.sh:
--------------------------------------------------------------------------------
1 | echo "[]" > openbas/private/testrooms.json
2 | cd openbas
3 | which mrt
4 | if [ $? != 0 ]
5 | then
6 | echo "No MRT, assuming clean repository"
7 | else
8 | mrt reset
9 | fi
10 | cd ..
11 | git submodule update --init
12 | rm -f openbas.tgz
13 | tar zcf openbas.tgz openbas upmu-plotter
14 | git checkout openbas/private
15 | echo "Done"
16 |
--------------------------------------------------------------------------------
/monitor/README.md:
--------------------------------------------------------------------------------
1 | This is a collection of scripts and apps built over the sMAP archiver to work alongside OpenBAS.
2 |
--------------------------------------------------------------------------------
/monitor/example.txt:
--------------------------------------------------------------------------------
1 | ********** HVAC Monthly Report **********
2 |
3 | ##### Demand #####
4 | Total Demand (past 30 days): 113208.4
5 | ==================
6 | Max Inst. Demand: 19.96 kW, 2014-09-26 20:41:15
7 | Min Inst. Demand: 0.00 kW, 2014-09-25 23:12:55
8 | ==================
9 | Max Daily Total Demand: 34845.76 kW, 2014-09-30 00:00:00
10 | Min Daily Total Demand: 6166.08 kW, 2014-09-27 00:00:00
11 | ==================
12 | Max Daily Avg Demand: 11.40 kW, 2014-09-25 00:00:00
13 | Min Daily Avg Demand: 2.91 kW, 2014-09-28 00:00:00
14 | ==================
15 |
16 | ##### Zones #####
17 | **** DOSA ****
18 | Total Cooling Time (past 30 days): 1:55:25
19 | Total Heating Time (past 30 days): 0:03:00
20 | Total Off Time (past 30 days): 4 days, 17:40:00
21 | kW for cooling: 4.84933884298
22 | ==================
23 | Max Inst. Temperature : 74.0 F, 2014-09-26 02:55:39
24 | Min Inst. Temperature: -1.0 F, 2014-09-25 19:52:50
25 | Max Avg. Temperature: 73.0850853549 F, 2014-09-28 00:00:00
26 | Min Avg. Temperature: 72.1263818794 F, 2014-09-27 00:00:00
27 |
28 | **** Open ****
29 | Total Cooling Time (past 30 days): 0:00:00
30 | Total Heating Time (past 30 days): 0:03:35
31 | Total Off Time (past 30 days): 4 days, 18:15:40
32 | kW for cooling: 0
33 | ==================
34 | Max Inst. Temperature : 75.0 F, 2014-09-27 23:24:58
35 | Min Inst. Temperature: -1.0 F, 2014-09-25 19:46:07
36 | Max Avg. Temperature: 73.3124163116 F, 2014-09-28 00:00:00
37 | Min Avg. Temperature: 72.1644907648 F, 2014-09-30 00:00:00
38 |
39 | **** South ****
40 | Total Cooling Time (past 30 days): 6:02:15
41 | Total Heating Time (past 30 days): 0:00:00
42 | Total Off Time (past 30 days): 2 days, 1:37:30
43 | kW for cooling: 5.4542680359
44 | ==================
45 | Max Inst. Temperature : 214748364.7 F, 2014-09-26 23:58:04
46 | Min Inst. Temperature: -214748364.7 F, 2014-09-25 21:17:58
47 | Max Avg. Temperature: -103711.277503 F, 2014-09-29 00:00:00
48 | Min Avg. Temperature: -1143607.9526 F, 2014-09-26 00:00:00
49 |
50 | **** Server ****
51 | Total Cooling Time (past 30 days): 22:16:55
52 | Total Heating Time (past 30 days): 0:00:00
53 | Total Off Time (past 30 days): 2 days, 11:19:40
54 | kW for cooling: 3.8184901401
55 | ==================
56 | Max Inst. Temperature : 214748364.7 F, 2014-09-29 18:27:07
57 | Min Inst. Temperature: -214748364.7 F, 2014-09-29 18:25:55
58 | Max Avg. Temperature: 72.0344349347 F, 2014-09-28 00:00:00
59 | Min Avg. Temperature: -137469.736315 F, 2014-09-30 00:00:00
60 |
--------------------------------------------------------------------------------
/monitor/report/app.py:
--------------------------------------------------------------------------------
1 | from flask import Flask, render_template, url_for, jsonify
2 | import json
3 |
4 | def fix_name(name):
5 | return name.replace(" ","_")
6 |
7 | data = json.load(open('report.json'))
8 | app = Flask(__name__)
9 | app.add_template_global(fix_name)
10 |
11 | @app.route("/")
12 | def report():
13 | total_demand = data['demand']['Total Demand']
14 | disaggregation_results = data['disaggregate']
15 | reportdata = {k:v for k,v in data['demand'].iteritems() if k not in ["Demand Data", "Total Demand"]}
16 | return render_template("index.html", total_demand=total_demand, demand=reportdata, disaggregation_results=disaggregation_results, zones=data['zones'])
17 |
18 | @app.route("/demanddata")
19 | def demanddata():
20 | return jsonify({'data': [{'date': k,'value': v} for k,v in data['demand']['Demand Data'].iteritems()]})
21 |
22 | @app.route("/zonedata//")
23 | def zonedata(key, zone):
24 | """
25 | key: "Min Daily Avg Demand" from report.demand_report
26 | zone: zone name like "DOSA"
27 | """
28 | key = key.replace("_"," ")
29 | ret = {}
30 | for ts, dd in data['demand'][key]['Data'][zone].iteritems():
31 | ret[ts] = [{'date': k, 'value': v} for k,v in dd.iteritems()]
32 | return jsonify(ret)
33 |
34 | @app.route("/histogram/")
35 | def histogram(zone):
36 | return jsonify({'data': data['disaggregate_histograms'][zone]})
37 |
38 | @app.route("/hvacdemand")
39 | def hvacdemand():
40 | return jsonify(data['hvac_demand'])
41 |
42 | if __name__=='__main__':
43 | app.run(host="0.0.0.0",debug=True)
44 |
--------------------------------------------------------------------------------
/monitor/report/static/debut-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/monitor/report/static/debut-light.png
--------------------------------------------------------------------------------
/monitor/report/static/report.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
3 | font-weight: 200;
4 | font-size: 150%;
5 | padding: 0;
6 | background-image: url('debut-light.png');
7 | background-repeat: repeat-repeat;
8 | -webkit-user-select: none;
9 | -khtml-user-select: none;
10 | -moz-user-select: none;
11 | -o-user-select: none;
12 | user-select: none;
13 | }
14 |
15 | .center {
16 | text-align: center;
17 | }
18 |
19 | .col {
20 | padding-left: 50px;
21 | padding-right: 50px;
22 | }
23 |
24 | .plot-well {
25 | min-height: 20px;
26 | padding: 10px;
27 | margin-bottom: 10px;
28 | background-color: #fff;
29 | border: 1px solid #e3e3e3;
30 | border-radius: 4px;
31 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
32 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
33 | }
34 |
35 |
36 | .limit {
37 | max-width: 2000px;
38 | min-width: 2000px;
39 | padding: 20px;
40 | margin: 20px;
41 | }
42 |
43 | path {
44 | stroke: steelblue;
45 | stroke-width: 1;
46 | fill: none;
47 | }
48 |
49 | .temp {
50 | fill: none;
51 | stroke: black;
52 | }
53 |
54 | .temp_heat {
55 | fill: none;
56 | stroke: red;
57 | }
58 |
59 | .temp_cool {
60 | fill: none;
61 | stroke: blue;
62 | }
63 |
64 | .x.axis line, .x.axis path, .y.axis line, .y.axis path {
65 | fill: none;
66 | stroke: #000;
67 | }
68 |
69 | .bar rect {
70 | fill: lightcoral;
71 | shape-rendering: crispEdges;
72 | }
73 |
74 | .cleanwell {
75 | background-color: #fff;
76 | padding: 50px;
77 | }
78 |
--------------------------------------------------------------------------------
/monitor/report/templates/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Building Report
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
Building Report
20 |
21 |
22 |
Demand
23 |
Total Demand
24 |
{{ total_demand['Amount'] }} kW : {{ total_demand['Date'] }}
25 |
26 |
27 | {% for zone in zones %}
28 |
29 |
30 |
{{ zone }} : {{ disaggregation_results[zone] }} kW
31 |
32 |
33 |
34 | {% endfor %}
35 | {% for k,v in demand.iteritems() %}
36 |
{{ k }}
37 |
{{ v['Amount'] }} kW : {{ v['Date'] }}
38 |
39 |
40 | {% if v.has_key('Data') %}
41 | {% for zone, plotnames in v['Data'].iteritems() %}
42 |
45 | {% endfor %}
46 | {% endif %}
47 |
48 |
49 | {% endfor %}
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/openbas/.meteor/.finished-upgraders:
--------------------------------------------------------------------------------
1 | # This file contains information which helps Meteor properly upgrade your
2 | # app when you run 'meteor update'. You should check it into version control
3 | # with your project.
4 |
5 | notices-for-0.9.0
6 | notices-for-0.9.1
7 |
--------------------------------------------------------------------------------
/openbas/.meteor/.gitignore:
--------------------------------------------------------------------------------
1 | local
2 |
--------------------------------------------------------------------------------
/openbas/.meteor/.id:
--------------------------------------------------------------------------------
1 | # This file contains a token that is unique to your project.
2 | # Check it into your repository along with the rest of this directory.
3 | # It can be used for purposes such as:
4 | # - ensuring you don't accidentally deploy one app on top of another
5 | # - providing package authors with aggregated statistics
6 |
7 | 2hcmqjoy6hmr10sulzx
8 |
--------------------------------------------------------------------------------
/openbas/.meteor/cordova-plugins:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/openbas/.meteor/packages:
--------------------------------------------------------------------------------
1 | # Meteor packages used by this project, one per line.
2 | #
3 | # 'meteor add' and 'meteor remove' will edit this file for you,
4 | # but you can also edit it by hand.
5 | standard-app-packages
6 | autopublish
7 | insecure
8 | underscore
9 | accounts-base
10 | accounts-password
11 | http
12 | jquery
13 | kidovate:bootstrap-slider
14 | mrt:moment
15 | s3ui
16 | anytime
17 | vakata-jstree
18 | timezone-js
19 | jquery-simplecolorpicker
20 | newd3
21 | iron:router
22 | mizzao:jquery-ui
23 | mizzao:bootstrap-3
24 | cfs:standard-packages
25 | cfs:filesystem
26 | ian:accounts-ui-bootstrap-3
27 |
28 |
--------------------------------------------------------------------------------
/openbas/.meteor/release:
--------------------------------------------------------------------------------
1 | METEOR@0.9.3.1
2 |
--------------------------------------------------------------------------------
/openbas/.meteor/versions:
--------------------------------------------------------------------------------
1 | accounts-base@1.1.1
2 | accounts-password@1.0.2
3 | anti:i18n@0.4.3
4 | anytime@0.0.0
5 | application-configuration@1.0.2
6 | autopublish@1.0.0
7 | autoupdate@1.1.1
8 | base64@1.0.0
9 | binary-heap@1.0.0
10 | blaze-tools@1.0.0
11 | blaze@2.0.1
12 | boilerplate-generator@1.0.0
13 | callback-hook@1.0.0
14 | cfs:access-point@0.0.0
15 | cfs:base-package@0.0.0
16 | cfs:collection-filters@0.0.0
17 | cfs:collection@0.0.0
18 | cfs:data-man@0.0.0
19 | cfs:file@0.0.0
20 | cfs:filesystem@0.0.0
21 | cfs:http-methods@0.0.24
22 | cfs:http-publish@0.0.0
23 | cfs:power-queue@0.0.1
24 | cfs:reactive-list@0.0.0
25 | cfs:reactive-property@0.0.0
26 | cfs:standard-packages@0.0.2
27 | cfs:storage-adapter@0.0.0
28 | cfs:tempstore@0.0.2
29 | cfs:upload-http@0.0.2
30 | cfs:worker@0.0.0
31 | check@1.0.1
32 | col-resizable@0.0.0
33 | ctl-helper@1.0.3
34 | ctl@1.0.1
35 | ddp@1.0.9
36 | deps@1.0.4
37 | ejson@1.0.3
38 | email@1.0.3
39 | fastclick@1.0.0
40 | follower-livedata@1.0.1
41 | geojson-utils@1.0.0
42 | handlebars@1.0.0
43 | html-tools@1.0.1
44 | htmljs@1.0.1
45 | http@1.0.6
46 | ian:accounts-ui-bootstrap-3@1.1.11
47 | id-map@1.0.0
48 | insecure@1.0.0
49 | iron:core@0.3.4
50 | iron:dynamic-template@0.4.1
51 | iron:layout@0.4.1
52 | iron:router@0.9.4
53 | jquery-migrate@0.0.0
54 | jquery-simplecolorpicker@0.0.0
55 | jquery@1.0.0
56 | json@1.0.0
57 | kidovate:bootstrap-slider@0.0.5
58 | livedata@1.0.10
59 | localstorage@1.0.0
60 | logging@1.0.3
61 | meteor-platform@1.1.1
62 | meteor@1.1.1
63 | minifiers@1.1.0
64 | minimongo@1.0.3
65 | mizzao:bootstrap-3@3.2.0_1
66 | mizzao:build-fetcher@0.2.0
67 | mizzao:jquery-ui@1.11.0
68 | mobile-status-bar@1.0.0
69 | mongo-livedata@1.0.5
70 | mongo@1.0.6
71 | mrt:moment@2.8.1
72 | newd3@0.0.0
73 | npm-bcrypt@0.7.7
74 | observe-sequence@1.0.2
75 | ordered-dict@1.0.0
76 | raix:eventemitter@0.0.1
77 | random@1.0.0
78 | reactive-dict@1.0.3
79 | reactive-var@1.0.2
80 | reload@1.1.0
81 | retry@1.0.0
82 | routepolicy@1.0.1
83 | s3ui@0.0.0
84 | service-configuration@1.0.1
85 | session@1.0.2
86 | sha@1.0.0
87 | spacebars-compiler@1.0.2
88 | spacebars@1.0.2
89 | srp@1.0.0
90 | standard-app-packages@1.0.2
91 | stylus@1.0.4
92 | templating@1.0.7
93 | timezone-js@0.0.0
94 | tracker@1.0.2
95 | ui@1.0.3
96 | underscore@1.0.0
97 | url@1.0.0
98 | vakata-jstree@0.0.0
99 | webapp-hashing@1.0.0
100 | webapp@1.1.2
101 |
--------------------------------------------------------------------------------
/openbas/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, Regents of the University of California
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
--------------------------------------------------------------------------------
/openbas/building.ini:
--------------------------------------------------------------------------------
1 | [report 0]
2 | ReportDeliveryLocation = mongo://localhost:3001
3 |
4 | [report 1]
5 | ReportDeliveryLocation = http://localhost:8079/add/lVzBMDpnkXApJmpjUDSvm4ceGfpbrLLSd9cq
6 |
7 | [report 2]
8 | ReportDeliveryLocation = http://archiver.cal-sdb.org:9000/data/legacyadd/likeaboss
9 |
10 | [/]
11 | uuid = 06d634a6-1c03-11e4-965d-6003089ed1d0
12 | Metadata/SourceName = Demo Driver
13 | Metadata/Building = Soda Hall
14 | Metadata/Site = 0273d18f-1c03-11e4-a490-6003089ed1d0
15 | Metadata/configured = True
16 |
17 | [server]
18 | port = 8081
19 |
20 | [/buildinglighting]
21 | type = Collection
22 | Metadata/System = Lighting
23 | Metadata/Role = Building Lighting
24 | Metadata/Floor = 4
25 | Metadata/Name = 4th Floor Lighting
26 |
27 | [/buildinglighting/group0]
28 | type = smap.drivers.lights.virtuallight.VirtualLight
29 | Metadata/Group = 0
30 | Metadata/LightingZone = 410 Soda
31 |
32 | [/buildinglighting/group1]
33 | type = smap.drivers.lights.virtuallight.VirtualLight
34 | Metadata/Group = 1
35 | Metadata/LightingZone = 410 Soda
36 |
37 | [/buildinglighting/group2]
38 | type = smap.drivers.lights.virtuallight.VirtualLight
39 | Metadata/Group = 2
40 | Metadata/LightingZone = 420 Soda
41 |
42 | [/buildinghvac]
43 | type = Collection
44 | Metadata/System = HVAC
45 | Metadata/Role = Building HVAC
46 | Metadata/Floor = 4
47 | Metadata/Name = 4th Floor HVAC
48 |
49 | [/buildinghvac/thermostat0]
50 | type = smap.drivers.thermostats.virtualthermostat.VirtualThermostat
51 | Metadata/Room = 410 Soda
52 | Metadata/HVACZone = 410 Soda W
53 | archiver=http://localhost:8079
54 | temp_heat=Metadata/HVACZone = '410 Soda W' and Metadata/Description = 'Zone Heating setpoint'
55 | temp_cool=Metadata/HVACZone = '410 Soda W' and Metadata/Description = 'Zone Cooling setpoint'
56 |
57 | [/buildinghvac/thermostat1]
58 | type = smap.drivers.thermostats.virtualthermostat.VirtualThermostat
59 | Metadata/Room = 410 Soda
60 | Metadata/HVACZone = 410 Soda E
61 | temp_heat=Metadata/HVACZone = '410 Soda E' and Metadata/Description = 'Zone Heating setpoint'
62 | temp_cool=Metadata/HVACZone = '410 Soda E' and Metadata/Description = 'Zone Cooling setpoint'
63 |
64 | [/buildinghvac/thermostat2]
65 | type = smap.drivers.thermostats.virtualthermostat.VirtualThermostat
66 | Metadata/Room = 411 Soda
67 | Metadata/HVACZone = 411 Soda
68 | temp_heat=Metadata/HVACZone = '411 Soda' and Metadata/Description = 'Zone Heating setpoint'
69 | temp_cool=Metadata/HVACZone = '411 Soda' and Metadata/Description = 'Zone Cooling setpoint'
70 |
71 | [/monitoring]
72 | type = Collection
73 | Metadata/System = Monitoring
74 | Metadata/Role = Monitoring
75 | Metadata/Floor = 4
76 | Metadata/Name = 4th Floor Monitoring
77 |
78 | [/monitoring/airtemphumidity0]
79 | type = smap.drivers.sensors.virtualATHsensor.VirtualATHSensor
80 | initialtemp = 68
81 | initialhumidity = 38
82 | Metadata/Type = Sensor
83 | Metadata/Room = 410 Soda
84 | Metadata/HVACZone = 410 Soda E
85 |
86 | [/monitoring/airtemphumidity1]
87 | type = smap.drivers.sensors.virtualATHsensor.VirtualATHSensor
88 | initialtemp = 69
89 | initialhumidity = 39
90 | Metadata/Type = Sensor
91 | Metadata/Room = 410 Soda
92 | Metadata/HVACZone = 410 Soda W
93 |
94 | [/monitoring/airtemphumidity2]
95 | type = smap.drivers.sensors.virtualATHsensor.VirtualATHSensor
96 | initialtemp = 70
97 | initialhumidity = 40
98 | Metadata/Type = Sensor
99 | Metadata/Room = 410 Soda
100 | Metadata/HVACZone = 410 Soda W
101 |
102 | [/monitoring/airtemphumidity3]
103 | type = smap.drivers.sensors.virtualATHsensor.VirtualATHSensor
104 | initialtemp = 71
105 | initialhumidity = 41
106 | Metadata/Type = Sensor
107 | Metadata/Room = 411 Soda
108 | Metadata/HVACZone = 411 Soda
109 |
110 | [/monitoring/illumination0]
111 | type = smap.drivers.sensors.virtualLightsensor.VirtualLightSensor
112 | initiallux = 140
113 | Metadata/Type = Sensor
114 | Metadata/Room = 410 Soda
115 | Metadata/LightingZone = 410 Soda
116 |
117 | [/monitoring/illumination1]
118 | type = smap.drivers.sensors.virtualLightsensor.VirtualLightSensor
119 | initiallux = 100
120 | Metadata/Type = Sensor
121 | Metadata/Room = 410 Soda
122 | Metadata/LightingZone = 410 Soda
123 |
124 | [/monitoring/illumination2]
125 | type = smap.drivers.sensors.virtualLightsensor.VirtualLightSensor
126 | initiallux = 300
127 | Metadata/Type = Sensor
128 | Metadata/Room = 420 Soda
129 | Metadata/LightingZone = 420 Soda
130 |
131 | [/monitoring/illumination3]
132 | type = smap.drivers.sensors.virtualLightsensor.VirtualLightSensor
133 | initiallux = 200
134 | Metadata/Type = Sensor
135 | Metadata/Room = 420 Soda
136 | Metadata/LightingZone = 420 Soda
137 |
138 | [/monitoring/demand0]
139 | type = smap.drivers.sensors.virtualpowermeter.VirtualPowerMeter
140 | Metadata/Type = Sensor
141 | Metadata/Room = Soda Hall
142 |
143 | [/tasklighting]
144 | type = Collection
145 | Metadata/System = Lighting
146 | Metadata/Role = Task Lighting
147 |
148 | [/tasklighting/group0]
149 | type = smap.drivers.lights.virtuallight.VirtualLight
150 | Metadata/Type = Command
151 | Metadata/Group = 0
152 | Metadata/LightingZone = 410 Soda
153 |
154 | [/tasklighting/group1]
155 | type = smap.drivers.lights.virtuallight.VirtualLight
156 | Metadata/Type = Command
157 | Metadata/Group = 1
158 | Metadata/LightingZone = 410 Soda
159 |
160 | [/tasklighting/group2]
161 | type = smap.drivers.lights.virtuallight.VirtualLight
162 | Metadata/Type = Command
163 | Metadata/Group = 2
164 | Metadata/LightingZone = 420 Soda
165 |
166 | ## General Control ##
167 | [/generalcontrol]
168 | type = Collection
169 | Metadata/System = GeneralControl
170 |
171 | [/generalcontrol/controller1]
172 | type = smap.drivers.virtualcontroller.VirtualController
173 | Metadata/HVACZone = 410 Soda W
174 | Metadata/Room = 410 Soda
175 | Metadata/Name = Virtual Space Heater
176 |
177 | [/generalcontrol/controller2]
178 | type = smap.drivers.virtualcontroller.VirtualController
179 | Metadata/HVACZone = 410 Soda W
180 | Metadata/Room = 410 Soda
181 | Metadata/Name = Virtual Fan
182 |
183 | ## Zone Controller ##
184 | [/zonecontroller1]
185 | type = followcontroller.FollowMaster
186 | subscribe/temp_heat = Metadata/Description = 'Master Heating setpoint'
187 | subscribe/temp_cool = Metadata/Description = 'Master Cooling setpoint'
188 | archiver = http://localhost:8079
189 | synchronous = False
190 | Metadata/HVACZone = 410 Soda W
191 |
192 | [/zonecontroller1/temp_heat]
193 | Metadata/Description = Zone Heating setpoint
194 | Metadata/Type = Setpoint
195 |
196 | [/zonecontroller1/temp_cool]
197 | Metadata/Description = Zone Cooling setpoint
198 | Mnetadata/Type = Setpoint
199 |
200 | [/zonecontroller2]
201 | type = followcontroller.FollowMaster
202 | subscribe/temp_heat = Metadata/Description = 'Master Heating setpoint'
203 | subscribe/temp_cool = Metadata/Description = 'Master Cooling setpoint'
204 | archiver = http://localhost:8079
205 | synchronous = False
206 | Metadata/HVACZone = 410 Soda E
207 |
208 | [/zonecontroller2/temp_heat]
209 | Metadata/Description = Zone Heating setpoint
210 | Metadata/Type = Setpoint
211 |
212 | [/zonecontroller2/temp_cool]
213 | Metadata/Description = Zone Cooling setpoint
214 | Mnetadata/Type = Setpoint
215 |
216 | [/zonecontroller3]
217 | type = followcontroller.FollowMaster
218 | subscribe/temp_heat = Metadata/Description = 'Master Heating setpoint'
219 | subscribe/temp_cool = Metadata/Description = 'Master Cooling setpoint'
220 | archiver = http://localhost:8079
221 | synchronous = False
222 | Metadata/HVACZone = 411 Soda
223 |
224 | [/zonecontroller3/temp_heat]
225 | Metadata/Description = Zone Heating setpoint
226 | Metadata/Type = Setpoint
227 |
228 | [/zonecontroller3/temp_cool]
229 | Metadata/Description = Zone Cooling setpoint
230 | Mnetadata/Type = Setpoint
231 |
232 | ## Master Scheduler ##
233 | [/masterscheduler]
234 | type = smap.services.scheduler.Scheduler
235 | rate = 1
236 | source = mongodb://localhost:3001/meteor
237 |
238 | [/masterscheduler/temp_heat]
239 | Metadata/Description = Master Heating setpoint
240 | Metadata/System = Schedule
241 | Metadata/Type = Setpoint
242 |
243 | [/masterscheduler/temp_cool]
244 | Metadata/Description = Master Cooling setpoint
245 | Metadata/System = Schedule
246 | Metadata/Type = Setpoint
247 |
248 | [/masterscheduler/on]
249 | Metadata/Description = Master Lighting control
250 | Metadata/System = Schedule
251 | Metadata/Type = Command
252 |
--------------------------------------------------------------------------------
/openbas/client/building.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Rooms
5 |
6 |
7 |
8 | Name of the Space
9 | Description
10 | Exposure
11 | HVAC zone
12 | Lighting zone
13 | Edit
14 |
15 |
16 |
17 | {{#each rooms}}
18 |
19 | {{RoomNumber}}
20 | {{Description}}
21 | {{Exposure}}
22 | {{HVACZone}}
23 | {{LightingZone}}
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | {{/each}}
33 |
34 |
35 |
36 |
37 | Add room
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | {{#each floorplans}}
47 | {{> floorplan}}
48 | {{/each}}
49 |
50 | {{> upload_floorplan }}
51 |
52 |
53 |
54 |
71 |
72 |
73 |
74 |
75 | {{> add_room }}
76 |
77 |
78 |
79 |
136 |
137 |
138 | {{#each floorplans}}
139 | {{> floorplan}}
140 | {{/each}}
141 |
142 | Save
143 | Cancel
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
{{description}}
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 | Select a floorplan image file to upload:
167 |
168 |
191 |
192 |
193 |
194 |
195 |
--------------------------------------------------------------------------------
/openbas/client/building.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, Regents of the University of California
2 | // All rights reserved.
3 | //
4 | // Redistribution and use in source and binary forms, with or without
5 | // modification, are permitted provided that the following conditions are met:
6 | //
7 | // 1. Redistributions of source code must retain the above copyright notice, this
8 | // list of conditions and the following disclaimer.
9 | //
10 | // 2. Redistributions in binary form must reproduce the above copyright notice,
11 | // this list of conditions and the following disclaimer in the documentation
12 | // and/or other materials provided with the distribution.
13 | //
14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
25 | Template.add_room.rendered = function() {
26 | var rooms = Rooms.find({}).fetch();
27 | var hvac_zones = _.pluck(rooms, 'HVACZone');
28 | var descriptions = _.pluck(rooms, 'Description');
29 | var lighting_zones = _.pluck(rooms, 'LightingZones');
30 |
31 | $('#room-description').autocomplete({
32 | source: _.uniq(descriptions),
33 | minLength: 0,
34 | });
35 |
36 | $('#hvac-zone').autocomplete({
37 | source: _.uniq(hvac_zones),
38 | minLength: 0,
39 | });
40 |
41 | $('#lighting-zone').autocomplete({
42 | source: _.uniq(lighting_zones),
43 | minLength: 0,
44 | });
45 | };
46 |
47 | Template.add_room.events({
48 | 'click #save-room': function(){
49 | var marker = $(".floorplan-marker");
50 | if (marker.length){
51 | var image = marker.siblings('img');
52 | var floorplan_id = image.attr('id');
53 | var img_position_abs = image.position();
54 | var marker_position_abs = marker.position();
55 | var marker_position_rel = {
56 | 'top': marker_position_abs.top - img_position_abs.top,
57 | 'left': marker_position_abs.left - img_position_abs.left
58 | };
59 | } else {
60 | var marker_position_rel = null;
61 | }
62 | var r = {
63 | 'RoomNumber': $("#room-number").val(),
64 | 'Description': $("#room-description").val(),
65 | 'HVACZone': $("#hvac-zone").val(),
66 | 'LightingZone': $("#lighting-zone").val(),
67 | 'Exposure': $("#exposure").val(),
68 | 'FloorplanId': floorplan_id,
69 | 'MarkerPosition': marker_position_rel,
70 | };
71 | if (this._id != undefined){
72 | Rooms.update({_id: this._id}, r);
73 | } else {
74 | Rooms.insert(r);
75 | }
76 | Router.go('/building/');
77 | },
78 | 'click #cancel-room': function(){
79 | Router.go('/building/');
80 | },
81 | 'click .floorplan': function(event){
82 | $('.floorplan-marker').remove();
83 | var offsetX = -15;
84 | var offsetY = -31;
85 | var markerX = event.pageX + offsetX;
86 | var markerY = event.pageY + offsetY;
87 | var marker = $(' ')
88 | .attr('class', 'floorplan-marker glyphicon glyphicon-map-marker')
89 | .attr('display', 'none')
90 | .css('left', markerX + "px")
91 | .css('top', markerY + "px");
92 |
93 | $(event.target).parent('div').append(marker);
94 |
95 | jQuery({count: 100}).animate({count: 0},{
96 | duration: 1000,
97 | step: function(){
98 | marker.css('top', markerY - this.count)
99 | },
100 | easing: 'easeOutBounce',
101 | });
102 | },
103 | });
104 |
105 | Template.building.events({
106 | 'click #upload-floorplan': function(event, template) {
107 | $("#loading-gif").show();
108 | var file = $('#floorplan-file')[0].files[0];
109 | var description = $('#floorplan-description').val();
110 | FloorplansFS.insert(file, function (err, fileObj) {
111 | Floorplans.insert({"description": description, "file_id": fileObj._id});
112 | });
113 | Router.go('/building');
114 | },
115 | 'hover .floorplan-marker': function(event){
116 | var room_id = $(event.target).data('room');
117 | var room = Rooms.find({_id: room_id}).fetch();
118 | },
119 | 'click .floorplan-delete': function(event){
120 | var floorplan_id = $(event.target).data('floorplan');
121 | var modal = $('.modal')
122 | modal.modal('show');
123 | $('.modal #confirm').click(function(){
124 | Floorplans.remove({_id: floorplan_id});
125 | $('#floorplan-'+floorplan_id).fadeOut('slow');
126 | modal.modal('hide');
127 | });
128 | }
129 | });
130 |
131 | function place_marker(room){
132 | if (room.hasOwnProperty('MarkerPosition')){
133 | var img_pos = $('img#' + room.FloorplanId).position();
134 | var marker = $(' ')
135 | .attr('title', room.RoomNumber)
136 | .attr('class', 'floorplan-marker floorplan-marker-static glyphicon glyphicon-map-marker')
137 | .attr('data-room', room._id)
138 | .css('left', img_pos.left + room.MarkerPosition.left + "px")
139 | .css('top', img_pos.top + room.MarkerPosition.top + "px");
140 | $('div#floorplan-' + room.FloorplanId).append(marker);
141 |
142 | marker.click(function(){
143 | Router.go('/room/' + room.RoomNumber);
144 | });
145 | }
146 | }
147 |
148 | function draw_markers() {
149 | var rooms = Rooms.find({}).fetch();
150 | _.each(rooms, function(room){
151 | place_marker(room);
152 | });
153 | $(".floorplan-marker").tooltip({
154 | placement: "top",
155 | });
156 | }
157 |
158 | Template.building.rendered = function() {
159 | draw_markers();
160 | // make sure all images are loaded
161 | $(window).load(function(){
162 | draw_markers();
163 | });
164 | $(window).resize(function(){
165 | $('.floorplan-marker').remove();
166 | draw_markers();
167 | });
168 | };
169 |
170 | Template.building.floorplans = function() {
171 | return Floorplans.find({}, {reactive: false});
172 | };
173 | Template.add_room.floorplans = Template.building.floorplans;
174 |
175 | Template.building.rooms = function() {
176 | return Rooms.find({});
177 | };
178 |
179 | Template.floorplan.helpers({
180 | getImgPath: function(){
181 | var fpfile = FloorplansFS.findOne({'_id': this.file_id});
182 | if (fpfile.hasCopy('images')){
183 | return '/floorplans/' + fpfile.copies.images.key;
184 | } else {
185 | return '/img/ajax-loader.gif';
186 | }
187 | },
188 | });
189 |
190 | Template.edit_room.rendered = function() {
191 | $('.add-room-container').find('.panel-heading').html('Edit room');
192 | $('#room-number').val(this.data.RoomNumber);
193 | $('#room-description').val(this.data.Description);
194 | $('#hvac-zone').val(this.data.HVACZone);
195 | $('#lighting-zone').val(this.data.LightingZone);
196 | $('#exposure').val(this.data.Exposure);
197 | place_marker(this.data);
198 | };
199 |
--------------------------------------------------------------------------------
/openbas/client/compatibility/buttons.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Project: Buttons
3 | * Description: A highly customizable CSS button library built with Sass and Compass
4 | * Author: Alex Wolfe
5 | * License: Apache License v2.0
6 | */
7 |
8 | // the semi-colon before function invocation is a safety net against concatenated
9 | // scripts and/or other plugins which may not be closed properly.
10 | ;(function ( $, window, document, undefined ) {
11 | 'use strict';
12 |
13 | // undefined is used here as the undefined global variable in ECMAScript 3 is
14 | // mutable (ie. it can be changed by someone else). undefined isn't really being
15 | // passed in so we can ensure the value of it is truly undefined. In ES5, undefined
16 | // can no longer be modified.
17 |
18 | // window and document are passed through as local variable rather than global
19 | // as this (slightly) quickens the resolution process and can be more efficiently
20 | // minified (especially when both are regularly referenced in your plugin).
21 |
22 | // Create the defaults once
23 | var pluginName = "menuButton";
24 | var menuClass = ".button-dropdown";
25 | var defaults = {
26 | propertyName: "value"
27 | };
28 |
29 | // The actual plugin constructor
30 | function Plugin( element, options ) {
31 |
32 | //SET OPTIONS
33 | this.options = $.extend( {}, defaults, options );
34 | this._defaults = defaults;
35 | this._name = pluginName;
36 |
37 | //REGISTER ELEMENT
38 | this.$element = $(element);
39 |
40 | //INITIALIZE
41 | this.init();
42 | }
43 |
44 | Plugin.prototype = {
45 | constructor: Plugin,
46 |
47 | init: function() {
48 | // WE DON'T STOP PROPGATION SO CLICKS WILL AUTOMATICALLY
49 | // TOGGLE AND REMOVE THE DROPDOWN & OVERLAY
50 | this.toggle();
51 | },
52 |
53 | toggle: function(el, options) {
54 | if(this.$element.data('dropdown') === 'show') {
55 | this.hideMenu();
56 | }
57 | else {
58 | this.showMenu();
59 | }
60 | },
61 |
62 | showMenu: function() {
63 | this.$element.data('dropdown', 'show');
64 | this.$element.find('ul').show();
65 |
66 | if(this.$overlay) {
67 | this.$overlay.show();
68 | }
69 | else {
70 | this.$overlay = $('
');
71 | this.$element.append(this.$overlay);
72 | }
73 | },
74 |
75 | hideMenu: function() {
76 | this.$element.data('dropdown', 'hide');
77 | this.$element.find('ul').hide();
78 | this.$overlay.hide();
79 | }
80 | };
81 |
82 | // A really lightweight plugin wrapper around the constructor,
83 | // preventing against multiple instantiations
84 | $.fn[pluginName] = function ( options ) {
85 | return this.each(function () {
86 |
87 | // TOGGLE BUTTON IF IT EXISTS
88 | if ($.data(this, "plugin_" + pluginName)) {
89 | $.data(this, "plugin_" + pluginName).toggle();
90 | }
91 | // OTHERWISE CREATE A NEW INSTANCE
92 | else {
93 | $.data(this, "plugin_" + pluginName, new Plugin( this, options ));
94 | }
95 | });
96 | };
97 |
98 |
99 | //DELEGATE CLICK EVENT FOR DROPDOWN MENUS
100 | $(document).on('click', '[data-buttons=dropdown]', function(e) {
101 | var $dropdown = $(e.currentTarget);
102 | $dropdown.menuButton();
103 | });
104 |
105 | //IGNORE CLICK EVENTS FROM DISPLAY BUTTON IN DROPDOWN
106 | $(document).on('click', '[data-buttons=dropdown] > a', function(e) {
107 | e.preventDefault();
108 | });
109 |
110 | })( jQuery, window, document);
--------------------------------------------------------------------------------
/openbas/client/css/slider.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Slider for Bootstrap
3 | *
4 | * Copyright 2012 Stefan Petre
5 | * Licensed under the Apache License v2.0
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | */
9 | .slider {
10 | display: inline-block;
11 | vertical-align: middle;
12 | position: relative;
13 | }
14 | .slider.slider-horizontal {
15 | width: 210px;
16 | height: 20px;
17 | }
18 | .slider.slider-horizontal .slider-track {
19 | height: 10px;
20 | width: 100%;
21 | margin-top: -5px;
22 | top: 50%;
23 | left: 0;
24 | }
25 | .slider.slider-horizontal .slider-selection {
26 | height: 100%;
27 | top: 0;
28 | bottom: 0;
29 | }
30 | .slider.slider-horizontal .slider-handle {
31 | margin-left: -10px;
32 | margin-top: -5px;
33 | }
34 | .slider.slider-horizontal .slider-handle.triangle {
35 | border-width: 0 10px 10px 10px;
36 | width: 0;
37 | height: 0;
38 | border-bottom-color: #0480be;
39 | margin-top: 0;
40 | }
41 | .slider.slider-vertical {
42 | height: 210px;
43 | width: 20px;
44 | }
45 | .slider.slider-vertical .slider-track {
46 | width: 10px;
47 | height: 100%;
48 | margin-left: -5px;
49 | left: 50%;
50 | top: 0;
51 | }
52 | .slider.slider-vertical .slider-selection {
53 | width: 100%;
54 | left: 0;
55 | top: 0;
56 | bottom: 0;
57 | }
58 | .slider.slider-vertical .slider-handle {
59 | margin-left: -5px;
60 | margin-top: -10px;
61 | }
62 | .slider.slider-vertical .slider-handle.triangle {
63 | border-width: 10px 0 10px 10px;
64 | width: 1px;
65 | height: 1px;
66 | border-left-color: #0480be;
67 | margin-left: 0;
68 | }
69 | .slider input {
70 | display: none;
71 | }
72 | .slider .tooltip-inner {
73 | white-space: nowrap;
74 | }
75 | .slider-track {
76 | position: absolute;
77 | cursor: pointer;
78 | background-color: #f7f7f7;
79 | background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9);
80 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));
81 | background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9);
82 | background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9);
83 | background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9);
84 | background-repeat: repeat-x;
85 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);
86 | -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
87 | -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
88 | box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
89 | -webkit-border-radius: 4px;
90 | -moz-border-radius: 4px;
91 | border-radius: 4px;
92 | }
93 | .slider-selection {
94 | position: absolute;
95 | background-color: #f7f7f7;
96 | background-image: -moz-linear-gradient(top, #f9f9f9, #f5f5f5);
97 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f9f9f9), to(#f5f5f5));
98 | background-image: -webkit-linear-gradient(top, #f9f9f9, #f5f5f5);
99 | background-image: -o-linear-gradient(top, #f9f9f9, #f5f5f5);
100 | background-image: linear-gradient(to bottom, #f9f9f9, #f5f5f5);
101 | background-repeat: repeat-x;
102 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff9f9f9', endColorstr='#fff5f5f5', GradientType=0);
103 | -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
104 | -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
105 | box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
106 | -webkit-box-sizing: border-box;
107 | -moz-box-sizing: border-box;
108 | box-sizing: border-box;
109 | -webkit-border-radius: 4px;
110 | -moz-border-radius: 4px;
111 | border-radius: 4px;
112 | }
113 | .slider-handle {
114 | position: absolute;
115 | width: 20px;
116 | height: 20px;
117 | background-color: #0e90d2;
118 | background-image: -moz-linear-gradient(top, #149bdf, #0480be);
119 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));
120 | background-image: -webkit-linear-gradient(top, #149bdf, #0480be);
121 | background-image: -o-linear-gradient(top, #149bdf, #0480be);
122 | background-image: linear-gradient(to bottom, #149bdf, #0480be);
123 | background-repeat: repeat-x;
124 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);
125 | -webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
126 | -moz-box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
127 | box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05);
128 | opacity: 0.8;
129 | border: 0px solid transparent;
130 | }
131 | .slider-handle.round {
132 | -webkit-border-radius: 20px;
133 | -moz-border-radius: 20px;
134 | border-radius: 20px;
135 | }
136 | .slider-handle.triangle {
137 | background: transparent none;
138 | }
--------------------------------------------------------------------------------
/openbas/client/dashboard.css:
--------------------------------------------------------------------------------
1 | .dashboard-column {
2 | padding: 5px;
3 | }
4 |
5 | h2.dashboard-column-heading {
6 | margin-top: 0px;
7 | margin-bottom: 5px;
8 | color: #939393;
9 | font-size: 1.5em;
10 | text-align: center;
11 | text-shadow: -1px 1px 2px #ccc;
12 | }
13 |
14 | h3.dashboard-column-heading {
15 | font-size: 1.2em;
16 | margin-top: 9px;
17 | }
18 |
19 | .dark {
20 | background: #f5f5f5;
21 | }
22 |
23 | .infobox {
24 | font-size: small;
25 | }
26 |
27 | .centertext {
28 | margin-left: auto;
29 | margin-right: auto;
30 | text-align: center;
31 | }
32 |
33 | .sensorbox {
34 | outline: 1px solid black;
35 | padding: 5px;
36 | }
37 |
38 | .dashboard-well {
39 | min-height: 20px;
40 | padding: 10px;
41 | margin-bottom: 10px;
42 | background-color: #fff;
43 | border: 1px solid #e3e3e3;
44 | border-radius: 4px;
45 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
46 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05);
47 | }
48 |
49 | .sparkline {
50 | fill: none;
51 | stroke: #000;
52 | stroke-width: 0.5px;
53 | }
54 |
55 | .sparkline-container {
56 | float: left;
57 | margin-right: 6px;
58 | }
59 |
60 | svg.HVAC-zone-summary {
61 | display: block;
62 | margin: 0 auto;
63 | }
64 |
65 | div.HVAC-zone-summary {
66 | margin-top: 10px;
67 | margin-bottom: 10px;
68 | }
69 |
70 | .heattempline {
71 | fill: none;
72 | stroke: #ff0000;
73 | stroke-width: 1px;
74 | }
75 |
76 | .cooltempline {
77 | fill: none;
78 | stroke: #0000ff;
79 | stroke-width: 1px;
80 | }
81 |
82 | .templine {
83 | fill: none;
84 | stroke: #000;
85 | stroke-width: 1px;
86 | }
87 |
88 | .table-heading {
89 | padding-top: 5px;
90 | padding-bottom: 5px;
91 | font-size: small;
92 | }
93 |
94 | .zone-detail-container {
95 | margin: 0 auto;
96 | width: 70%;
97 | max-width: 800px;
98 | }
99 |
100 | .demand-widget {
101 | height: 75px;
102 | }
103 |
104 | td.day {
105 | text-align: center;
106 | }
107 |
108 | td.current-day {
109 | font-weight: bold;
110 | }
111 |
112 | .current-period{
113 | background-color: #FEFFF2;
114 | }
115 |
116 | .overlay {
117 | fill: none;
118 | pointer-events: all;
119 | }
120 |
121 | .focus text {
122 | font-size: 1.2em;
123 | }
124 |
125 | /* Chrome, Safari, Opera */
126 | @-webkit-keyframes snowflake {
127 | 0% {color: blue;}
128 | 50% {color: #ADD8E6;}
129 | 100% {color: red;}
130 | }
131 |
132 | /* Standard syntax */
133 | @keyframes snowflake {
134 | 0% {color: blue;}
135 | 30% {color: #ADD8E6;}
136 | 100% {color: blue;}
137 | }
138 |
139 | /* Chrome, Safari, Opera */
140 | @-webkit-keyframes flame {
141 | 0% {color: red;}
142 | 50% {color: #FFA500;}
143 | 100% {color: red;}
144 | }
145 |
146 | /* Standard syntax */
147 | @keyframes flame {
148 | 0% {color: red;}
149 | 30% {color: #FFA500;}
150 | 100% {color: red;}
151 | }
152 |
153 | span#snowflake {
154 | -webkit-animation-name: snowflake;
155 | -webkit-animation-duration: 1.5s;
156 | -webkit-animation-iteration-count: infinite;
157 | -webkit-animation-timing-function: linear;
158 | }
159 |
160 | @-webkit-keyframes snowflake {
161 | from {
162 | -webkit-transform: rotate( 0deg );
163 | }
164 | to {
165 | -webkit-transform: rotate( 360deg );
166 | }
167 | }
168 |
169 | span#flame {
170 | -webkit-animation: flame 4s linear infinite; /* Chrome, Safari, Opera */
171 | animation: flame 4s linear infinite;
172 | }
173 |
174 | .HVAC-status-label {
175 | margin-left: 7px;
176 | }
177 |
178 | .HVAC-state-container {
179 | font-size: 1.2em;
180 | text-align: center;
181 | margin-top: 5px;
182 | margin-bottom: 10px;
183 | }
184 |
--------------------------------------------------------------------------------
/openbas/client/openbas.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
3 | font-weight: 200;
4 | font-size: 150%;
5 | padding: 0;
6 | background-image: url(img/debut-light.png);
7 | background-repeat: repeat-repeat;
8 | -webkit-user-select: none;
9 | -khtml-user-select: none;
10 | -moz-user-select: none;
11 | -o-user-select: none;
12 | user-select: none;
13 | }
14 |
15 | .point {
16 | padding: 5px;
17 | }
18 |
19 | .value {
20 | font-size: 1.25em;
21 | font-weight: bold;
22 | color: #777;
23 | }
24 |
25 | .point .actuator {
26 | display: inline-block;
27 | width: 100px;
28 | padding-left: 10px;
29 | text-align: right;
30 | font-size: 2em;
31 | font-weight: bold;
32 | color: #777;
33 | }
34 |
35 | #actuators {
36 | width: 900px;
37 | margin: 0 auto;
38 | }
39 |
40 | div.container {
41 | width: 900px;
42 | margin: 0 auto;
43 | }
44 |
45 | div.schedule-container {
46 | width: 500px;
47 | margin: 0 auto;
48 | }
49 |
50 | div.rooms-container {
51 | width: 600px;
52 | margin: 0 auto;
53 | }
54 |
55 | div.add-room-container {
56 | width: 400px;
57 | margin: 0 auto;
58 | }
59 |
60 | table td.text-label {
61 | font-weight: bold;
62 | }
63 |
64 | a:hover {
65 | text-decoration: none;
66 | }
67 |
68 | .ui-autocomplete {
69 | z-index: 5000;
70 | }
71 |
72 | .popover {
73 | z-index: 6000;
74 | }
75 |
76 | .floorplan-marker {
77 | position: absolute;
78 | font-size: 2em;
79 | color: #E00000;
80 | }
81 |
82 | .floorplan-marker-static:hover {
83 | color: red;
84 | }
85 |
86 | .modal {
87 | overflow-y:auto;
88 | }
89 |
90 |
91 | .atooltip {
92 | display: inline;
93 | position: relative;
94 | }
95 |
96 | .atooltip:hover {
97 | color: #c00;
98 | text-decoration: none;
99 | }
100 |
101 | .atooltip:hover:after {
102 | background: #111;
103 | background: rgba(0,0,0,.8);
104 | border-radius: .5em;
105 | bottom: 1.35em;
106 | color: #fff;
107 | content: attr(title);
108 | display: block;
109 | left: 1em;
110 | padding: .3em 1em;
111 | position: absolute;
112 | text-shadow: 0 1px 0 #000;
113 | white-space: nowrap;
114 | z-index: 98;
115 | }
116 |
117 | .atooltip:hover:before {
118 | border: solid;
119 | border-color: #111 transparent;
120 | border-color: rgba(0,0,0,.8) transparent;
121 | border-width: .4em .4em 0 .4em;
122 | bottom: 1em;
123 | content: "";
124 | display: block;
125 | left: 2em;
126 | position: absolute;
127 | z-index: 99;
128 | }
129 |
130 | .hvac-zone-table tr {
131 | height: 51px;
132 | }
133 |
134 | .focus-sparkline text {
135 | font-size: 1.1em;
136 | opacity: 0.7;
137 | }
138 |
139 | span.glyph-animate {
140 | -webkit-animation-name: rotate;
141 | -webkit-animation-duration: 1.5s;
142 | -webkit-animation-iteration-count: infinite;
143 | -webkit-animation-timing-function: linear;
144 | }
145 |
146 | @-webkit-keyframes rotate {
147 | from {
148 | -webkit-transform: rotate( 0deg );
149 | }
150 | to {
151 | -webkit-transform: rotate( 360deg );
152 | }
153 | }
154 |
155 | .loading-div {
156 | color: #939393;
157 | font-size: 1.5em;
158 | text-align: center;
159 | margin: 0 auto;
160 | width: 50%;
161 | text-shadow: -1px 1px 2px #ccc;
162 | margin-top:50px;
163 | }
164 |
--------------------------------------------------------------------------------
/openbas/client/openbas.html:
--------------------------------------------------------------------------------
1 |
2 | OpenBAS
3 |
4 |
5 |
6 | {{> navbar}}
7 |
8 |
9 |
10 |
11 |
14 |
15 |
18 |
21 |
24 |
27 |
30 |
33 |
36 |
37 | {{> loginButtons }}
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
Buildings consume over 40% of the total energy consumption in the U.S. A significant portion of the energy consumed in buildings is wasted because of the lack of building controls or the inability to use existing Building Automation Systems (BAS) properly. Over 90% of the buildings in the U.S. are either small-sized (<5,000 square feet, or sf) or medium-sized (between 5,000 sf and 50,000 sf); these buildings typically do not use BAS to monitor and control their building systems from a central location.
46 |
In 2013, the Department of Energy solicited “Turn-Key” Open Source Software Solutions for Energy Management of Small to Medium Sized Buildings (DE-FOA-0000822). The objective was to develop a “turn key” BAS solution using Open-Source software and architecture specifically tailored to small and medium buildings to advance opportunities for energy efficiency in this sector.
47 |
The OpenBAS platform seen here is the product of the UC Berkeley-led team’s solution. The sourcecode for this project can be found on GitHub .
48 |
49 |
50 |
51 |
52 |
53 |
Points
54 |
55 |
56 |
57 | Path
58 | Trend
59 | value
60 |
61 |
62 |
63 | {{#each pointsAll}}
64 | {{#if notActuator}}
65 | {{> point_row}}
66 | {{/if}}
67 | {{/each}}
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | {{Path}}
77 |
78 |
79 | {{value_fmt}}
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | Path
88 | Value
89 | Type
90 |
91 |
92 |
93 | {{# each actuatorsAll}}
94 |
95 | {{> actuator_display}}
96 |
97 | {{/each}}
98 |
99 |
100 |
101 |
102 |
103 | {{#with point uuid}}
104 | {{device}}
105 | {{value}}
106 | {{#if isDiscrete}}
107 | {{> actuator_discrete}}
108 | {{else}} {{#if isContinuous}}
109 | {{> actuator_continuous}}
110 | {{else}} {{#if isBinary}}
111 | {{> actuator_binary}}
112 | {{else}}
113 | None
114 | {{/if}}{{/if}}{{/if}}
115 | {{/with}}
116 |
117 |
118 |
119 | {{#with point uuid}}
120 | {{name}}
121 | {{value}}
122 |
123 | {{/with}}
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 | On
138 | Off
139 |
140 |
141 |
142 |
143 | Loading ...
144 |
145 |
146 |
147 |
148 |
149 | {{room.RoomNumber}}
150 | {{#if general_controllers.count }}
151 |
152 |
General controllers
153 |
154 |
155 |
156 | Name
157 | Device
158 | State
159 |
160 |
161 |
162 |
163 | {{#each general_controllers}}
164 | {{# if timeseries.on.Metadata.Name}}
165 |
166 | {{ timeseries.on.Metadata.Name}}
167 | {{> point timeseries.on}}
168 |
169 | {{/if}}
170 | {{/each}}
171 |
172 |
173 |
174 | {{/if}}
175 |
176 |
190 |
191 |
192 |
193 |
194 |
Welcome to OpenBAS. Please sign in above.
195 |
196 |
197 |
--------------------------------------------------------------------------------
/openbas/client/openbas.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, Regents of the University of California
2 | // All rights reserved.
3 | //
4 | // Redistribution and use in source and binary forms, with or without
5 | // modification, are permitted provided that the following conditions are met:
6 | //
7 | // 1. Redistributions of source code must retain the above copyright notice, this
8 | // list of conditions and the following disclaimer.
9 | //
10 | // 2. Redistributions in binary form must reproduce the above copyright notice,
11 | // this list of conditions and the following disclaimer in the documentation
12 | // and/or other materials provided with the distribution.
13 | //
14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
25 | Accounts.ui.config({
26 | passwordSignupFields: 'USERNAME_ONLY'
27 | });
28 |
29 | OpenBAS = {};
30 |
31 | OpenBAS.PathNames = [
32 | {"path": "temp_heat", "name": "Heating setpoint"},
33 | {"path": "temp_cool", "name": "Cooling setpoint"},
34 | {"path": "hvac_state", "name": "HVAC state"},
35 | {"path": "on", "name": "Lights"},
36 | ];
37 |
38 | Template.points.pointsAll = function() {
39 | return Points.find({});
40 | };
41 |
42 | Template.points.helpers({
43 | notActuator: function(template){
44 | var re = /.*_act$/;
45 | var result = re.exec(this.Path);
46 | return result == null;
47 | }
48 | });
49 |
50 | Template.point_row.value_fmt = function() {
51 | var val = this.value
52 | if ((String(val).split('.')[1] || []).length > 2){
53 | return this.value.toFixed(2);
54 | } else {
55 | return this.value;
56 | }
57 | }
58 |
59 | Template.navbar.building_name = function() {
60 | return Meteor.settings.public.building_name;
61 | };
62 |
63 | Template.navbar.helpers({
64 | activeIf: function (template) {
65 | var currentRoute = Router.current();
66 | return currentRoute &&
67 | template === currentRoute.lookupTemplate() ? 'active' : '';
68 | },
69 | });
70 |
--------------------------------------------------------------------------------
/openbas/client/plot.html:
--------------------------------------------------------------------------------
1 |
2 | {{#with plot_data}}
3 | {{> s3plot}}
4 | {{/with}}
5 |
6 |
--------------------------------------------------------------------------------
/openbas/client/plot.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, Regents of the University of California
2 | // All rights reserved.
3 | //
4 | // Redistribution and use in source and binary forms, with or without
5 | // modification, are permitted provided that the following conditions are met:
6 | //
7 | // 1. Redistributions of source code must retain the above copyright notice, this
8 | // list of conditions and the following disclaimer.
9 | //
10 | // 2. Redistributions in binary form must reproduce the above copyright notice,
11 | // this list of conditions and the following disclaimer in the documentation
12 | // and/or other materials provided with the distribution.
13 | //
14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
25 | function parsePixelsToInt(q) {
26 | return parseFloat(q.slice(0, q.length - 2));
27 | }
28 | instances = [];
29 | if (Meteor.isClient) {
30 | var localtest = false;
31 | Template.plot.plot_data = [
32 | {
33 | tagsURL: localtest ? 'http://localhost:7856' : (Meteor.settings.public.archiverUrl + "/api/query?"),
34 | dataURLStart: localtest ? 'http://localhost:7856/data/uuid' : 'http://archiver.cal-sdb.org:9000/data/uuid/',
35 | bracketURL: "http://archiver.cal-sdb.org:9000/q/bracket",
36 | },
37 | function (inst)
38 | {
39 | instances.push(inst);
40 | s3ui.default_cb1(inst);
41 | },
42 | s3ui.default_cb2];
43 | }
44 |
--------------------------------------------------------------------------------
/openbas/client/point.css:
--------------------------------------------------------------------------------
1 | svg {
2 | font: 10px sans-serif;
3 | }
4 |
5 | .line {
6 | fill: none;
7 | stroke: #000;
8 | stroke-width: 1.5px;
9 | }
10 |
11 | .axis path,
12 | .axis line {
13 | fill: none;
14 | stroke: #000;
15 | shape-rendering: crispEdges;
16 | }
17 |
18 | .pointDetailContainer {
19 | width: 900px;
20 | margin: 0 auto;
21 | }
22 |
--------------------------------------------------------------------------------
/openbas/client/point.html:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
--------------------------------------------------------------------------------
/openbas/client/point.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, Regents of the University of California
2 | // All rights reserved.
3 | //
4 | // Redistribution and use in source and binary forms, with or without
5 | // modification, are permitted provided that the following conditions are met:
6 | //
7 | // 1. Redistributions of source code must retain the above copyright notice, this
8 | // list of conditions and the following disclaimer.
9 | //
10 | // 2. Redistributions in binary form must reproduce the above copyright notice,
11 | // this list of conditions and the following disclaimer in the documentation
12 | // and/or other materials provided with the distribution.
13 | //
14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
25 | Template.points.rendered = function(){
26 | var rows = $(".point");
27 | _.each(rows, function(row){
28 | var uuid = $(row).data('uuid');
29 | var restrict = 'uuid="' + uuid + '"';
30 | var elementId = '#trend-' + uuid;
31 | var width = 150;
32 | var height = 40;
33 | var N = width;
34 | var q = "select data in (now -8h, now) where " + restrict;
35 | Meteor.call("query", q, function(err, res){
36 | if (res[0] != undefined){
37 | var mydata = jsonify(res[0].Readings);
38 | sparkline(elementId, mydata, width, height, "last 8 hours");
39 | }
40 | });
41 | });
42 | };
43 |
--------------------------------------------------------------------------------
/openbas/client/schedule.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, Regents of the University of California
2 | // All rights reserved.
3 | //
4 | // Redistribution and use in source and binary forms, with or without
5 | // modification, are permitted provided that the following conditions are met:
6 | //
7 | // 1. Redistributions of source code must retain the above copyright notice, this
8 | // list of conditions and the following disclaimer.
9 | //
10 | // 2. Redistributions in binary form must reproduce the above copyright notice,
11 | // this list of conditions and the following disclaimer in the documentation
12 | // and/or other materials provided with the distribution.
13 | //
14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
25 | var iperiod = 0;
26 |
27 | Template.schedule.master = function(){
28 | return MasterSchedule.findOne({});
29 | }
30 |
31 | Template.schedule.days = function(){
32 | var rv = [
33 | {'day': 'Sunday', 'day_id': 'sun'},
34 | {'day': 'Monday', 'day_id': 'mon'},
35 | {'day': 'Tuesday', 'day_id': 'tue'},
36 | {'day': 'Wednesday', 'day_id': 'wed'},
37 | {'day': 'Thursday', 'day_id': 'thu'},
38 | {'day': 'Friday', 'day_id': 'fri'},
39 | {'day': 'Saturday', 'day_id': 'sat'}
40 | ];
41 | return rv;
42 | };
43 |
44 | Template.schedule.schedules = function(){
45 | return Schedules.find({});
46 | };
47 |
48 | Template.schedule.rendered = function(){
49 | var master_sched = MasterSchedule.findOne({});
50 | _.each(master_sched, function(type, day){
51 | $('#'+day+'-schedule').val(type);
52 | });
53 | };
54 |
55 | Template.period_point.path_names = function(){
56 | return OpenBAS.PathNames;
57 | };
58 |
59 | Template.schedule.events({
60 | 'click #save-schedule': function(){
61 | var master_sched = MasterSchedule.findOne({});
62 | var id = master_sched._id;
63 | _.each(master_sched, function(val, ind){
64 | master_sched[ind] = $('#'+ind+'-schedule').val();
65 | });
66 | delete master_sched._id;
67 | var r = MasterSchedule.update(id, {$set: master_sched});
68 | if (r){
69 | $("#success-alert").slideDown(800);
70 | window.setTimeout(function(){
71 | $("#success-alert").slideUp(800);
72 | }, 5000);
73 | }
74 | },
75 | 'click .delete-schedule': function(event){
76 | var clicked_id = $(event.target).data('id');
77 | Schedules.remove({ _id: clicked_id});
78 | },
79 | });
80 |
81 | Template.edit_schedule.events({
82 | 'click #add-period': function(event){
83 | iperiod++;
84 | var rendered = UI.renderWithData(Template.schedule_period, {'iperiod': iperiod});
85 | UI.insert(rendered, $('table')[0]);
86 | },
87 | 'click .add-control-point': function(event){
88 | var clicked_name = $(event.target).data('name') || $(event.target).data('period');
89 | var rendered = UI.render(Template.period_point);
90 | UI.insert(rendered, $("#schedule-period-" + clicked_name)[0]);
91 | },
92 | 'click #edit-schedule-cancel': function(){
93 | Router.go("/schedule");
94 | },
95 | 'click #edit-schedule-save': function(){
96 | var id = this._id;
97 | var periods = _.map($('.schedule-period'), function(p){
98 | rv = {};
99 | rv.name = $(p).find(".period-name").val();
100 | rv.start = $(p).find(".period-start").val();
101 | rv.points = _.map($(p).find(".period-point"), function(point){
102 | mypoint = {};
103 | mypoint.path = $(point).find('.period-point-name').val();
104 | mypoint.value = $(point).find('.period-point-value').val();
105 | return mypoint;
106 | });
107 | return rv;
108 | });
109 | this.periods = periods;
110 | this.name = $('#schedule-name').val();
111 | delete this._id;
112 |
113 | var r = Schedules.update(id, {$set: this});
114 | if (r){
115 | Router.go("/schedule/");
116 | }
117 | },
118 | 'click .remove-control-point': function(){
119 | $(event.target).closest('tr').remove();
120 | },
121 | 'click .remove-period': function(){
122 | var name = $(event.target).data('name');
123 | $('#schedule-period-'+name).remove();
124 | $(event.target).closest('tbody').remove();
125 | }
126 | });
127 |
128 | Template.edit_schedule.rendered = function(){
129 | _.each(this.data.periods, function(p){
130 | var el_period = $('#schedule-period-'+p.name);
131 | var el_period_point_name = $(el_period).find(".period-point-name");
132 | var el_period_point_value = $(el_period).find(".period-point-value");
133 | _.each(_.zip(el_period_point_name, el_period_point_value, p.points), function(x){
134 | x[0].value = x[2].path;
135 | x[1].value = x[2].value;
136 | });
137 | });
138 | };
139 |
140 | Template.add_schedule.events({
141 | 'click #add-period': function(event){
142 | iperiod++;
143 | var rendered = UI.renderWithData(Template.schedule_period, {'iperiod': iperiod});
144 | UI.insert(rendered, $('table')[0]);
145 | },
146 | 'click .add-control-point': function(event){
147 | var clicked_iperiod = $(event.target).data('period');
148 | var rendered = UI.render(Template.period_point);
149 | UI.insert(rendered, $("#schedule-period-" + clicked_iperiod)[0]);
150 | },
151 | 'click #add-schedule-cancel': function(){
152 | Router.go("/schedule/");
153 | },
154 | 'click #add-schedule-save': function(){
155 | var sched = {};
156 | var periods = _.map($('.schedule-period'), function(p){
157 | rv = {};
158 | rv.name = $(p).find(".period-name").val();
159 | rv.start = $(p).find(".period-start").val();
160 | rv.points = _.map($(p).find(".period-point"), function(point){
161 | mypoint = {};
162 | mypoint.path = $(point).find('.period-point-name').val();
163 | mypoint.value = $(point).find('.period-point-value').val();
164 | return mypoint;
165 | });
166 | return rv;
167 | });
168 | sched.periods = periods;
169 | sched.name = $('#schedule-name').val();
170 | sched.color = "#EBCACA";
171 | var r = Schedules.insert(sched);
172 | if (r){
173 | Router.go("/schedule/");
174 | }
175 | },
176 | 'click .remove-control-point': function(){
177 | $(event.target).closest('tr').remove();
178 | },
179 | 'click .remove-period': function(){
180 | var period = $(event.target).data('period');
181 | $('#schedule-period-'+period).remove();
182 | $(event.target).closest('tbody').remove();
183 | }
184 | });
185 |
186 | Template.view_schedule.helpers({
187 | getPathName: function(path){
188 | var mypath = _.find(OpenBAS.PathNames, function(p){
189 | return p.path == path;
190 | });
191 | return mypath.name;
192 | },
193 | });
194 |
--------------------------------------------------------------------------------
/openbas/client/status.html:
--------------------------------------------------------------------------------
1 |
2 | Device Status Page
3 |
4 |
5 |
6 | Unconfigured Devices
7 |
8 |
9 | Type
10 | Model
11 | Driver
12 | Last Seen
13 | Path
14 |
15 |
16 |
17 |
18 | {{#each unconfigured}}
19 | {{> device}}
20 | {{/each}}
21 |
22 |
23 |
24 | Configured Devices
25 |
26 |
27 | Type
28 | Model
29 | Driver
30 | Last Seen
31 | Path
32 |
33 |
34 |
35 |
36 | {{#each sources}}
37 | {{> device}}
38 | {{/each}}
39 |
40 |
41 |
42 |
43 |
44 |
45 | {{device}}
46 | {{model}}
47 | {{driver}}
48 | {{lastseen}}
49 | {{path}}
50 | {{> configuration}}
51 |
52 |
53 |
54 |
55 | {{#if isunconfigured}}
56 | Automap
57 | {{else}}{{#if nosystem}}
58 | Configure
59 | {{else}}
60 | Reconfigure
61 | {{/if}}{{/if}}
62 |
63 |
97 |
98 |
99 |
100 |
101 | {{> contents}}
102 |
103 |
104 |
105 |
106 |
107 |
--------------------------------------------------------------------------------
/openbas/demo.ini:
--------------------------------------------------------------------------------
1 | [report 0]
2 | ReportDeliveryLocation = mongo://localhost:3001
3 |
4 | [report 1]
5 | ReportDeliveryLocation = http://new.openbms.org/backend/add/CFixR8pyZEWpk4bIzpeWI5CdDPWR1TlxANUW
6 |
7 | #[report 1]
8 | #ReportDeliveryLocation = new.openbms..
9 |
10 | [/]
11 | uuid = db43b080-176c-11e4-b2ab-6003089ed1d0
12 | Metadata/SourceName = Demo Driver
13 | Metadata/Building = Soda Hall
14 | Metadata/Site = 2d1fd317-1772-11e4-87c5-6003089ed1d0
15 |
16 | [server]
17 | port = 8081
18 |
19 | [/buildinglighting]
20 | type = Collection
21 | Metadata/System = Lighting
22 | Metadata/Role = Building Lighting
23 | Metadata/Room = 410 Soda
24 |
25 | [/buildinglighting/group0]
26 | type = smap.drivers.lights.virtuallight.VirtualLight
27 | Metadata/Type = Light Group
28 | Metadata/Floor = 4
29 | Metadata/Group = 0
30 | Metadata/Name = 4th Floor Lighting
31 |
32 | [/buildinglighting/group1]
33 | type = smap.drivers.lights.virtuallight.VirtualLight
34 | Metadata/Type = Light Group
35 | Metadata/Floor = 5
36 | Metadata/Group = 1
37 | Metadata/Name = 5th Floor Lighting
38 |
39 | [/buildinglighting/group2]
40 | type = smap.drivers.lights.virtuallight.VirtualLight
41 | Metadata/Type = Light Group
42 | Metadata/Floor = 6
43 | Metadata/Group = 1
44 | Metadata/Name = 6th Floor Lighting
45 |
46 | [/buildinghvac]
47 | type = Collection
48 | Metadata/System = HVAC
49 | Metadata/Role = Building HVAC
50 | Metadata/Room = 410 Soda
51 |
52 | [/buildinghvac/group0]
53 | type = smap.drivers.thermostats.virtualthermostat.VirtualThermostat
54 | Metadata/Type = HVAC Group
55 | Metadata/Floor = 4
56 | Metadata/Group = 0
57 | Metadata/Name = 4th Floor HVAC
58 |
59 | [/buildinghvac/group1]
60 | type = smap.drivers.thermostats.virtualthermostat.VirtualThermostat
61 | Metadata/Type = HVAC Group
62 | Metadata/Floor = 5
63 | Metadata/Group = 0
64 | Metadata/Name = 5th Floor HVAC
65 |
--------------------------------------------------------------------------------
/openbas/followcontroller.py:
--------------------------------------------------------------------------------
1 | from smap.services.zonecontroller import ZoneController
2 |
3 | class FollowMaster(ZoneController):
4 | def setup(self, opts):
5 | ZoneController.setup(self, opts)
6 | self.add_timeseries('/temp_heat','F',data_type='double')
7 | self.add_timeseries('/temp_cool','F',data_type='double')
8 |
9 | def step(self):
10 | print 'ZONE CONTROL',self.points
11 | self.add('/temp_heat', float(self.points['temp_heat']))
12 | self.add('/temp_cool', float(self.points['temp_cool']))
13 |
14 | class FollowMasterTrim(ZoneController):
15 | def setup(self, opts):
16 | ZoneController.setup(self, opts)
17 | self.add_timeseries('/temp_heat','F',data_type='double')
18 | self.add_timeseries('/temp_cool','F',data_type='double')
19 |
20 | def step(self):
21 | print 'ZONE CONTROL',self.points
22 | self.add('/temp_heat', float(self.points['temp_heat'])+5)
23 | self.add('/temp_cool', float(self.points['temp_cool'])+5)
24 |
--------------------------------------------------------------------------------
/openbas/lib/collections.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, Regents of the University of California
2 | // All rights reserved.
3 | //
4 | // Redistribution and use in source and binary forms, with or without
5 | // modification, are permitted provided that the following conditions are met:
6 | //
7 | // 1. Redistributions of source code must retain the above copyright notice, this
8 | // list of conditions and the following disclaimer.
9 | //
10 | // 2. Redistributions in binary form must reproduce the above copyright notice,
11 | // this list of conditions and the following disclaimer in the documentation
12 | // and/or other materials provided with the distribution.
13 | //
14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
25 | Points = new Meteor.Collection("points");
26 | Rooms = new Meteor.Collection("rooms");
27 | HVAC = new Meteor.Collection("hvac");
28 | Lighting = new Meteor.Collection("lighting");
29 | Monitoring = new Meteor.Collection("monitoring");
30 | GeneralControl = new Meteor.Collection("general_control");
31 | Schedules = new Meteor.Collection("schedules");
32 | MasterSchedule = new Meteor.Collection("master_schedule");
33 | Unconfigured = new Meteor.Collection("unconfigured");
34 | Floorplans = new Meteor.Collection("floorplans");
35 | Site = new Meteor.Collection("site");
36 |
37 | if (Meteor.isServer) {
38 |
39 | if (Schedules.find({}).fetch().length == 0){
40 | var schedules = EJSON.parse(Assets.getText("schedules.json"));
41 | _.each(schedules, function(s){
42 | Schedules.insert(s);
43 | });
44 | }
45 |
46 | if (MasterSchedule.find({}).fetch().length == 0){
47 | MasterSchedule.insert({
48 | 'mon': 'weekday',
49 | 'tue': 'weekday',
50 | 'wed': 'weekday',
51 | 'thu': 'weekday',
52 | 'fri': 'weekday',
53 | 'sat': 'weekend',
54 | 'sun': 'weekend',
55 | });
56 | }
57 |
58 | if (Rooms.find({}).fetch().length == 0){
59 | var rooms = EJSON.parse(Assets.getText(Meteor.settings.roomsfile));
60 | _.each(rooms, function(r){
61 | Rooms.insert(r);
62 | });
63 | }
64 |
65 | Meteor.publish("master_schedule", function () {
66 | return MasterSchedule.find({});
67 | });
68 | Meteor.publish("schedules", function () {
69 | return Schedules.find({});
70 | });
71 | Meteor.publish("hvac", function () {
72 | return HVAC.find({});
73 | });
74 | Meteor.publish("lighting", function () {
75 | return Lighting.find({});
76 | });
77 | Meteor.publish("points", function () {
78 | return Points.find({});
79 | });
80 | Meteor.publish("monitoring", function () {
81 | return Monitoring.find({});
82 | });
83 | Meteor.publish("unconfigured", function () {
84 | return Unconfigured.find({});
85 | });
86 | Meteor.publish("site", function () {
87 | return Site.find({});
88 | });
89 |
90 | }
91 |
92 | Meteor.startup(function(){
93 | if (Meteor.isServer){
94 |
95 | // add default admin user (default password in settings)
96 | if (!Meteor.users.find().count()){
97 | var user = {
98 | username: 'admin',
99 | password: Meteor.settings.default_password,
100 | };
101 | Accounts.createUser(user);
102 | }
103 |
104 | Accounts.config({
105 | forbidClientAccountCreation: true
106 | });
107 |
108 | Accounts.onLogin(function(){
109 | Router.go('/');
110 | });
111 |
112 | Meteor.settings.public.project_root = process.env.PWD;
113 |
114 | };
115 |
116 | FloorplansFS = new FS.Collection("floorplans_fs", {
117 | stores: [new FS.Store.FileSystem("images", {path: Meteor.settings.public.project_root + "/public/floorplans"})]
118 | });
119 |
120 | Site.upsert({'_id':'Site'},{'_id': 'Site', 'Site': Meteor.settings.public.site});
121 | });
122 |
--------------------------------------------------------------------------------
/openbas/lib/router.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2015, Regents of the University of California
2 | // All rights reserved.
3 | //
4 | // Redistribution and use in source and binary forms, with or without
5 | // modification, are permitted provided that the following conditions are met:
6 | //
7 | // 1. Redistributions of source code must retain the above copyright notice, this
8 | // list of conditions and the following disclaimer.
9 | //
10 | // 2. Redistributions in binary form must reproduce the above copyright notice,
11 | // this list of conditions and the following disclaimer in the documentation
12 | // and/or other materials provided with the distribution.
13 | //
14 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
25 | Router.onBeforeAction(function(){
26 | if (!(Meteor.loggingIn() || Meteor.user())) {
27 | this.render('login');
28 | pause();
29 | }
30 | });
31 |
32 | Router.map(function() {
33 | this.route('dashboard', {
34 | path: '/',
35 | waitOn: function() {
36 | return [
37 | Meteor.subscribe('schedules'),
38 | Meteor.subscribe('master_schedule'),
39 | Meteor.subscribe('points'),
40 | Meteor.subscribe('hvac'),
41 | Meteor.subscribe('lighting'),
42 | Meteor.subscribe('monitoring')
43 | ];
44 | },
45 | action: function () {
46 | if (this.ready()){
47 | this.render();
48 | } else {
49 | this.render('loading');
50 | }
51 | }
52 | });
53 |
54 | this.route('schedule', {
55 | waitOn: function(){
56 | return [
57 | Meteor.subscribe('schedules'),
58 | Meteor.subscribe('master_schedule')
59 | ];
60 | },
61 | });
62 |
63 | this.route('status', {
64 | onBeforeAction: function() {
65 | Meteor.call('querysystem');
66 | this.subscribe('hvac').wait();
67 | this.subscribe('monitoring').wait();
68 | this.subscribe('lighting').wait();
69 | this.subscribe('points').wait();
70 | this.subscribe('unconfigured').wait();
71 | this.subscribe('site').wait();
72 | },
73 | });
74 |
75 | this.route('points');
76 | this.route('about');
77 | this.route('plot');
78 |
79 | this.route('zone_detail', {
80 | path: '/dashboard/:zonetype/:zone',
81 | waitOn: function() {
82 | return [
83 | Meteor.subscribe('points'),
84 | Meteor.subscribe('hvac'),
85 | Meteor.subscribe('lighting'),
86 | Meteor.subscribe('monitoring')
87 | ];
88 | },
89 | data: function() {
90 | if (this.params.zonetype == 'hvac') {
91 | return {'type': 'hvac', 'points': HVAC.find({'hvaczone': this.params.zone}).fetch()};
92 | } else if (this.params.zonetype == 'lighting') {
93 | return {'type': 'lighting', 'points': Lighting.find({'lightingzone': this.params.zone}).fetch()};
94 | } else {
95 | return 0
96 | }
97 | }
98 | });
99 |
100 | this.route('room_detail', {
101 | path: '/room/:room',
102 | data: function(){
103 | return {
104 | 'room': Rooms.findOne({'RoomNumber': this.params.room}),
105 | 'general_controllers': GeneralControl.find({'room': this.params.room}) };
106 | }
107 | });
108 |
109 | this.route('pointDetail', {
110 | path: '/points/:uuid',
111 | data: function() { return Points.findOne({uuid: this.params.uuid}); },
112 | });
113 |
114 | this.route('view_schedule', {
115 | path: '/schedule/view/:id',
116 | data: function() { return Schedules.findOne({_id: this.params.id}); },
117 | });
118 |
119 | this.route('edit_schedule', {
120 | path: '/schedule/edit/:id',
121 | data: function() { return Schedules.findOne({_id: this.params.id}); },
122 | });
123 |
124 | this.route('add_schedule', {
125 | path: '/schedule/add',
126 | data: {'iperiod': 0},
127 | });
128 |
129 | this.route('building', {
130 | waitOn: function(){
131 | return [Meteor.subscribe("rooms"), Meteor.subscribe("floorplans")];
132 | },
133 | });
134 |
135 | this.route('add_room', {
136 | path: '/building/add_room',
137 | waitOn: function(){
138 | return [Meteor.subscribe("rooms"), Meteor.subscribe("floorplans")];
139 | }
140 | });
141 |
142 | this.route('edit_room', {
143 | path: '/building/edit_room/:id',
144 | data: function() { return Rooms.findOne({_id: this.params.id}); },
145 | waitOn: function(){
146 | return [Meteor.subscribe("rooms"), Meteor.subscribe("floorplans")];
147 | }
148 | });
149 |
150 | this.route('view_room', {
151 | path: '/building/view_room/:id',
152 | data: function(){
153 | return Rooms.findOne({_id: this.params.id})
154 | },
155 | });
156 |
157 | });
158 |
159 | Router._filters = {
160 | resetScroll: function () {
161 | var scrollTo = window.currentScroll || 0;
162 | $('body').scrollTop(scrollTo);
163 | $('body').css("min-height", 0);
164 | }
165 | };
166 |
167 | var filters = Router._filters;
168 |
169 | if(Meteor.isClient) {
170 | Router.onAfterAction(filters.resetScroll); // for all pages
171 | }
172 |
--------------------------------------------------------------------------------
/openbas/packages/.gitignore:
--------------------------------------------------------------------------------
1 | /iron-router
2 | /blaze-layout
3 | /accounts-ui-bootstrap-3
4 | /bootstrap-3
5 | /houston
6 | /paginated-subscription
7 | /spin
8 | /bootstrap-slider
9 | /iron-layout
10 | /iron-core
11 | /iron-dynamic-template
12 | /moment
13 | /jquery-ui
14 | /jquery-ui-bootstrap
15 | /jqueryui
16 | /build-fetcher
17 | /collectionFS
18 | /cfs-base-package
19 | /cfs-file
20 | /cfs-collection
21 | /cfs-collection-filters
22 | /cfs-access-point
23 | /cfs-worker
24 | /cfs-upload-http
25 | /cfs-filesaver
26 | /cfs-storage-adapter
27 | /http-methods
28 | /numeral
29 | /data-man
30 | /emitter
31 | /cfs-upload-ddp
32 | /cfs-tempstore
33 | /collectionfs
34 | /http-publish
35 | /cfs-gridfs
36 | /power-queue
37 | /reactive-list
38 | /cfs-graphicsmagick
39 | /cfs-ejson-file
40 | /cfs-filesystem
41 | /reactive-property
42 | /micro-queue
43 |
--------------------------------------------------------------------------------
/openbas/packages/anytime:
--------------------------------------------------------------------------------
1 | ../../upmu-plotter/upmuplot/packages/anytime
--------------------------------------------------------------------------------
/openbas/packages/col-resizable:
--------------------------------------------------------------------------------
1 | ../../upmu-plotter/upmuplot/packages/col-resizable
--------------------------------------------------------------------------------
/openbas/packages/jquery-migrate:
--------------------------------------------------------------------------------
1 | ../../upmu-plotter/upmuplot/packages/jquery-migrate/
--------------------------------------------------------------------------------
/openbas/packages/jquery-simplecolorpicker:
--------------------------------------------------------------------------------
1 | ../../upmu-plotter/upmuplot/packages/jquery-simplecolorpicker
--------------------------------------------------------------------------------
/openbas/packages/newd3:
--------------------------------------------------------------------------------
1 | ../../upmu-plotter/upmuplot/packages/newd3
--------------------------------------------------------------------------------
/openbas/packages/s3ui:
--------------------------------------------------------------------------------
1 | ../../upmu-plotter/upmuplot/packages/s3ui
--------------------------------------------------------------------------------
/openbas/packages/timezone-js:
--------------------------------------------------------------------------------
1 | ../../upmu-plotter/upmuplot/packages/timezone-js/
--------------------------------------------------------------------------------
/openbas/packages/vakata-jstree:
--------------------------------------------------------------------------------
1 | ../../upmu-plotter/upmuplot/packages/vakata-jstree
--------------------------------------------------------------------------------
/openbas/private/rooms.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "RoomNumber":205,
4 | "Description":"Office",
5 | "Exposure":"S",
6 | "HVACZone":"South",
7 | "LightingZone":""
8 | },
9 | {
10 | "RoomNumber":206,
11 | "Description":"Office",
12 | "Exposure":"S",
13 | "HVACZone":"South",
14 | "LightingZone":""
15 | },
16 | {
17 | "RoomNumber":207,
18 | "Description":"Conference",
19 | "Exposure":"S",
20 | "HVACZone":"South",
21 | "LightingZone":""
22 | },
23 | {
24 | "RoomNumber":208,
25 | "Description":"Kitchen",
26 | "Exposure":"S",
27 | "HVACZone":"South",
28 | "LightingZone":""
29 | },
30 | {
31 | "RoomNumber":209,
32 | "Description":"Office",
33 | "Exposure":"S",
34 | "HVACZone":"South",
35 | "LightingZone":""
36 | },
37 | {
38 | "RoomNumber":210,
39 | "Description":"Office",
40 | "Exposure":"S",
41 | "HVACZone":"South",
42 | "LightingZone":""
43 | },
44 | {
45 | "RoomNumber":211,
46 | "Description":"Office",
47 | "Exposure":"S",
48 | "HVACZone":"South",
49 | "LightingZone":""
50 | },
51 | {
52 | "RoomNumber":212,
53 | "Description":"Office",
54 | "Exposure":"S",
55 | "HVACZone":"South",
56 | "LightingZone":""
57 | },
58 | {
59 | "RoomNumber":213,
60 | "Description":"Office",
61 | "Exposure":"SE",
62 | "HVACZone":"East",
63 | "LightingZone":""
64 | },
65 | {
66 | "RoomNumber":214,
67 | "Description":"Office",
68 | "Exposure":"E",
69 | "HVACZone":"East",
70 | "LightingZone":""
71 | },
72 | {
73 | "RoomNumber":215,
74 | "Description":"Office",
75 | "Exposure":"E",
76 | "HVACZone":"East",
77 | "LightingZone":""
78 | },
79 | {
80 | "RoomNumber":216,
81 | "Description":"Office",
82 | "Exposure":"E",
83 | "HVACZone":"East",
84 | "LightingZone":""
85 | },
86 | {
87 | "RoomNumber":217,
88 | "Description":"Office",
89 | "Exposure":"E",
90 | "HVACZone":"East",
91 | "LightingZone":""
92 | },
93 | {
94 | "RoomNumber":219,
95 | "Description":"Open Plan",
96 | "Exposure":"Core",
97 | "HVACZone":"General Area",
98 | "LightingZone":""
99 | },
100 | {
101 | "RoomNumber":221,
102 | "Description":"Open Plan",
103 | "Exposure":"Core",
104 | "HVACZone":"General Area",
105 | "LightingZone":""
106 | },
107 | {
108 | "RoomNumber":222,
109 | "Description":"Office",
110 | "Exposure":"Core",
111 | "HVACZone":"General Area",
112 | "LightingZone":""
113 | },
114 | {
115 | "RoomNumber":223,
116 | "Description":"Office",
117 | "Exposure":"Core",
118 | "HVACZone":"General Area",
119 | "LightingZone":""
120 | },
121 | {
122 | "RoomNumber":225,
123 | "Description":"Office",
124 | "Exposure":"Core",
125 | "HVACZone":"General Area",
126 | "LightingZone":""
127 | },
128 | {
129 | "RoomNumber":203,
130 | "Description":"Hallway",
131 | "Exposure":"Core",
132 | "HVACZone":"General Area",
133 | "LightingZone":""
134 | },
135 | {
136 | "RoomNumber":204,
137 | "Description":"Hallway",
138 | "Exposure":"Core",
139 | "HVACZone":"General Area",
140 | "LightingZone":""
141 | },
142 | {
143 | "RoomNumber":205,
144 | "Description":"Hallway",
145 | "Exposure":"Core",
146 | "HVACZone":"South",
147 | "LightingZone":""
148 | },
149 | {
150 | "RoomNumber":218,
151 | "Description":"Open Plan",
152 | "Exposure":"N",
153 | "HVACZone":"North",
154 | "LightingZone":""
155 | },
156 | {
157 | "RoomNumber":220,
158 | "Description":"Open Plan",
159 | "Exposure":"N",
160 | "HVACZone":"North",
161 | "LightingZone":""
162 | },
163 | {
164 | "RoomNumber":224,
165 | "Description":"Office",
166 | "Exposure":"N",
167 | "HVACZone":"North",
168 | "LightingZone":""
169 | },
170 | {
171 | "RoomNumber":226,
172 | "Description":"Open Plan",
173 | "Exposure":"N",
174 | "HVACZone":"North",
175 | "LightingZone":""
176 | }
177 | ]
178 |
--------------------------------------------------------------------------------
/openbas/private/schedules.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "weekday",
4 | "periods": [
5 | {
6 | "name": "morning",
7 | "start": "7:30",
8 | "points": [
9 | {
10 | "path": "temp_heat",
11 | "value": 72
12 | },
13 | {
14 | "path": "temp_cool",
15 | "value": 83
16 | },
17 | {
18 | "path": "on",
19 | "value": 1
20 | }
21 | ]
22 | },
23 | {
24 | "name": "afternoon",
25 | "start": "13:30",
26 | "points": [
27 | {
28 | "path": "temp_heat",
29 | "value": 70
30 | },
31 | {
32 | "path": "temp_cool",
33 | "value": 80
34 | },
35 | {
36 | "path": "on",
37 | "value": 1
38 | }
39 | ]
40 | },
41 | {
42 | "name": "evening",
43 | "start": "18:30",
44 | "points": [
45 | {
46 | "path": "temp_heat",
47 | "value": 50
48 | },
49 | {
50 | "path": "temp_cool",
51 | "value": 90
52 | },
53 | {
54 | "path": "on",
55 | "value": 0
56 | }
57 | ]
58 | }
59 | ],
60 | "color": "#EDF1FA"
61 | },
62 | {
63 | "name": "weekend",
64 | "periods": [
65 | {
66 | "name": "morning",
67 | "start": "09:30",
68 | "points": [
69 | {
70 | "path": "temp_heat",
71 | "value": 65
72 | },
73 | {
74 | "path": "temp_cool",
75 | "value": 85
76 | }
77 | ]
78 | },
79 | {
80 | "name": "afternoon",
81 | "start": "17:30",
82 | "points": [
83 | {
84 | "path": "temp_heat",
85 | "value": 70
86 | },
87 | {
88 | "path": "temp_cool",
89 | "value": 80
90 | }
91 | ]
92 | },
93 | {
94 | "name": "evening",
95 | "start": "21:00",
96 | "points": [
97 | {
98 | "path": "temp_heat",
99 | "value": 50
100 | },
101 | {
102 | "path": "temp_cool",
103 | "value": 90
104 | }
105 | ]
106 | }
107 | ],
108 | "color": "#FAF6ED"
109 | }
110 | ]
111 |
--------------------------------------------------------------------------------
/openbas/private/testrooms.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "RoomNumber":"410 Soda",
4 | "Description":"Office",
5 | "Exposure":"Core",
6 | "HVACZone":"410 W Soda",
7 | "LightingZone":"410 Soda"
8 | },
9 | {
10 | "RoomNumber":"410 Soda",
11 | "Description":"Office",
12 | "Exposure":"Core",
13 | "HVACZone":"410 E Soda",
14 | "LightingZone":"410 Soda"
15 | },
16 | {
17 | "RoomNumber":"411 Soda",
18 | "Description":"Office",
19 | "Exposure":"Core",
20 | "HVACZone":"411 Soda",
21 | "LightingZone":"411 Soda"
22 | },
23 | {
24 | "RoomNumber":"420 Soda",
25 | "Description":"Office",
26 | "Exposure":"Core",
27 | "HVACZone":"420 Soda",
28 | "LightingZone":"420 Soda"
29 | }
30 | ]
31 |
--------------------------------------------------------------------------------
/openbas/public/floorplans/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/openbas/public/floorplans/.DS_Store
--------------------------------------------------------------------------------
/openbas/public/img/CIEE-floorplan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/openbas/public/img/CIEE-floorplan.png
--------------------------------------------------------------------------------
/openbas/public/img/ajax-loader.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/openbas/public/img/ajax-loader.gif
--------------------------------------------------------------------------------
/openbas/public/img/debut-light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SoftwareDefinedBuildings/openbas/e18dcb2e2111afa6a020cac69f7028d4a1b7a520/openbas/public/img/debut-light.png
--------------------------------------------------------------------------------
/openbas/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "archiverUrl": "http://localhost:8079",
3 | "roomsfile": "testrooms.json",
4 | "default_password": "password",
5 | "apikey": "lVzBMDpnkXApJmpjUDSvm4ceGfpbrLLSd9cq",
6 | "public": {
7 | "archiverUrl": "http://localhost:8079",
8 | "site": "0273d18f-1c03-11e4-a490-6003089ed1d0",
9 | "building_name": "CIEE"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/smap-control-example/control.ini:
--------------------------------------------------------------------------------
1 | [report 0]
2 | ReportDeliveryLocation = http://localhost:8079/add/lVzBMDpnkXApJmpjUDSvm4ceGfpbrLLSd9cq
3 |
4 | [/]
5 | uuid = c4ca3cf0-34c8-11e1-9bca-030e8139310d
6 | Metadata/SourceName = Demo Driver Repub
7 |
8 | [server]
9 | port = 1235
10 |
11 | [/control]
12 | type=control.Controller
13 | archiver_url=http://localhost:8079
14 | roomuuid=e2153d3d-78c6-5712-a484-fcfb4937785a
15 | setpoint=72
16 | deadband=4
17 | rate=.1
18 |
--------------------------------------------------------------------------------
/smap-control-example/control.py:
--------------------------------------------------------------------------------
1 | from smap import actuate, driver
2 | from smap.archiver.client import RepublishClient
3 | from smap.util import periodicSequentialCall
4 | import random
5 | import math
6 |
7 | from controllogic import cool_controller
8 |
9 | class Controller(driver.SmapDriver):
10 | def setup(self, opts):
11 | # source of streaming data
12 | self.archiver_url = opts.pop('archiver_url','http://localhost:8079')
13 | # streaming data uuid
14 | self.roomuuid = opts.get('roomuuid')
15 | # subscribe to datastream
16 | restriction = "uuid = '{0}'".format(self.roomuuid)
17 | self.roomclient = RepublishClient(self.archiver_url, self.controlcb, restrict=restriction)
18 | # setpoint
19 | self.sp = float(opts.get('setpoint',72))
20 | # deadband
21 | self.db = float(opts.get('deadband',1))
22 | # period between calling controller
23 | self.rate = float(opts.get('rate',1))
24 | self.cool = 0 # off
25 |
26 | self.add_timeseries('/cool', 'On/Off',data_type='long')
27 |
28 | # initialize current temperature to setpoint
29 | self.cur_temp = self.sp
30 |
31 | self.cool_controller = cool_controller
32 |
33 | def start(self):
34 | self.roomclient.connect()
35 | periodicSequentialCall(self.read).start(self.rate)
36 |
37 | def read(self):
38 | # calculate and send control decision
39 | #print self.cool_controller(self.cur_temp, self.sp, self.db)
40 | #self.add('/cool', self.cool_controller(self.cur_temp, self.sp, self.db))
41 | if self.cool: #
42 | self.cool = self.cur_temp > self.sp - self.db
43 | else:
44 | self.cool = self.cur_temp > self.sp + self.db
45 | self.add('/cool',int(self.cool))
46 |
47 | def controlcb(self, _, data):
48 | # parse streaming data
49 | mostrecent = data[-1][-1]
50 | self.cur_temp = mostrecent[1]
51 |
--------------------------------------------------------------------------------
/smap-control-example/controllogic.py:
--------------------------------------------------------------------------------
1 | # Basic controller. Decides whether to cool or not. No damping
2 | def cool_controller(current_temperature, setpoint, deadband):
3 | print current_temperature, setpoint, deadband
4 | if current_temperature > setpoint + deadband:
5 | print 'COOL'
6 | return 1
7 | else:
8 | print 'NO COOL'
9 | return 0
10 |
11 | # simple controller
12 | # def cool_controller(current_temperature, setpoint, deadband):
13 | # return int(current_temperature > setpoint + deadband)
14 |
--------------------------------------------------------------------------------
/smap-control-example/oat.ini:
--------------------------------------------------------------------------------
1 | [report 0]
2 | ReportDeliveryLocation = http://localhost:8079/add/lVzBMDpnkXApJmpjUDSvm4ceGfpbrLLSd9cq
3 |
4 | [/]
5 | uuid = a4ca3cf0-34c8-11e1-9bca-030e8139310d
6 | Metadata/SourceName = Demo Driver Repub
7 |
8 | [server]
9 | port = 1234
10 |
11 | [/oat]
12 | type=oat.OAT
13 | start=80
14 | rate=.1
15 | jump=2
16 | Metadata/Extra/Type = Outside Air Temperature
17 |
--------------------------------------------------------------------------------
/smap-control-example/oat.py:
--------------------------------------------------------------------------------
1 | from smap import driver
2 | from smap.archiver.client import RepublishClient
3 | from smap.util import periodicSequentialCall
4 | import math
5 |
6 | class OAT(driver.SmapDriver):
7 | def setup(self, opts):
8 | # initial temperature
9 | self.start_temp = float(opts.pop('start_temp','80'))
10 | # period between polling sensor
11 | self.rate = float(opts.pop('rate','1'))
12 | self.jump = float(opts.pop('jump','1'))
13 | self.curtemp = self.start_temp
14 |
15 | self.t = 1
16 |
17 | self.add_timeseries('/temperature','F',data_type='double')
18 |
19 | def start(self):
20 | periodicSequentialCall(self.read).start(self.rate)
21 |
22 | def read(self):
23 | # temperature calculated as a parametric sine wave
24 | self.t += 1
25 | self.curtemp = 78 + 10*math.sin(.01 * self.t)
26 | self.add('/temperature', self.curtemp)
27 |
28 |
--------------------------------------------------------------------------------
/smap-control-example/room.ini:
--------------------------------------------------------------------------------
1 | [report 0]
2 | ReportDeliveryLocation = http://localhost:8079/add/lVzBMDpnkXApJmpjUDSvm4ceGfpbrLLSd9cq
3 |
4 | [/]
5 | uuid = b4ca3cf0-34c8-11e1-9bca-030e8139310d
6 | Metadata/SourceName = Demo Driver Repub
7 |
8 | [server]
9 | port = 1236
10 |
11 | [/room]
12 | type=room.Room
13 | oatuuid=9a846841-0834-5045-a0ae-4a9c57cebb0c
14 | cooluuid=c02b8f41-8da9-5c04-9020-58a135ba5879
15 | rate=.1
16 | therm_resistance=.1
17 | epsilon=.15
18 | starttemp=70
19 | archiver_url=http://localhost:8079
20 |
21 |
--------------------------------------------------------------------------------
/smap-control-example/room.py:
--------------------------------------------------------------------------------
1 | from smap import actuate, driver
2 | from smap.archiver.client import RepublishClient
3 | from smap.util import periodicSequentialCall
4 | import random
5 | import math
6 |
7 | class Room(driver.SmapDriver):
8 | def setup(self, opts):
9 | # source of streaming data
10 | self.archiver_url = opts.pop('archiver_url','http://localhost:8079')
11 | # streaming data uuid
12 | self.oatuuid = opts.get('oatuuid')
13 | # subscribe to datastream
14 | restriction = "uuid = '{0}'".format(self.oatuuid)
15 | self.oatclient = RepublishClient(self.archiver_url, self.oatcb, restrict=restriction)
16 |
17 | self.cooluuid = opts.get('cooluuid')
18 | restriction = "uuid = '{0}'".format(self.cooluuid)
19 | self.coolclient = RepublishClient(self.archiver_url, self.coolcb, restrict=restriction)
20 |
21 | # rate between recalculating room model
22 | self.rate = float(opts.get('rate',1))
23 | # thermal resistance of room
24 | self.therm_resistance = float(opts.get('therm_resistance', .1))
25 | # initial temperature of room
26 | self.temp = float(opts.get('starttemp', 75))
27 | # epsilon for room (for cooling)
28 | self.e = float(opts.get('epsilon',.1))
29 |
30 | # state vars
31 | self.oat_val = 0
32 | self.cool = 0
33 |
34 | self.add_timeseries('/currenttemp','F',data_type='double')
35 |
36 | def start(self):
37 | self.oatclient.connect()
38 | self.coolclient.connect()
39 | periodicSequentialCall(self.read).start(self.rate)
40 |
41 | def read(self):
42 | dt = (self.oat_val - self.temp)
43 | print self.temp, self.rate, self.therm_resistance, self.cool,self.e
44 | self.temp = self.temp + self.rate * self.therm_resistance * dt - self.cool*self.e*dt
45 | self.add('/currenttemp', self.temp)
46 |
47 | def oatcb(self, _, data):
48 | # list of arrays of [time, val]
49 | mostrecent = data[-1][-1]
50 | self.oat_val = mostrecent[1]
51 |
52 | def coolcb(self, _, data):
53 | # list of arrays of [time, val]
54 | mostrecent = data[-1][-1]
55 | self.cool = mostrecent[1]
56 | print 'am i cooling', self.cool
57 |
--------------------------------------------------------------------------------
/smap-control-example/startdrivers.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import sys
3 |
4 | inis = sys.argv[1:]
5 |
6 | processes = []
7 | for ini in inis:
8 | command = "twistd --pidfile {0}.pid -n smap {0}.ini".format(ini)
9 | p = subprocess.Popen(command, shell=True)
10 | processes.append(p)
11 |
12 | try:
13 | raw_input()
14 | except:
15 | for p in processes:
16 | print "killing",p
17 | p.terminate()
18 |
--------------------------------------------------------------------------------
/smap-control-tutorial/control-demo.ini:
--------------------------------------------------------------------------------
1 | # Set to api key for the particular site
2 | [report 0]
3 | ReportDeliveryLocation = http://localhost:8079/add/8KwKzvbVjZZ7AlQzEwBjR7LUPRiwWnh2jg3D
4 |
5 | # unique resource for this demonstration
6 | [/]
7 | # unique identifier for the collection
8 | uuid = c14af954-1686-11e4-9685-000c29b778da
9 |
10 | Metadata/SourceName = Pseudo Room Control Demonstration
11 |
12 | Metadata/Site/Name = Imaginary Building
13 | # unique identifier for the site, which may have many collections
14 | Metadata/Site/id = 2c8ed966-146a-11e4-9e46-000c29b778da
15 |
16 | # REST resource for control demo
17 | [server]
18 | port = 1236
19 |
20 | [/control]
21 | type=coolControllerService.Controller
22 | setpoint=72
23 | deadband=2
24 | rate=1
25 | archiver_url=http://localhost:8079
26 | zonepath = /room/airtemp
27 | zonesiteid = 2c8ed966-146a-11e4-9e46-000c29b778da
28 |
29 | [/control/cool]
30 | Metadata/Description = Cooling request
31 | Metadata/Type = Command
32 |
--------------------------------------------------------------------------------
/smap-control-tutorial/coolControllerService.py:
--------------------------------------------------------------------------------
1 | from smap import actuate, driver
2 | from smap.archiver.client import RepublishClient
3 | from smap.util import periodicSequentialCall
4 | import math
5 |
6 | class Controller(driver.SmapDriver):
7 | def setup(self, opts):
8 | self.archiver_url = opts.pop('archiver_url','http://localhost:8079')
9 | self.sp = float(opts.get('setpoint',78))
10 | self.db = float(opts.get('deadband',1))
11 | self.rate = float(opts.get('rate',1))
12 |
13 | # Initialize controller state
14 | self.cur_temp = self.sp
15 | self.state = 0 # initial cool is off
16 |
17 | # subscribe to zone air temperature
18 | self.zonepath = opts.pop('zonepath','/room/airtemp')
19 | self.siteid = opts.pop('zonesiteid','')
20 | restriction = "Path = '{0}'".format(self.zonepath)
21 | if self.siteid :
22 | restriction = restriction + " and Metadata/Site/id = '{0}'".format(self.siteid)
23 | self.roomclient = RepublishClient(self.archiver_url, self.controlcb, restrict=restriction)
24 |
25 | # create timeseries for contoller actions
26 | self.add_timeseries('/cool', 'On/Off', data_type='long')
27 |
28 | def start(self):
29 | self.roomclient.connect()
30 | periodicSequentialCall(self.read).start(self.rate)
31 |
32 | # Periodically schedule controller event
33 | # - update control state
34 | # - build timeseries of actions
35 | def read(self):
36 | if (self.cur_temp > self.sp + self.db) : self.state = 1 # start cool
37 | if (self.cur_temp < self.sp - self.db) : self.state = 0 # stop cool
38 | self.add('/cool', self.state) # publish the state change
39 |
40 | # Handle temperature reporting event
41 | # record most recent zone temperature for next contol event
42 | def controlcb(self, _, data):
43 | mostrecent = data[-1][-1]
44 | self.cur_temp = mostrecent[1]
45 |
--------------------------------------------------------------------------------
/smap-control-tutorial/oat.ini:
--------------------------------------------------------------------------------
1 | # Configuration for a collection monitoring the outside environment
2 | # The apikey must be pre-established on archiver host by adding a suscription
3 | [report 0]
4 | ReportDeliveryLocation = http://localhost:8079/add/8KwKzvbVjZZ7AlQzEwBjR7LUPRiwWnh2jg3D
5 |
6 | [/]
7 | uuid = a4ca3cf0-34c8-11e1-9bca-030e8139310d
8 | Metadata/SourceName = Demonstration outside air temperature
9 |
10 | Metadata/Site/Name = Imaginary Building
11 | # unique identifier for the site, which may have many collections
12 | Metadata/Site/id = 2c8ed966-146a-11e4-9e46-000c29b778da
13 |
14 | # REST resource for this source is http://driverhost:port
15 | [server]
16 | port = 1234
17 |
18 | # outside environment collection
19 | # The name of the resource matches the class provided by the driver
20 |
21 | # Model of outside world provided by a time varying sequence of values by pseudo-sensor
22 | # parameters provided to the driver describe its behavior
23 | # this section would be replaced by a real driver
24 | [/OAT]
25 | type=pseudoOATdriver.OAT
26 | start=78
27 | rate=1
28 | jump=2
29 | Metadata/Description = Pseudo sensor generating simulated OAT
30 |
31 | [/OAT/temperature]
32 | Metadata/Type = Sensor
33 | Metadata/Kind = Temperature
34 | Metadata/Form = Air
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/smap-control-tutorial/oat.py:
--------------------------------------------------------------------------------
1 | from smap import driver
2 | from smap.archiver.client import RepublishClient
3 | from smap.util import periodicSequentialCall
4 | import math
5 |
6 | class OAT(driver.SmapDriver):
7 | def setup(self, opts):
8 | self.starttemp = float(opts.pop('start_temp','80'))
9 | self.rate = float(opts.pop('rate','1'))
10 | self.jump = float(opts.pop('jump','1'))
11 | self.curtemp = self.starttemp
12 |
13 | self.t = 1
14 |
15 | self.add_timeseries('/temperature','F',data_type='double')
16 |
17 | def start(self):
18 | periodicSequentialCall(self.read).start(self.rate)
19 |
20 | def read(self):
21 | self.t += 1
22 | self.curtemp = 78 + 10*math.sin(.01 * self.t)
23 | self.add('/temperature', self.curtemp)
24 |
25 |
--------------------------------------------------------------------------------
/smap-control-tutorial/pseudoOATdriver.py:
--------------------------------------------------------------------------------
1 | # Pseudo outside air temperature driver
2 | # Produces a stream of values varying around a given point
3 | #
4 | # Configuration parameters example
5 | #
6 | # [/oat]
7 | # type=pseudoOATdriver.OAT
8 | # start=80 - initial temperature
9 | # rate=1 - period of report
10 | # jump=2 - width of variation
11 | #
12 | from smap import driver
13 | from smap.util import periodicSequentialCall
14 | import math
15 |
16 | class OAT(driver.SmapDriver):
17 | def setup(self, opts):
18 | self.starttemp = float(opts.pop('start_temp','80'))
19 | self.rate = float(opts.pop('rate','1'))
20 | self.jump = float(opts.pop('jump','2'))
21 | self.curtemp = self.starttemp
22 | self.t = 1
23 | self.add_timeseries('/temperature','F',data_type='double')
24 |
25 | def start(self):
26 | periodicSequentialCall(self.read).start(self.rate)
27 |
28 | def read(self):
29 | self.t += 1
30 | self.curtemp = self.starttemp + self.jump*math.sin(.01 * self.t)
31 | self.add('/temperature', self.curtemp)
32 |
33 |
--------------------------------------------------------------------------------
/smap-control-tutorial/room.ini:
--------------------------------------------------------------------------------
1 | # Configuration for a the room model, represented by a pseudo air temp
2 | # Metadata links it to common building
3 | # Operates as an independent source on its own thread
4 |
5 | # Archiver with API key
6 | [report 0]
7 | ReportDeliveryLocation = http://localhost:8079/add/8KwKzvbVjZZ7AlQzEwBjR7LUPRiwWnh2jg3D
8 |
9 | [/]
10 | uuid = 1814f354-1432-11e4-9cec-000c29b778da
11 | Metadata/SourceName = Demontration Room Model with Air Conditioner cooling
12 |
13 | Metadata/Site/Name = Imaginary Building
14 | Metadata/Site/id = 2c8ed966-146a-11e4-9e46-000c29b778da
15 |
16 | [server]
17 | port = 1237
18 |
19 | [/room]
20 | type=roommodeldriver.Room
21 | rate=1
22 | # warm at 100 s per degree diff, cool at 20s per degree
23 | therm_resistance=.01
24 | epsilon=.05
25 | starttemp=72
26 | archiver_url=http://localhost:8079
27 | OATpath = /OAT/temperature
28 | coolpath = /control/cool
29 | siteid = 2c8ed966-146a-11e4-9e46-000c29b778da
30 |
31 | [/room/airtemp]
32 | Metadata/Description = Pseudo sensor modeling room air temperature
33 | Metadata/Type = Sensor
34 | Metadata/Kind = Temperature
35 | Metadata/Form = Air
36 |
37 |
--------------------------------------------------------------------------------
/smap-control-tutorial/roommodeldriver.py:
--------------------------------------------------------------------------------
1 | from smap import actuate, driver
2 | from smap.archiver.client import RepublishClient
3 | from smap.util import periodicSequentialCall
4 | import random
5 | import math
6 |
7 | class Room(driver.SmapDriver):
8 | def badclient(self, resp):
9 | print "Error connecting: ", resp
10 |
11 | def setup(self, opts):
12 | self.archiver_url = opts.pop('archiver_url','http://localhost:8079')
13 | self.oatpath = opts.pop('OATpath','/OAT/temperature')
14 | self.coolpath = opts.pop('coolpath', '/control/cool')
15 | self.siteid = opts.pop('siteid','')
16 |
17 | # Subscription to OAT stream, registering a callback
18 | restriction = "Path = '{0}'".format(self.oatpath)
19 | if self.siteid :
20 | restriction = restriction + " and Metadata/Site/id = '{0}'".format(self.siteid)
21 | self.oatclient = RepublishClient(self.archiver_url, self.oatcb, restrict=restriction, connect_error=self.badclient)
22 |
23 | # subscribe to the cool control stream
24 | restriction = "Path = '{0}'".format(self.coolpath)
25 | if self.siteid :
26 | restriction = restriction + " and Metadata/Site/id = '{0}'".format(self.siteid)
27 |
28 | self.coolclient = RepublishClient(self.archiver_url, self.coolcb, restrict=restriction, connect_error=self.badclient)
29 |
30 | # initalize parameters for the room model
31 | self.rate = float(opts.get('rate',1)) # model update rate
32 | self.therm_resistance = float(opts.get('therm_resistance', .1)) # thermal resistance factor
33 | self.e = float(opts.get('epsilon',.1)) # cooling factor
34 |
35 | # initial state of the room
36 | self.temp = float(opts.get('starttemp', 75)) # initial room
37 | self.oat_val = self.temp
38 | self.cool = 0
39 |
40 | # Create the timeseries for the pseudo air temp sensor
41 | self.add_timeseries('/airtemp','F',data_type='double')
42 |
43 | # start the driver
44 | def start(self):
45 | self.oatclient.connect() # activate subscription to OAT stream'
46 | self.coolclient.connect() # activate subscription to cool stream
47 | periodicSequentialCall(self.read).start(self.rate) # schedule model periodically
48 |
49 | # Model simple physics of a room with heating across a thermally
50 | # resistant barrier and forced cooling
51 | def read(self):
52 | dt = (self.oat_val - self.temp)
53 | # self.temp = self.oat_val + 2.3 # little test during debugging
54 | self.temp = self.temp + self.rate * self.therm_resistance * dt - self.cool*self.e*dt
55 | self.add('/airtemp', self.temp) # add new value to the airtemp stream
56 |
57 | # Event handler for publication to OAT stream
58 | def oatcb(self, _, data):
59 | # list of arrays of [time, val]
60 | mostrecent = data[-1][-1]
61 | self.oat_val = mostrecent[1]
62 |
63 | # Event handler for publication to cool stream
64 | def coolcb(self, _, data):
65 | mostrecent = data[-1][-1]
66 | self.cool = mostrecent[1]
67 |
--------------------------------------------------------------------------------
/smap-control-tutorial/schedule-demo.ini:
--------------------------------------------------------------------------------
1 | # Set to api key for the particular site
2 | [report 0]
3 | ReportDeliveryLocation = http://localhost:8079/add/8KwKzvbVjZZ7AlQzEwBjR7LUPRiwWnh2jg3D
4 |
5 | # unique resource for this demonstration
6 | [/]
7 | # unique identifier for the collection
8 | uuid = 8b37a536-1ebc-11e4-9e51-000c29b778da
9 |
10 | Metadata/SourceName = Schedule Demonstration
11 |
12 | Metadata/Site/Name = Imaginary Building
13 | # unique identifier for the site, which may have many collections
14 | Metadata/Site/id = 2c8ed966-146a-11e4-9e46-000c29b778da
15 |
16 | # REST resource for control demo
17 | [server]
18 | port = 1240
19 |
20 | [/scheduler]
21 | type = schedulerService.Scheduler
22 | rate = 15
23 | defaultHeatSetpoint = 68
24 | defaultCoolSetpoint = 76
25 |
26 | [/scheduler/heatSetpoint]
27 | Metadata/Description = Heating setpoint
28 | Metadata/Type = Setpoint
29 |
30 | [/scheduler/coolSetpoint]
31 | Metadata/Description = Cooling setpoint
32 | Metadata/Type = Setpoint
33 |
34 | [/thermostat1]
35 | type=virtualthermostatDriver.VirtualThermostat
36 | Metadata/Description = Virtual Thermostat for Zone 1
37 | archiver_url=http://localhost:8079
38 | heatSPpath = /scheduler/heatSetpoint
39 | coolSPpath = /scheduler/coolSetpoint
40 | trim = 1
41 |
42 | [/thermostat2]
43 | type=virtualthermostatDriver.VirtualThermostat
44 | Metadata/Description = Virtual Thermostat for Zone 2
45 | archiver_url=http://localhost:8079
46 | heatSPpath = /scheduler/heatSetpoint
47 | coolSPpath = /scheduler/coolSetpoint
48 | trim = -1
49 |
--------------------------------------------------------------------------------
/smap-control-tutorial/schedule.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (c) 20142, Regents of the University of California
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions
7 | are met:
8 |
9 | - Redistributions of source code must retain the above copyright
10 | notice, this list of conditions and the following disclaimer.
11 | - Redistributions in binary form must reproduce the above copyright
12 | notice, this list of conditions and the following disclaimer in the
13 | documentation and/or other materials provided with the
14 | distribution.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
19 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
20 | THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
21 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
25 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
27 | OF THE POSSIBILITY OF SUCH DAMAGE.
28 | """
29 | """
30 | @author David Culler
31 | """
32 |
33 | from copy import copy
34 |
35 | def cvt(hr,mn) :return hr+mn/60.0
36 |
37 | class scheduler():
38 | """
39 | Scheduler class to hold and manage a set of named schedules,
40 | each constisting of an ordered sequence of named epochs
41 | with a start time, as ':', and a dictionary of :value
42 |
43 | """
44 | schedules = {}
45 |
46 | def __init__(self, initial={}) :
47 | """
48 | Create a scheduler, optionally with a dict of schedules
49 | {:((,,{})*)*}
50 |
51 | traverses initial schedules to validate and transfer to internal representation
52 |
53 | """
54 | for schedName, sched in initial.iteritems() :
55 | self.addSchedule(schedName, sched)
56 |
57 | def getSchedules(self) :
58 | """
59 | Return the sequence of schedule names in the scheduler
60 | """
61 | return [name for name, schedule in self.schedules.iteritems()]
62 |
63 | def getSchedule(self, scheduleName) :
64 | """
65 | Return the list of (,,) epochs for the named schedule
66 | """
67 | return copy(self.schedules[scheduleName]) if scheduleName in self.schedules else {}
68 |
69 | def addSchedule(self, scheduleName, schedule) :
70 | """
71 | Add a schedule to the scheduler, replacing any schedule with the same name
72 | """
73 | self.schedules[scheduleName] = [(name, start, points) for name, start, points in schedule]
74 |
75 | def delSchedule(self,scheduleName) :
76 | """
77 | Delete a schedule for the scheduler
78 | """
79 | del self.schedules[scheduleName]
80 |
81 | def getPoints(self, scheduleName, hour, minute) :
82 | """
83 | Return the epoch name, points for the epoch covered by the specified time
84 | """
85 | # Lookup named schedule in schedules database
86 | # Return: (epoch, points)
87 | # epoch: name of the epoch containing hour:minute
88 | # points: dictionary of points for the prior enclosing epoch
89 | # 'none', {} if schedule or enclosing epoch does not exist
90 | epoch, prev = 'none', {}
91 | target = cvt(hour, minute)
92 | if (scheduleName in self.schedules) :
93 | schedule = self.schedules[scheduleName]
94 | for ep,start,points in schedule :
95 | hr,mn = start.split(':')
96 | nextTime = cvt(int(hr),int(mn))
97 | if (target < nextTime) : break
98 | epoch, prev = ep, points
99 | return epoch, copy(prev)
100 |
101 | def getFinalPoints(self, scheduleName) :
102 | """
103 | Return the epoch name, points for the final epoch in a schedule
104 |
105 | This is useful for constructing cyclic schedules
106 | """
107 | if (scheduleName in self.schedules) :
108 | schedule = self.schedules[scheduleName]
109 | name, start, points = schedule[-1]
110 | return name, copy(points)
111 | else :
112 | return 'none',{}
113 |
114 | def generateDay(self, scheduleName, minutes=30, initial='') :
115 | """
116 | Generate and iterator that will produce a periodic series
117 | of eopch,points for an day under a schedule
118 |
119 | initial, if provided, starts the series with the final epoch of the
120 | specified schedule
121 | """
122 | name, state = 'none', {}
123 | if initial : name, state = self.getFinalPoints(initial)
124 | for m in range(0,24*60,minutes) :
125 | hour = m/60
126 | min = m - hour*60
127 | ename, points = self.getPoints(scheduleName,hour,min)
128 | if ename != 'none' : name = ename
129 | for point, val in points.iteritems() : state[point] = val
130 | yield (hour, min, name, state)
131 |
132 | if __name__ == '__main__':
133 | testschedules = {'weekday': (
134 | ('morning', '7:30', {'setpoint':72, 'light':'on'}),
135 | ('evening','17:00', {'setpoint':75, 'light':'on'}),
136 | ('night', '20:30', {'setpoint':54, 'light':'off'})
137 | ),
138 | 'weekend': (
139 | ('morning', '9:30', {'setpoint':71, 'light':'on'}),
140 | ('evening','17:00', {'setpoint':75, 'light':'on'}),
141 | ('night', '21:00', {'setpoint':57, 'light':'off'})
142 | )
143 | }
144 | s = scheduler(testschedules)
145 | for x in s.generateDay('weekday', 30, 'weekend') : print x
146 |
147 |
148 |
149 |
--------------------------------------------------------------------------------
/smap-control-tutorial/schedulerService.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (c) 2014, Regents of the University of California
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions
7 | are met:
8 |
9 | - Redistributions of source code must retain the above copyright
10 | notice, this list of conditions and the following disclaimer.
11 | - Redistributions in binary form must reproduce the above copyright
12 | notice, this list of conditions and the following disclaimer in the
13 | documentation and/or other materials provided with the
14 | distribution.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
19 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
20 | THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
21 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
25 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
27 | OF THE POSSIBILITY OF SUCH DAMAGE.
28 | """
29 | """
30 | @author David Culler
31 | """
32 | from smap import actuate, driver
33 | from smap.util import periodicSequentialCall
34 | import schedule
35 | import time
36 |
37 | testschedules = {'weekday': (('morning', '8:00', {'heatSetpoint':68, 'coolSetpoint': 75}),
38 | ('night', '18:00', {'heatSetpoint':55, 'coolSetpoint': 85})),
39 | 'weekend': (('morning', '9:30', {'heatSetpoint':68, 'coolSetpoint': 75}),
40 | ('evening','14:00', {'heatSetpoint':55, 'coolSetpoint': 85}),
41 | ('night', '21:00', {'heatSetpoint':55, 'coolSetpoint': 85}))}
42 |
43 | class Scheduler(driver.SmapDriver):
44 | def setup(self, opts):
45 | self.rate = float(opts.get('rate',15))
46 | self.sched = schedule.scheduler(testschedules)
47 |
48 | # initialize weekly schedule - uses date.weekday convention
49 | self.weekly = ('weekday','weekday','weekday','weekday','weekday','weekend','weekend')
50 | self.schedule = 'none'
51 | self.epoch = 'none'
52 |
53 | # Current state of the points
54 | self.points = {}
55 | self.points['heatSetpoint']=int(opts.get('defaultHeatSetpoint',68))
56 | self.points['coolSetpoint']=int(opts.get('defaultCoolSetpoint',76))
57 | print "Scheduler ", self.points
58 |
59 | # create timeseries for scheduler actions
60 | heatSetPoint = self.add_timeseries('/heatSetpoint', 'F', data_type='long')
61 | coolSetPoint = self.add_timeseries('/coolSetpoint', 'F', data_type='long')
62 |
63 | # ought to have schedule and epoch be timeseries too. This forces enumerate
64 | heatSetPoint.add_actuator(setpointActuator(scheduler=self, range=(40,90)))
65 | coolSetPoint.add_actuator(setpointActuator(scheduler=self, range=(40,90)))
66 |
67 | def start(self):
68 | print "scheduler start: ", self.rate
69 | periodicSequentialCall(self.read).start(self.rate)
70 |
71 | # Periodically check the schedule
72 | # when there is a change publish to thermostats, will persist till new epoch or overide
73 | def read(self):
74 | now = time.localtime()
75 | scheduleName = self.weekly[now.tm_wday]
76 | self.epoch, self.points = self.sched.getPoints(scheduleName, now.tm_hour, now.tm_min)
77 | print "scheduler read: ", self.epoch, self.points
78 | for (point,val) in self.points.iteritems() :
79 | self.points[point] = val
80 | self.add('/'+point, val)
81 | print "scheduler points: ", self.points
82 |
83 | class setpointActuator(actuate.ContinuousActuator):
84 | def __init__(self, **opts):
85 | actuate.ContinuousActuator.__init__(self, range=(40,100))
86 | self.scheduler = opts.get('scheduler')
87 |
88 | def get_state(self, request):
89 | return self.scheduler.points
90 |
91 | def set_state(self, request, val):
92 | print "set state", request, val
93 | # self.scheduler.sp = val
94 | return state
95 |
96 |
97 |
98 |
99 |
100 |
--------------------------------------------------------------------------------
/smap-control-tutorial/schedulerZoneDemo.ini:
--------------------------------------------------------------------------------
1 | # Set to api key for the particular application
2 | [report 0]
3 | ReportDeliveryLocation = http://localhost:8079/add/8KwKzvbVjZZ7AlQzEwBjR7LUPRiwWnh2jg3D
4 |
5 | # unique resource for this demonstration
6 | [/]
7 |
8 | # unique identifier for the collection
9 | uuid = 8b37a536-1ebc-11e4-9e51-000c29b778da
10 |
11 | Metadata/SourceName = Schedule Demonstration
12 | Metadata/Site/Name = Imaginary Building
13 | # unique identifier for the site, which may have many collections
14 | Metadata/Site/id = 2c8ed966-146a-11e4-9e46-000c29b778da
15 |
16 | # REST resource for control demo
17 | [server]
18 | port = 1240
19 |
20 | # Scheduler operating across hvac and lighting
21 | [/scheduler]
22 | type = schedulerService.Scheduler
23 | rate = 10
24 | defaultHeatSetpoint = 70
25 | defaultCoolSetpoint = 74
26 | archiver_url=http://localhost:8079
27 |
28 | [/scheduler/heatSetpoint]
29 | Metadata/Description = Master Heating setpoint
30 | Metadata/Type = Setpoint
31 |
32 | [/scheduler/coolSetpoint]
33 | Metadata/Description = Master Cooling setpoint
34 | Metadata/Type = Setpoint
35 |
36 | [/buildinghvac]
37 | type = Collection
38 | Metadata/System = HVAC
39 | Metadata/Role = Building HVAC
40 | archiver_url=http://localhost:8079
41 |
42 | # Zone with zone controller intermediary between master schedule and thermostat
43 | [/buildinghvac/zonecontroller1]
44 | type = zoneControllerService.ZoneController
45 | Metadata/Description = Zone controller for Zone 1
46 | Metadata/HVACZone = Zone1
47 | rate = 10
48 | heatSPwhere = Path="/scheduler/heatSetpoint" and Metadata/Site/id = "2c8ed966-146a-11e4-9e46-000c29b778da"
49 | coolSPwhere = Path="/scheduler/coolSetpoint" and Metadata/Site/id = "2c8ed966-146a-11e4-9e46-000c29b778da"
50 | thermwhere = Path = "/buildinghvac/thermostat1/temp" and Metadata/Site/id = "2c8ed966-146a-11e4-9e46-000c29b778da"
51 | tempwhere = Metadata/HVACZone = "Zone1" and Metadata/Site/id = "2c8ed966-146a-11e4-9e46-000c29b778da" and Metadata/Type = "Sensor" and Metadata/Kind = "Temperature" and Metadata/Form = "Air"
52 |
53 |
54 | defaultHeatSetpoint = 68
55 | defaultCoolSetpoint = 76
56 | trim = 1
57 |
58 | [/buildinghvac/thermostat1]
59 | type=virtualthermostatDriver.VirtualThermostat
60 | Metadata/Description = Virtual Thermostat for Zone 1
61 | Metadata/HVACZone = Zone1
62 | Metadata/Room = Big Room
63 | heatSPpath = /zoneController1/heatSetpoint
64 | coolSPpath = /zoneController1/coolSetpoint
65 |
66 | # zone controlled directly from master schedule
67 | [/buildinghvac/thermostat2]
68 | type=virtualthermostatDriver.VirtualThermostat
69 | Metadata/Description = Virtual Thermostat for Zone 2
70 | Metadata/HVACZone = Zone2
71 | Metadata/Room = Library
72 | archiver_url=http://localhost:8079
73 | heatSPpath = /scheduler/heatSetpoint
74 | coolSPpath = /scheduler/coolSetpoint
75 |
76 |
77 | [/monitoring]
78 | type = Collection
79 | Metadata/System = Monitoring
80 | Metadata/Role = Monitoring
81 |
82 | [/monitoring/temp1]
83 | type=temperatureDriver.Temperature
84 | Metadata/Description = Virtual Temperature Sensor for Room 2 Zone 1
85 | Metadata/Room = Little room
86 | Metadata/HVACZone = Zone1
87 |
88 | [/monitoring/temp1/temperature]
89 | Metadata/Type = Sensor
90 | Metadata/Kind = Temperature
91 | Metadata/Form = Air
92 | start=70
93 | rate=7
94 | jump=2
95 |
96 | [/monitoring/temp2]
97 | type=temperatureDriver.Temperature
98 | Metadata/Description = Virtual Temperature Sensor for Room 3 Zone 1
99 | Metadata/Room = Green room
100 | Metadata/HVACZone = Zone1
101 |
102 | [/monitoring/temp2/temperature]
103 | Metadata/Type = Sensor
104 | Metadata/Kind = Temperature
105 | Metadata/Form = Air
106 | start=68
107 | rate=5
108 | jump=2
109 |
110 | [/monitoring/OAT]
111 | type=pseudoOATdriver.OAT
112 | Metadata/Kind = Temperature
113 | Metadata/Form = Air
114 | start=78
115 | rate=1
116 | jump=2
117 | Metadata/Description = Pseudo sensor generating simulated OAT
118 |
119 | [/monitoring/OAT/temperature]
120 | Metadata/Type = Sensor
121 | Metadata/Kind = Temperature
122 | Metadata/Form = Air
123 |
124 |
125 |
--------------------------------------------------------------------------------
/smap-control-tutorial/startdrivers.py:
--------------------------------------------------------------------------------
1 | import subprocess
2 | import sys
3 |
4 | inis = sys.argv[1:]
5 |
6 | processes = []
7 | for ini in inis:
8 | command = "twistd --pidfile {0}.pid -n smap {0}.ini".format(ini)
9 | p = subprocess.Popen(command, shell=True)
10 | processes.append(p)
11 | # run smap query to see if the streams are registered
12 | # This is partly to get around the race condition arising when
13 | # a driver subscribes to the output of another
14 | # However, top handle the mutual dependence case, it really needs to
15 | # restart the services
16 | # Note that this encodes the uri of the archiver
17 | print subprocess.Popen('smap-query -u http://localhost:8079/api "select uuid"', shell=True, stdout=subprocess.PIPE).stdout.read()
18 | try:
19 | raw_input()
20 | except:
21 | for p in processes:
22 | print "killing",p
23 | p.terminate()
24 |
--------------------------------------------------------------------------------
/smap-control-tutorial/temperatureDriver.py:
--------------------------------------------------------------------------------
1 | from smap import driver
2 | from smap.archiver.client import RepublishClient
3 | from smap.util import periodicSequentialCall
4 | import math
5 |
6 | class Temperature(driver.SmapDriver):
7 | def setup(self, opts):
8 | self.starttemp = float(opts.pop('start_temp','72'))
9 | self.rate = float(opts.pop('rate','1'))
10 | self.jump = float(opts.pop('jump','1'))
11 | self.curtemp = self.starttemp
12 |
13 | self.t = 1
14 |
15 | self.add_timeseries('/temperature','F',data_type='double')
16 |
17 | def start(self):
18 | periodicSequentialCall(self.read).start(self.rate)
19 |
20 | def read(self):
21 | self.t += 1
22 | self.curtemp = 78 + 10*math.sin(.01 * self.t)
23 | self.add('/temperature', self.curtemp)
24 |
25 |
--------------------------------------------------------------------------------
/smap-control-tutorial/virtualthermostatDriver.py:
--------------------------------------------------------------------------------
1 | from smap import driver, actuate
2 | from smap.archiver.client import RepublishClient
3 | from smap.util import periodicSequentialCall
4 |
5 | class VirtualThermostat(driver.SmapDriver):
6 | def setup(self, opts):
7 | self.state = {'temp': 70,
8 | 'humidity': 50,
9 | 'hvac_state': 1,
10 | 'temp_heat': 70,
11 | 'temp_cool': 75,
12 | 'hold': 0,
13 | 'override': 0,
14 | 'hvac_mode': 1,
15 | 'fan_mode': 1,
16 | }
17 |
18 | self.readperiod = float(opts.get('ReadPeriod',3))
19 | self.add_timeseries('/temp', 'F', data_type='long')
20 | self.add_timeseries('/humidity', '%RH', data_type='long')
21 | self.add_timeseries('/hvac_state', 'Mode', data_type='long')
22 | temp_heat = self.add_timeseries('/temp_heat', 'F', data_type='long')
23 | temp_cool = self.add_timeseries('/temp_cool', 'F', data_type='long')
24 | hold = self.add_timeseries('/hold', 'On/Off', data_type='long')
25 | override = self.add_timeseries('/override', 'On/Off', data_type='long')
26 | hvac_mode = self.add_timeseries('/hvac_mode', 'Mode', data_type='long')
27 | fan_mode = self.add_timeseries('/fan_mode', 'Mode', data_type='long')
28 |
29 | temp_heat.add_actuator(SetpointActuator(tstat=self, path='temp_heat', _range=(45, 95)))
30 | temp_cool.add_actuator(SetpointActuator(tstat=self, path='temp_cool', _range=(45, 95)))
31 | hold.add_actuator(OnOffActuator(tstat=self, path='hold'))
32 | override.add_actuator(OnOffActuator(tstat=self, path='override'))
33 | hvac_mode.add_actuator(ModeActuator(tstat=self, path='hvac_mode', states=[0,1,2,3]))
34 | fan_mode.add_actuator(OnOffActuator(tstat=self, path='fan_mode'))
35 |
36 | self.archiver_url = opts.pop('archiver_url','http://localhost:8079')
37 | self.heatSPpath = opts.pop('heatSPpath', '/scheduler/heatSetpoint')
38 | self.coolSPpath = opts.pop('coolSPpath', '/scheduler/coolSetpoint')
39 | # add mode
40 | self.siteid = opts.pop('siteid','')
41 | restriction = "Path = '{0}'".format(self.heatSPpath)
42 | if self.siteid :
43 | restriction = restriction + " and Metadata/Site/id = '{0}'".format(self.siteid)
44 | self.heatSPclient = RepublishClient(self.archiver_url, self.heatSPcb, restrict=restriction)
45 | restriction = "Path = '{0}'".format(self.coolSPpath)
46 | if self.siteid :
47 | restriction = restriction + " and Metadata/Site/id = '{0}'".format(self.siteid)
48 | self.coolSPclient = RepublishClient(self.archiver_url, self.coolSPcb, restrict=restriction)
49 |
50 |
51 | metadata_type = [
52 | ('/temp','Sensor'),
53 | ('/humidity','Sensor'),
54 | ('/temp_heat','Reading'),
55 | ('/temp_heat_act','SP'),
56 | ('/temp_cool','Reading'),
57 | ('/temp_cool_act','SP'),
58 | ('/hold','Reading'),
59 | ('/hold_act','Command'),
60 | ('/override','Reading'),
61 | ('/override_act','Command'),
62 | ('/hvac_mode','Reading'),
63 | ('/hvac_mode_act','Command')
64 | ]
65 | for ts, tstype in metadata_type:
66 | self.set_metadata(ts,{'Metadata/Type':tstype})
67 |
68 | def start(self):
69 | self.heatSPclient.connect() # activate subscription scheduler setpoints
70 | self.coolSPclient.connect()
71 | periodicSequentialCall(self.read).start(self.readperiod)
72 |
73 | def read(self):
74 | for k,v in self.state.iteritems():
75 | self.add('/'+k, v)
76 |
77 | # Event handler for publication to heatSP stream
78 | def heatSPcb(self, _, data):
79 | # list of arrays of [time, val]
80 | mostrecent = data[-1][-1]
81 | self.heatSP = mostrecent[1]
82 | print "Set heating setpoint", self.heatSP
83 | self.state['temp_heat'] = self.heatSP
84 |
85 | def coolSPcb(self, _, data):
86 | # list of arrays of [time, val]
87 | mostrecent = data[-1][-1]
88 | self.coolSP = mostrecent[1]
89 | print "Set cooling setpoint", self.coolSP
90 | self.state['temp_cool'] = self.coolSP
91 |
92 | class VirtualThermostatActuator(actuate.SmapActuator):
93 | def __init__(self, **opts):
94 | self.tstat = opts.get('tstat')
95 | self.path = opts.get('path')
96 |
97 | class SetpointActuator(VirtualThermostatActuator, actuate.ContinuousIntegerActuator):
98 | def __init__(self, **opts):
99 | actuate.ContinuousIntegerActuator.__init__(self, opts['_range'])
100 | VirtualThermostatActuator.__init__(self, **opts)
101 |
102 | def get_state(self, request):
103 | return self.tstat.state[self.path]
104 |
105 | def set_state(self, request, state):
106 | self.tstat.state[self.path] = int(state)
107 | return self.tstat.state[self.path]
108 |
109 | class ModeActuator(VirtualThermostatActuator, actuate.NStateActuator):
110 | def __init__(self, **opts):
111 | actuate.NStateActuator.__init__(self, opts['states'])
112 | VirtualThermostatActuator.__init__(self, **opts)
113 |
114 | def get_state(self, request):
115 | return self.tstat.state[self.path]
116 |
117 | def set_state(self, request, state):
118 | self.tstat.state[self.path] = int(state)
119 | return self.tstat.state[self.path]
120 |
121 | class OnOffActuator(VirtualThermostatActuator, actuate.BinaryActuator):
122 | def __init__(self, **opts):
123 | actuate.BinaryActuator.__init__(self)
124 | VirtualThermostatActuator.__init__(self, **opts)
125 |
126 | def get_state(self, request):
127 | return self.tstat.state[self.path]
128 |
129 | def set_state(self, request, state):
130 | self.tstat.state[self.path] = int(state)
131 | return self.tstat.state[self.path]
132 |
--------------------------------------------------------------------------------
/smap-control-tutorial/zoneControllerService.py:
--------------------------------------------------------------------------------
1 | """
2 | Copyright (c) 2014, Regents of the University of California
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions
7 | are met:
8 |
9 | - Redistributions of source code must retain the above copyright
10 | notice, this list of conditions and the following disclaimer.
11 | - Redistributions in binary form must reproduce the above copyright
12 | notice, this list of conditions and the following disclaimer in the
13 | documentation and/or other materials provided with the
14 | distribution.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
19 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
20 | THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
21 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
25 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
27 | OF THE POSSIBILITY OF SUCH DAMAGE.
28 | """
29 | """
30 | @author David Culler
31 | """
32 | from smap import actuate, driver
33 | from smap.util import periodicSequentialCall
34 | from smap.archiver.client import RepublishClient
35 | from smap.archiver.client import SmapClient
36 | import time
37 | from pprint import pprint
38 | from smap.contrib import dtutil
39 |
40 | class ZoneController(driver.SmapDriver):
41 | def setup(self, opts):
42 | self.rate = float(opts.get('rate',10))
43 | # Current state of the points
44 | self.heatSP=int(opts.get('defaultHeatSetpoint',68))
45 | self.coolSP=int(opts.get('defaultCoolSetpoint',76))
46 |
47 | self.therm_temp = 70
48 |
49 | self.trim = int(opts.get('trim',0)) # dummy zoneCtrl action
50 |
51 | # create timeseries for zone controller actions
52 | heatSetPoint = self.add_timeseries('/heatSetpoint', 'F', data_type='double')
53 | coolSetPoint = self.add_timeseries('/coolSetpoint', 'F', data_type='double')
54 | # add actuators to them
55 | heatSetPoint.add_actuator(setpointActuator(controller=self, range=(40,90)))
56 | coolSetPoint.add_actuator(setpointActuator(controller=self, range=(40,90)))
57 |
58 | # get master set point stream paths
59 | self.archiver_url = opts.get('archiver_url','http://localhost:8079')
60 | self.heatSPwhere = opts.get('heatSPwhere', '')
61 | self.coolSPwhere = opts.get('coolSPwhere', '')
62 | self.thermwhere = opts.get('thermwhere', '')
63 | self.tempwhere = opts.get('tempwhere', '')
64 |
65 | print "ZoneController: heat sp where = ", self.heatSPwhere
66 | print "ZoneController: cool sp where = ", self.coolSPwhere
67 | print "ZoneController: thermostat where = ", self.thermwhere
68 | print "ZoneController: temp sensor where = ", self.tempwhere
69 |
70 | self.client = SmapClient(self.archiver_url)
71 |
72 | self.heatSPclient = RepublishClient(self.archiver_url, self.heatSPcb, restrict=self.heatSPwhere)
73 | self.coolSPclient = RepublishClient(self.archiver_url, self.coolSPcb, restrict=self.coolSPwhere)
74 | #self.tempclient = RepublishClient(self.archiver_url, self.tempcb, restrict=self.tempwhere)
75 | self.thermclient = RepublishClient(self.archiver_url, self.thermcb, restrict=self.thermwhere)
76 |
77 |
78 | def start(self):
79 | print "zone controller start: ", self.rate
80 | self.heatSPclient.connect() # activate subscription scheduler setpoints
81 | self.coolSPclient.connect()
82 | #self.tempclient.connect()
83 | self.thermclient.connect()
84 | periodicSequentialCall(self.read).start(self.rate)
85 |
86 | def read(self):
87 | all_readings = self.client.latest(self.tempwhere)
88 | for p in all_readings:
89 | print '-'*20
90 | md = self.client.tags('uuid = "'+p['uuid']+'"')[0]
91 | print 'Room:', md['Metadata/Room']
92 | print 'Reading:', p['Readings'][0][1]
93 | ts = dtutil.ts2dt(p['Readings'][0][0]/1000)
94 | print 'Time:', dtutil.strftime_tz(ts, tzstr='America/Los_Angeles')
95 | avg_room_temp = sum([x['Readings'][0][1] for x in all_readings]) / float(len(all_readings))
96 |
97 | # get difference between avg room temperature and thermostat temperature
98 | new_diff = self.therm_temp - avg_room_temp
99 |
100 | # periodically update output streams. Here a bogus adjustment
101 | self.add('/heatSetpoint', self.heatSP + new_diff)
102 | self.add('/coolSetpoint', self.coolSP + new_diff)
103 | print "zone controller publish: ", self.heatSP, self.coolSP
104 |
105 | # Event handler for publication to heatSP stream
106 | def heatSPcb(self, _, data):
107 | # list of arrays of [time, val]
108 | print "ZoneController heatSPcb: ", data
109 | mostrecent = data[-1][-1]
110 | self.heatSP = mostrecent[1]
111 |
112 | def coolSPcb(self, _, data):
113 | # list of arrays of [time, val]
114 | print "ZoneController coolSPcb: ", data
115 | mostrecent = data[-1][-1]
116 | self.coolSP = mostrecent[1]
117 |
118 | def tempcb(self, _, data):
119 | # list of arrays of [time, val]
120 | print "ZoneController tempcb: ", data
121 |
122 |
123 | def thermcb(self, _, data):
124 | # list of arrays of [time, val]
125 | print "ZoneController thermcb: ", data
126 | self.therm_temp = data[-1][-1][1]
127 |
128 |
129 |
130 | class setpointActuator(actuate.ContinuousActuator):
131 | def __init__(self, **opts):
132 | actuate.ContinuousActuator.__init__(self, range=(40,100))
133 | self.controller = opts.get('controller')
134 |
135 | def get_state(self, request):
136 | return self.controller.heatSP, self.controller.coolSP
137 |
138 | def set_state(self, request, val):
139 | print "set state", request, val
140 | # do something here
141 | return self.controller.heatSP, self.controller.coolSP
142 |
143 |
144 |
145 |
146 |
147 |
148 |
--------------------------------------------------------------------------------
/zone-controller-tutorial/README.md:
--------------------------------------------------------------------------------
1 | # Zone Controller Example
2 |
3 | Before reading this, please read the following sMAP wiki pages:
4 |
5 | * [Schedules](https://github.com/SoftwareDefinedBuildings/smap/wiki/Scheduler-Service)
6 | * [Zone Controllers](https://github.com/SoftwareDefinedBuildings/smap/wiki/Scheduler-Service)
7 |
8 | We consider the following small, 3-room, 2-zone building:
9 |
10 | ```
11 | <---------------Zone 1-----------------><-----Zone 2----->
12 | +--------------------------------------------------------+
13 | | | | |
14 | | *sensor | *sensor | *sensor |
15 | | | | |
16 | | | | |
17 | | | | |
18 | | Room 1 | Room 2 | Room 3 |
19 | | | | |
20 | | | | |
21 | | | | |
22 | | | | |
23 | |*thermostat | |*thermostat |
24 | +--------------------------------------------------------+
25 | ```
26 |
27 | Each room has a temperature/humidity sensor, but only Rooms 1 and 3 have
28 | thermostats. Each room also has an independent lighting controller.
29 |
30 | We have a master schedule and a zone controller each for Zone 1 and Zone 2. We
31 | will have Zone 2 follow the master schedule with a simple trim of 5 degrees F,
32 | and Zone 1 will follow the master schedule, but trim by the difference between
33 | the average sensor temperature and the thermostat's reported temperature.
34 |
--------------------------------------------------------------------------------
/zone-controller-tutorial/building.ini:
--------------------------------------------------------------------------------
1 | [report 1]
2 | ReportDeliveryLocation = http://localhost:8079/add/lVzBMDpnkXApJmpjUDSvm4ceGfpbrLLSd9cq
3 |
4 | [/]
5 | uuid = 90cc4cd1-3e8e-11e4-b536-6003089ed1d0
6 | Metadata/SourceName = Zone Controller Demo
7 | Metadata/Building = Demo Building
8 | Metadata/Site = a1a04b23-3e8e-11e4-9e19-6003089ed1d0
9 |
10 | [server]
11 | port = 8080
12 |
13 | ## Lighting Drivers ##
14 | [/buildinglighting]
15 | type = Collection
16 | Metadata/System = Lighting
17 | Metadata/Role = Building Lighting
18 | Metadata/Floor = 1
19 | Metadata/Name = 1st Floor Lighting Control
20 |
21 | [/buildinglighting/group0]
22 | type = smap.drivers.lights.virtuallight.VirtualLight
23 | Metadata/Group = 0
24 | Metadata/LightingZone = Room 1
25 |
26 | [/buildinglighting/group1]
27 | type = smap.drivers.lights.virtuallight.VirtualLight
28 | Metadata/Group = 1
29 | Metadata/LightingZone = Room 2
30 |
31 | [/buildinglighting/group2]
32 | type = smap.drivers.lights.virtuallight.VirtualLight
33 | Metadata/Group = 2
34 | Metadata/LightingZone = Room 3
35 |
36 | ## Thermostat Drivers ##
37 | [/buildinghvac]
38 | type = Collection
39 | Metadata/System = HVAC
40 | Metadata/Role = Building HVAC
41 | Metadata/Floor = 1
42 | Metadata/Name = 1st Floor Thermostats
43 |
44 | [/buildinghvac/thermostat0]
45 | type = smap.drivers.thermostats.virtualthermostat.VirtualThermostat
46 | Metadata/Room = 1
47 | Metadata/HVACZone = Zone 1
48 | temp_heat = Metadata/System = 'HVAC' and Metadata/HVACZone = 'Zone 1' and Metadata/Description = 'Zone Heating setpoint'
49 | temp_cool = Metadata/System = 'HVAC' and Metadata/HVACZone = 'Zone 1' and Metadata/Description = 'Zone Cooling setpoint'
50 |
51 | [/buildinghvac/thermostat1]
52 | type = smap.drivers.thermostats.virtualthermostat.VirtualThermostat
53 | Metadata/Room = 3
54 | Metadata/HVACZone = Zone 2
55 | temp_heat = Metadata/System = 'HVAC' and Metadata/HVACZone = 'Zone 2' and Metadata/Description = 'Zone Heating setpoint'
56 | temp_cool = Metadata/System = 'HVAC' and Metadata/HVACZone = 'Zone 2' and Metadata/Description = 'Zone Cooling setpoint'
57 |
58 | ## Sensor Drivers ##
59 | [/monitoring]
60 | type = Collection
61 | Metadata/System = Monitoring
62 | Metadata/Role = Monitoring
63 | Metadata/Floor = 1
64 | Metadata/Name = 1st Floor Sensors
65 |
66 | [/monitoring/airtemphumidity0]
67 | type = smap.drivers.sensors.virtualATHsensor.VirtualATHSensor
68 | initialtemp = 68
69 | initialhumidity = 38
70 | Metadata/Type = Sensor
71 | Metadata/Room = 1
72 | Metadata/HVACZone = Zone 1
73 |
74 | [/monitoring/airtemphumidity1]
75 | type = smap.drivers.sensors.virtualATHsensor.VirtualATHSensor
76 | initialtemp = 69
77 | initialhumidity = 39
78 | Metadata/Type = Sensor
79 | Metadata/Room = 2
80 | Metadata/HVACZone = Zone 1
81 |
82 | [/monitoring/airtemphumidity2]
83 | type = smap.drivers.sensors.virtualATHsensor.VirtualATHSensor
84 | initialtemp = 70
85 | initialhumidity = 40
86 | Metadata/Type = Sensor
87 | Metadata/Room = 3
88 | Metadata/HVACZone = Zone 2
89 |
90 | ## Zone Controller ##
91 | [/zonecontroller1]
92 | type = zone1controller.AvgSensorFollowMaster
93 | subscribe/temp_sensor = Metadata/System = 'Monitoring' and Metadata/HVACZone = 'Zone 1' and Metadata/Type = 'Sensor' and Properties/UnitofMeasure = 'F'
94 | subscribe/thermostat_temp = Metadata/System = 'HVAC' and Metadata/HVACZone = 'Zone 1' and Metadata/Type = 'Sensor' and Properties/UnitofMeasure = 'F'
95 | subscribe/temp_heat = Metadata/System = 'Schedule' and Metadata/Description = 'Master Heating setpoint'
96 | subscribe/temp_cool = Metadata/System = 'Schedule' and Metadata/Description = 'Master Cooling setpoint'
97 | archiver = http://localhost:8079
98 | synchronous = True
99 | Metadata/HVACZone = Zone 1
100 |
101 | [/zonecontroller1/temp_heat]
102 | Metadata/Description = Zone Heating setpoint
103 | Metadata/System = HVAC
104 | Metadata/Type = Setpoint
105 |
106 | [/zonecontroller1/temp_cool]
107 | Metadata/Description = Zone Cooling setpoint
108 | Metadata/System = HVAC
109 | Metadata/Type = Setpoint
110 |
111 | [/zonecontroller2]
112 | type = zone2controller.FollowMaster
113 | subscribe/temp_heat = Metadata/System = 'Schedule' and Metadata/Description = 'Master Heating setpoint'
114 | subscribe/temp_cool = Metadata/System = 'Schedule' and Metadata/Description = 'Master Cooling setpoint'
115 | archiver = http://localhost:8079
116 | synchronous = True
117 | Metadata/HVACZone = Zone 2
118 |
119 | [/zonecontroller2/temp_heat]
120 | Metadata/Description = Zone Heating setpoint
121 | Metadata/System = HVAC
122 | Metadata/Type = Setpoint
123 |
124 | [/zonecontroller2/temp_cool]
125 | Metadata/Description = Zone Cooling setpoint
126 | Metadata/System = HVAC
127 | Metadata/Type = Setpoint
128 |
129 | ## Master Scheduler ##
130 | [/masterscheduler]
131 | type = smap.services.scheduler.Scheduler
132 | pollrate = 1
133 | publishrate = 1
134 | source = file:///schedules.json
135 |
136 | [/masterscheduler/temp_heat]
137 | Metadata/Description = Master Heating setpoint
138 | Metadata/System = Schedule
139 | Metadata/Type = Setpoint
140 |
141 | [/masterscheduler/temp_cool]
142 | Metadata/Description = Master Cooling setpoint
143 | Metadata/System = Schedule
144 | Metadata/Type = Setpoint
145 |
146 | [/masterscheduler/on]
147 | Metadata/Description = Master Lighting control
148 | Metadata/System = Schedule
149 | Metadata/Type = Command
150 |
--------------------------------------------------------------------------------
/zone-controller-tutorial/schedules.json:
--------------------------------------------------------------------------------
1 | {
2 | "master_schedule": {
3 | "fri": "weekday",
4 | "mon": "weekday",
5 | "sat": "weekend",
6 | "sun": "weekend",
7 | "thu": "weekday",
8 | "tue": "weekday",
9 | "wed": "weekday"
10 | },
11 | "schedules": [
12 | {
13 | "color": "#EDF1FA",
14 | "name": "weekday",
15 | "periods": [
16 | {
17 | "name": "morning",
18 | "points": [
19 | {
20 | "path": "temp_heat",
21 | "value": 72
22 | },
23 | {
24 | "path": "temp_cool",
25 | "value": 83
26 | }
27 | ],
28 | "start": "7:30"
29 | },
30 | {
31 | "name": "afternoon",
32 | "points": [
33 | {
34 | "path": "temp_heat",
35 | "value": 70
36 | },
37 | {
38 | "path": "temp_cool",
39 | "value": 80
40 | }
41 | ],
42 | "start": "13:30"
43 | },
44 | {
45 | "name": "evening",
46 | "points": [
47 | {
48 | "path": "temp_heat",
49 | "value": 50
50 | },
51 | {
52 | "path": "temp_cool",
53 | "value": 90
54 | }
55 | ],
56 | "start": "18:30"
57 | }
58 | ]
59 | },
60 | {
61 | "color": "#FAF6ED",
62 | "name": "weekend",
63 | "periods": [
64 | {
65 | "name": "morning",
66 | "points": [
67 | {
68 | "path": "temp_heat",
69 | "value": 65
70 | },
71 | {
72 | "path": "temp_cool",
73 | "value": 85
74 | }
75 | ],
76 | "start": "09:30"
77 | },
78 | {
79 | "name": "afternoon",
80 | "points": [
81 | {
82 | "path": "temp_heat",
83 | "value": 70
84 | },
85 | {
86 | "path": "temp_cool",
87 | "value": 80
88 | }
89 | ],
90 | "start": "17:30"
91 | },
92 | {
93 | "name": "evening",
94 | "points": [
95 | {
96 | "path": "temp_heat",
97 | "value": 50
98 | },
99 | {
100 | "path": "temp_cool",
101 | "value": 90
102 | }
103 | ],
104 | "start": "21:00"
105 | }
106 | ]
107 | }
108 | ]
109 | }
110 |
--------------------------------------------------------------------------------
/zone-controller-tutorial/zone1controller.py:
--------------------------------------------------------------------------------
1 | from smap.services.zonecontroller import ZoneController
2 |
3 | class AvgSensorFollowMaster(ZoneController):
4 | def setup(self, opts):
5 | ZoneController.setup(self, opts)
6 | # we want to publish the heat/cool setpoints
7 | self.add_timeseries('/temp_heat','F',data_type='double')
8 | self.add_timeseries('/temp_cool','F',data_type='double')
9 | # we want to write a special averaging function to handle the temperature sensors, so
10 | # we use add_callback to run it automatically when new sensor data comes in
11 | self.add_callback('temp_sensor', self.avg_temp, opts.get('subscribe/temp_sensor'))
12 |
13 | def step(self):
14 | # adjust heat/cool setpoints by the difference between the thermostat temperature and the
15 | # average room setpoint
16 | new_diff = self.points['thermostat_temp'] - self.points['avg_temp']
17 | self.points['temp_heat'] += new_diff
18 | self.points['temp_cool'] += new_diff
19 | self.add('/temp_heat', float(self.points['temp_heat']))
20 | self.add('/temp_cool', float(self.points['temp_cool']))
21 |
22 | def avg_temp(self, point, uuids, data):
23 | # averages the readings
24 | self.points['avg_temp'] = sum(map(lambda x: x[-1][1], data)) / float(len(data))
25 |
--------------------------------------------------------------------------------
/zone-controller-tutorial/zone2controller.py:
--------------------------------------------------------------------------------
1 | from smap.services.zonecontroller import ZoneController
2 |
3 | class FollowMaster(ZoneController):
4 | def setup(self, opts):
5 | ZoneController.setup(self, opts)
6 | self.add_timeseries('/temp_heat','F',data_type='double')
7 | self.add_timeseries('/temp_cool','F',data_type='double')
8 |
9 | def step(self):
10 | """
11 | self.points contains the most recent advertised values
12 | for 'temp_haet' and 'temp_cool' from the master scheduler.
13 | We add 5 to these, and then republish to our end points
14 | """
15 | self.add('/temp_heat', float(self.points['temp_heat']) + 5)
16 | self.add('/temp_cool', float(self.points['temp_cool']) + 5)
17 |
--------------------------------------------------------------------------------