Histories in BAC0

Histories in BAC0 were first introduced as the result of every reading the software made on each points, to keep trace of what was going on. It is different from the BACnet TrendLog object that may be configured in a device to keep history records in the memory of the controller.

Everytime a value is read in BAC0, the value will be stored in memory as what will be called from here : history.

TrendLog will also being accessible but for now, let’s focus on BAC0’s histories.

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['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/].

History Size

By default, BAC0 doesn’t provide a history_size per points (number of records). But it could be useful in certain cases when the script will run for a long period of time and you want to keep control over memory

dev = BAC0.device('2:4',4,bacnet,history_size=2)
# or after...
dev.update_history_size(100)
# or just on one point :
dev['point'].properties.history_size = 30

Resampling 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.

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'),
        '102_ZN-SP' : local102['ZN-SP'].history.resample('1min'),
        '104_ZN-T' : local104['ZN-T'].history.resample('1min'),
        '104_ZN-SP' : local104['ZN-SP'].history.resample('1min'),
        '105_ZN-T' : local105['ZN-T'].history.resample('1min'),
        '105_ZN-SP' : local105['ZN-SP'].history.resample('1min'),
        '106_ZN-T' : local106['ZN-T'].history.resample('1min'),
        '106_ZN-SP' : local106['ZN-SP'].history.resample('1min'),
        '109_ZN-T' : local109['ZN-T'].history.resample('1min'),
        '109_ZN-SP' : local109['ZN-SP'].history.resample('1min'),
        '110_ZN-T' : local110['ZN-T'].history.resample('1min'),
        '110_ZN-SP' : local110['ZN-SP'].history.resample('1min'),
       }
# 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
temp_pieces['Erreur_102'] = temp_pieces['102_ZN-T'] - temp_pieces['102_ZN-SP']
temp_pieces['Erreur_104'] = temp_pieces['104_ZN-T'] - temp_pieces['104_ZN-SP']
temp_pieces['Erreur_105'] = temp_pieces['105_ZN-T'] - temp_pieces['105_ZN-SP']
temp_pieces['Erreur_106'] = temp_pieces['106_ZN-T'] - temp_pieces['106_ZN-SP']
temp_pieces['Erreur_109'] = temp_pieces['109_ZN-T'] - temp_pieces['109_ZN-SP']
temp_pieces['Erreur_110'] = temp_pieces['110_ZN-T'] - temp_pieces['110_ZN-SP']

# 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()

TrendLog

BACnet TrendLog is an object that a controller may implement. Once configured, it’ll keep in memory a certain number of records for one particular point. Often though, as the controller memory may be limited, we’ll have access to a limited number of records and the interval between each record may have been configured long enough to optimize the time frame coverage of the records. For example, taking records every 10 minutes instead of every 2 minutes will give 5 times longer time interval. Using a total of 2000 number of records, it will represent almost 14 days (10 min) vs less than 3 days (2 min).

BAC0 supports the reading of TrendLog objects and will convert the records to pandas Series if possible. This will allow to use pandas syntax over histories and make analysis easier for the user.

TrendLog objects have also been made compatible with the format required to be added as Bokeh Chart in the web interface.:

# Manually create a TrendLog
import BAC0
bacnet = BAC0.connect()
device = BAC0.device('2:5',5,bacnet)

# Given a TrendLog object at address 1 for device
trend = BAC0.TrendLog(1,device)

# Retrieve pandas serie
trend.history

# Adding this object to live trends
trend.chart()