Skip to content

Commit

Permalink
Merge pull request #110 from ChristianTremblay/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
ChristianTremblay authored Oct 2, 2018
2 parents 8ecc530 + a61f021 commit d80314f
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 37 deletions.
2 changes: 1 addition & 1 deletion BAC0/infos.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@
__email__ = '[email protected]'
__url__ = 'https://github.com/ChristianTremblay/BAC0'
__download_url__ = 'https://github.com/ChristianTremblay/BAC0/archive/master.zip'
__version__ = '0.99.942'
__version__ = '0.99.943'
__license__ = 'LGPLv3'
62 changes: 27 additions & 35 deletions BAC0/sql/sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,29 +30,28 @@

#------------------------------------------------------------------------------


class SQLMixin(object):
"""
Use SQL to persist a device's contents. By saving the device contents to an SQL
database, you can work with the device's data while offline, or while the device
is not available.
"""
def _read_from_sql(self,request,db_name):

def _read_from_sql(self, request, db_name):
"""
Using the contextlib, I hope to close the connection to database when
not in use
"""
with contextlib.closing(sqlite3.connect('{}.db'.format(db_name))) as con:
return sql.read_sql(sql=request, con=con)

return sql.read_sql(sql=request, con=con)

def dev_properties_df(self):
dic = self.properties.asdict.copy()
dic.pop('network', None)
dic.pop('pss', None)
return dic



def points_properties_df(self):
"""
Return a dictionary of point/point_properties in preparation for storage in SQL.
Expand All @@ -69,23 +68,23 @@ def points_properties_df(self):
df = pd.DataFrame(pprops)
return df


def backup_histories_df(self):
"""
Build a dataframe of the point histories
"""
backup = {}
for point in self.points:
if point.history.dtypes == object:
backup[point.properties.name] = point.history.replace(['inactive', 'active'], [0, 1]).resample('1s').mean()
backup[point.properties.name] = point.history.replace(
['inactive', 'active'], [0, 1]).resample('1s').mean()
else:
backup[point.properties.name] = point.history.resample('1s').mean()
backup[point.properties.name] = point.history.resample(
'1s').mean()

df = pd.DataFrame(dict([ (k,pd.Series(v)) for k,v in backup.items() ]))
df = pd.DataFrame(dict([(k, pd.Series(v)) for k, v in backup.items()]))
return df.fillna(method='ffill')


def save(self, filename = None):
def save(self, filename=None):
"""
Save the point histories to sqlite3 database.
Save the device object properties to a pickle file so the device can be reloaded.
Expand All @@ -96,71 +95,64 @@ def save(self, filename = None):
self.properties.db_name = filename
else:
self.properties.db_name = '{}'.format(self.properties.name)

# Does file exist? If so, append data
if os.path.isfile('{}.db'.format(self.properties.db_name)):
db = sqlite3.connect('{}.db'.format(self.properties.db_name))
his = sql.read_sql('select * from "{}"'.format('history'), db)
his = self._read_from_sql(
'select * from "{}"'.format('history'), self.properties.db_name)
his.index = his['index'].apply(Timestamp)
last = his.index[-1]
df_to_backup = self.backup_histories_df()[last:]
db.close()


else:
self._log.debug('Creating a new backup database')
df_to_backup = self.backup_histories_df()

# DataFrames that will be saved to SQL
with contextlib.closing(sqlite3.connect('{}.db'.format(db_name))) as con:
return sql.to_sql(df_to_backup, name='history', con=con,
index_label = 'index', index = True, if_exists = 'append')
with contextlib.closing(sqlite3.connect('{}.db'.format(self.properties.db_name))) as con:
sql.to_sql(df_to_backup, name='history', con=con,
index_label='index', index=True, if_exists='append')

# Saving other properties to a pickle file...
# Saving other properties to a pickle file...
prop_backup = {}
prop_backup['device'] = self.dev_properties_df()
prop_backup['points'] = self.points_properties_df()
with open( "{}.bin".format(self.properties.db_name), "wb" ) as file:
with open("{}.bin".format(self.properties.db_name), "wb") as file:
pickle.dump(prop_backup, file)

self._log.info('Device saved to {}.db'.format(self.properties.db_name))


def points_from_sql(self, db_name):
"""
Retrieve point list from SQL database
"""
points = self._read_from_sql("SELECT * FROM history;", db_name)
points = self._read_from_sql("SELECT * FROM history;", db_name)
return list(points.columns.values)[1:]


def his_from_sql(self, db_name, point):
"""
Retrive point histories from SQL database
"""
his = self._read_from_sql('select * from "%s"' % 'history', db_name)
his = self._read_from_sql('select * from "%s"' % 'history', db_name)
his.index = his['index'].apply(Timestamp)
return his.set_index('index')[point]


def value_from_sql(self, db_name, point):
"""
Take last known value as the value
"""
return self.his_from_sql(db_name, point).last_valid_index()

return self.his_from_sql(db_name, point).last_valid_index()

def read_point_prop(self, device_name, point):
"""
Points properties retrieved from pickle
"""
with open( "%s.bin" % device_name, "rb" ) as file:
with open("%s.bin" % device_name, "rb") as file:
return pickle.load(file)['points'][point]


def read_dev_prop(self, device_name):
"""
Device properties retrieved from pickle
"""
with open( "{}.bin".format(device_name), "rb" ) as file:
return pickle.load(file)['device']

with open("{}.bin".format(device_name), "rb") as file:
return pickle.load(file)['device']
29 changes: 28 additions & 1 deletion tests/test_WithFakeDevice.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@

import pytest
import BAC0
import os.path
import os

from BAC0.core.devices.create_objects import create_AV, create_MV, create_BV


@pytest.fixture(scope='session')
def network_and_devices():
bacnet = BAC0.lite()
Expand All @@ -31,6 +34,11 @@ def network_and_devices():
test_device = BAC0.device('{}:47809'.format(ip), boid, bacnet)

yield (bacnet, device_app, test_device)

# Delete db and bin files created
# os.remove('{}.db'.format(test_device.properties.db_name))
# os.remove('{}.bin'.format(test_device.properties.db_name))

# Close when done
test_device.disconnect()
bacnet.disconnect()
Expand All @@ -41,10 +49,29 @@ def test_ReadAV(network_and_devices):
bacnet, device_app, test_device = network_and_devices
assert (test_device['av'] - 99.90) < 0.01


def test_ReadMV(network_and_devices):
bacnet, device_app, test_device = network_and_devices
assert test_device['mv'].value == 1



def test_ReadBV(network_and_devices):
bacnet, device_app, test_device = network_and_devices
assert test_device['bv'].value == 'active'


def test_SaveToSQL(network_and_devices):
bacnet, device_app, test_device = network_and_devices
test_device.save()

assert os.path.isfile('{}.db'.format(test_device.properties.db_name))
assert os.path.isfile('{}.bin'.format(test_device.properties.db_name))


def test_LoadFromSQL(network_and_devices):
bacnet, device_app, test_device = network_and_devices
file = '{}.db'.format(test_device.properties.db_name)
bck_dev = BAC0.device(from_backup=file)
assert bck_dev.properties.objects_list == test_device.properties.objects_list

assert (bck_dev['av'].history[-1:].iloc[0] - 99.9) < 0.01

0 comments on commit d80314f

Please sign in to comment.