Skip to content

Commit

Permalink
Merge af9b402 into cae83a4
Browse files Browse the repository at this point in the history
  • Loading branch information
kjlockhart committed Mar 30, 2017
2 parents cae83a4 + af9b402 commit 264cbb7
Show file tree
Hide file tree
Showing 14 changed files with 257 additions and 170 deletions.
59 changes: 34 additions & 25 deletions doc/source/README.rst
Original file line number Diff line number Diff line change
@@ -1,41 +1,50 @@
BAC0 |build-status| |coverage| |docs|
=====================================
BAC0 is a Python 3 (3.4 and over) scripting application that uses BACpypes_ to process BACnet messages on a IP network.
This library brings out simple commands to browse a BACnet network, read properties from BACnet devices or write to them.
BAC0 is a Python 3 (3.4 and later) scripting application that uses BACpypes_ to process BACnet messages on a IP network.
This library exposes simple functions to browse the BACnet network, and read & write properties from the BACnet devices.

Python is a simple language to learn and a very powerful tool for data processing. Coupled to BACnet, it becomes a great
tool to test devices an interact with controllers.
Python is a simple language to learn and a very powerful tool for data processing. Coupled with BACnet,
it becomes a **great tool for testing BACnet** and interacting with BACnet controllers.

BAC0 takes its name from the default IP port used by BACnet/IP communication which is port 47808. In hexadecimal, it's written 0xBAC0.
BAC0 takes its name from the default IP port assigned to BACnet/IP communications - port (47808 decimal, 0xBAC0
hexadecimal).

Test driven development (TDD) for DDC controls
==============================================
BAC0 is made for building automation system (BAS) programmers. Controllers used in this field are commonly called DDC Controllers (Direct Digital Control).
BAC0 is intended for assisting BAS (building automation system) programmers, with configuring, testing, and
commissioning of BAS Controllers - often called DDC (Direct Digital Control) Controllers.

Typical controllers can be programmed in different ways, depending on the manufacturer selling them (block programming, basic "kinda" scripts, C code, etc...).
BAC0, is a unified way, using Python language and BACnet/IP communication, to interact with those controllers once their sequence is built.
Typically BAS controllers are programmed using vendor specific tools, and vendor specific programming languages
to define how they will operate. The resulting programs are the controller's **sequence of operations**.
Different vendors, use different methods to define these sequences - including 'block programming',
'graphical programming', and 'text/procedural programming'.

BAC0 allows users to simply test an application even if sensors are not connected to the controller. Using the out_of_service
property, it's easy to write a value to the input so the controller will think an input is conencted.
BAC0 provides a generalized (vendor-independent) means to programmatically interact with the BAS controllers,
via Python and the BACnet/IP communication protocol. BAC0 allows users to test a controller even if no sensors
or outputs are connected to the controller. Thanks to the BACnet **out_of_service** property, it is easy to write
a value to the input pin(s) so the controller believes a sensor is connected, and its **operating sequence** will
respond accordingly. Likewise, it is possible to write a value to an output pin(s) to operate any connected
equipment (often called a **manual command** or to **override an output**). In fact, BAC0 exposes a great many of a
controller's BACnet Objects and Object Properties, enabling automated interactions using Python; as a simple
scripting language, a powerful testing & commissioning tool, or a general application development environment.

It's also possible to do "manual commands" on output (often called overrides). In fact, every variable is exposed and seen by BAC0 and
it's possible to interact with them using a simple scripting language or a complete unit test suite (like Pytest).
Using BAC0 as test tool, makes automated BAS testing quick, reliable, and repeatable. Compare this to
the BAS vendor provided tools, which only allow the controllers to be programmed, and where all the
testing must be done manually. Very slow. Very error-prone. Now you can write your tests and re-run them
as often as you need.

Without a program like BAC0, you can rely on your DDC programming tool... but it is often slow and
every test must be done manually. That means also that if you want to repeat the tests, the more complicated they are, the less chance you'll be able to do so.

Now you can write your test and run them as often as you want. We'll show you how it works.
Better commissioning thanks to automatic data logging
=====================================================
As you will discover, when you define a controller in BAC0, you automatically get **historical data logs** for
every variable in the controller. All I/O points are trended every 10 seconds (by default). Meaning
you can do data analysis of the controller's operation while you're doing your basic **sequence testing**.
This gives you a high-level overview of the controller's performance while highlighting trouble areas really fast.

Better start-up with data acquisition
=====================================
As you will discover, when you define a controller in BAC0, you will get access to historical data of
every variables in the controllers. Every points are trended every 10 seconds by default. Which means
that you can do data analysis on everything while you're doing your startup. It allows to see performances and
trouble really fast.

This make BAC0 not only a good tool to test your sequence while your in the office.
But also a really good tool to assist your startup, test and balancing. Using Jupyter Notebook, you'll
even be able to create nice looking report right from your code.
BAC0 is not only a good tool for testing your **sequence of operations** while in-the-office.
It is also a really good tool to assist on-site. Use it to test controller startup, operation, and balancing
in-the-field. When combined with Jupyter Notebook, you are even able to create nice looking reports right from your
automation code.


.. |build-status| image:: https://travis-ci.org/ChristianTremblay/BAC0.svg?branch=master
Expand Down
92 changes: 67 additions & 25 deletions doc/source/connect.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,39 +27,81 @@ To read a point, simply ask for it using bracket syntax::

mycontroller['point_name']

Write to a point
----------------
simple write

Writing to Points
-----------------

Simple write
************
If point is a analogValue, binaryValue or a multistateValue BAC0 will write to the default
priority ::
If point is a value:

* analogValue (AV)
* binaryValue (BV)
* multistateValue (MV)

You can change its value with a simple assignment. BAC0 will write the value to the object's
**presentValue** at the default priority.::

mycontroller['point_name'] = 23

.. image:: images/AV_write.png


Write to an Output (Override)
*****************************
If the point is an output:

* analogOutput (AO)
* binaryOutput (BO)
* multistateOutput (MO)

You can change its value with a simple assignment. BAC0 will write the value to the object's
**presentValue** (a.k.a override it) at priority 8 (Manual Operator).::

mycontroller['outputName'] = 45

.. image:: images/AO_write.png


Write to an Input (simulate)
****************************
If the point is an input:

* analogInput (AI)
* binaryOutput (BO)
* multistateOutput (MO)

You can change its value with a simple assigment, thus overriding any external value it is
reading and simulating a different sensor reading. The override occurs because
BAC0 sets the point's **out_of_service** (On) and then writes to the point's **presentValue**.

mycontroller['inputName'] = <simulated value>

mycontroller['Temperature'] = 23.5 # overiding actual reading of 18.8 C

mycontroller['point_name'] = 10
.. image:: images/AI_override.png

Relinquish default
******************
If you must write to relinquish default, it must be said explicitly ::

mycontroller['pointToChange'].default(10)
Releasing an Input simulation or Output override
*************************************************

This distinction is made because of the sensibility to multiple writes to those values.
Thoses are often written to EEPROM directly and have a ±250000 write cycle.
To return control of an Input or Output back to the controller, it needs to be released.
Releasing a point returns it automatic control. This is done with an assignment to 'auto'.::

Override
*********
If the point is a output, BAC0 will override it (@priority 8)::
mycontroller['pointToRelease'] = 'auto'

mycontroller['outputName'] = 100
.. image:: images/AI_auto.png
.. image:: images/AO_auto.png

simulate (out_of_service)
**************************
If the point is an input, BAC0 will set the out_of_service flag to On and write
to the present value (which will simulate it)::

Setting a Relinquish_Default
****************************
When a point (with a priority array) is released of all override commands, it takes on the value
of its **Relinquish_Default**. [BACnet clause 12.4.12] If you wish to set this default value,
you may with this command::

mycontroller['inputName'] = 34
mycontroller['pointToChange'].default(<value>)
mycontroller['Output'].default(75)

Releasing a simulation or an override
**************************************
Simply affect 'auto' to the point ::
.. image:: images/AO_set_default.png

mycontroller['pointToRelease'] = 'auto'
69 changes: 43 additions & 26 deletions doc/source/histories.rst
Original file line number Diff line number Diff line change
@@ -1,34 +1,51 @@
Histories in BAC0
====================
As said, every points get saved in a pandas Series every 10 seconds by default.
This means that you can look for historical data from the moment you connect to a device.
Access a historyTable::

BAC0 uses the Python Data Analysis library **pandas** [http://pandas.pydata.org/] to
maintain histories of point values over time. All points are saved by BAC0 in a **pandas**
Series every 10 seconds (by default). This means you will automatically have historical data
from the moment you connect to a BACnet device.

Access the contents of a point's history is very simple.::
controller['nvoAI1'].history

Result example ::

controller['nvoAI1'].history
Out[8]:
2015-09-20 21:41:37.093985 21.740000
2015-09-20 21:42:23.672387 21.790001
2015-09-20 21:42:34.358801 21.790001
2015-09-20 21:42:45.841596 21.790001
2015-09-20 21:42:56.308144 21.790001
2015-09-20 21:43:06.897034 21.790001
2015-09-20 21:43:17.593321 21.790001
2015-09-20 21:43:28.087180 21.790001
2015-09-20 21:43:38.597702 21.790001
2015-09-20 21:43:48.815317 21.790001
2015-09-20 21:44:00.353144 21.790001
2015-09-20 21:44:10.871324 21.790001
controller['pointName'].history

Example ::

controller['Temperature'].history
2017-03-30 12:50:46.514947 19.632507
2017-03-30 12:50:56.932325 19.632507
2017-03-30 12:51:07.336394 19.632507
2017-03-30 12:51:17.705131 19.632507
2017-03-30 12:51:28.111724 19.632507
2017-03-30 12:51:38.497451 19.632507
2017-03-30 12:51:48.874454 19.632507
2017-03-30 12:51:59.254916 19.632507
2017-03-30 12:52:09.757253 19.536366
2017-03-30 12:52:20.204171 19.536366
2017-03-30 12:52:30.593838 19.536366
2017-03-30 12:52:40.421532 19.536366
dtype: float64


.. note::
**pandas** is an extensive data analysis tool, with a vast array of data manipulation operators.
Exploring these is beyond the scope of this documentation. Instead we refer you to this
cheat sheet [https://github.com/pandas-dev/pandas/blob/master/doc/cheatsheet/Pandas_Cheat_Sheet.pdf] and
the pandas website [http://pandas.pydata.org/].


Resampling data
---------------
As those are pandas DataFrame or Series, you can resample data::
One common task associated with point histories is preparing it for use with other tools.
This usually involves (as a first step) changing the frequency of the data samples - called
**resampling** in pandas terminology.

# This piece of code show what can of operation can be made using Pandas
Since the point histories are standard pandas data structures (DataFrames, and Series), you can
manipulate the data with pandas operators, as follows.::

# code snipet showing use of pandas operations on a BAC0 point history.
# Resample (consider the mean over a period of 1 min)
tempPieces = {
'102_ZN-T' : local102['ZN-T'].history.resample('1min'),
Expand All @@ -44,7 +61,7 @@ As those are pandas DataFrame or Series, you can resample data::
'110_ZN-T' : local110['ZN-T'].history.resample('1min'),
'110_ZN-SP' : local110['ZN-SP'].history.resample('1min'),
}
# Remove any NaN value
# Remove any NaN values
temp_pieces = pd.DataFrame(tempPieces).fillna(method = 'ffill').fillna(method = 'bfill')
# Create a new column in the DataFrame which is the error between setpoint and temperature
Expand All @@ -57,4 +74,4 @@ As those are pandas DataFrame or Series, you can resample data::

# Create a new dataframe from results and show some statistics
temp_erreurs = temp_pieces[['Erreur_102', 'Erreur_104', 'Erreur_105', 'Erreur_106', 'Erreur_109', 'Erreur_110']]
temp_erreurs.describe()
temp_erreurs.describe()
Binary file added doc/source/images/AI_auto.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/source/images/AI_override.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/source/images/AO_auto.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/source/images/AO_set_default.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/source/images/AO_write.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/source/images/AV_write.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 11 additions & 5 deletions doc/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to BACØ's documentation!
================================
Welcome to BACØ - BACnet Test Tool
==================================

.. include:: README.rst


Expand All @@ -22,9 +23,14 @@ Table of contents
tests
pytest

Modules documentation
=====================
.. include:: BAC0.rst

Developer documentation
=======================
.. toctree::
:maxdepth: 2

BAC0


Index and search tool
======================
Expand Down
46 changes: 19 additions & 27 deletions doc/source/pytest.rst
Original file line number Diff line number Diff line change
@@ -1,32 +1,24 @@
Using Pytest_
=============
Pytest_ is a "a mature full-featured Python testing tool".
It allows the creation of test files that can be called by a command line script.
It's then possible to create different test, all in there own file start
the process and work on something else while tests are running.
Pytest_ [https://docs.pytest.org/en/latest/] is a "a mature full-featured Python testing tool".
It allows the creation of test files that can be called by a command line script,
and run automatically while you work on something else.

Here an example of a Pytest_ module that could be created.
For more details, please refer Pytest's documentation.

Please note that Pytest_ on its own is a very complete solution. For more documentation about
it, please refer to the documentation of the project.

I'll simply describe minimal feature I present in the example.

Some basic stuff before we begin
--------------------------------
Pytest is a very simple testing tool. The default unit test tool for python is called
unittest. Based on Java xUnit, it's more formal, uses a lot of different functions and classes...
It can easily become too much for the needs of testing DDC controllers.

Pytest uses only simple `assert` command, inside functions defined in a module.
Pytest is a very simple testing tool. While, the default unit test tool for python is
**unittest** (which is more formal and has more features); unittest can easily become
too much for the needs of testing DDC controllers.

Pytest also allows the usage of "fixtures" which are little snippets of code that will be
used to prepare what's needed for the test, then finalize the process when it's over. In unittest,
thoses functions are called setUp and tearDown.
Pytest uses only simple the `assert` command, and locally defined functions.
It also allows the usage of "fixtures" which are little snippets of code that prepare things
prior to the test (setUp), then finalize things when the test is over (tearDown).

In the example module I'll show you, I'm using fixtures to create the BACnet connection at the beginning
of the test, making this connection valid for all the tests in the module, then closing the connection
after the tests have been done, just after having saved the controller so the histories be available.
The following example uses fixtures to establish the BACnet connection prior to the test,
and then saves the controller histories and closes the connection after the tests are done.

Example
+++++++
Expand Down Expand Up @@ -69,12 +61,12 @@ Code ::

Success result
..............
If you named you file test_mytest.py, you can just run ::
If you name the file: test_mytest.py, you can just run ::

py.test -v -s

Pytest_ will look for test files, find them and run them. Or you can define the
file you want to run ::
Pytest_ will look for the test files, find them and run them. Or you can define the
exact file you want to run ::

py.test mytestfile.py -v -s

Expand Down Expand Up @@ -117,7 +109,7 @@ Here's what it looks like ::
Failure result
..............

Here's what it looks like when a test fails ::
Here's what a test failure looks like::

============================= test session starts =============================
platform win32 -- Python 3.4.4, pytest-2.8.5, py-1.4.31, pluggy-0.3.1 -- C:\User
Expand Down Expand Up @@ -163,12 +155,12 @@ Here's what it looks like when a test fails ::
pytest_example.py:30: AssertionError
===================== 1 failed, 1 passed in 30.71 seconds =====================

I modified the test here to generate an failure if nvoAI2 is not greater than 1000.
Note: I modified the test to generate an failure - nvoAI2 cannot exceed 1000.

Conclusion
----------
Using Pytest_ is a really good way to generate test files that can be reused and modified
depending on different use cases. It's a good way to run multiple tests at once.
It will give you a concise report of every failure and tell you if tests succeeded.
It provides concise reports of every failure and tells you when your tests succeed.

.. _pytest : http://pytest.org/latest/
.. _pytest : http://pytest.org/latest/
Loading

0 comments on commit 264cbb7

Please sign in to comment.