diff --git a/.project b/.project new file mode 100644 index 0000000..0a16233 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + pyhaystack + + + + + + org.python.pydev.PyDevBuilder + + + + + + org.python.pydev.pythonNature + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..a3b0732 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,3 @@ +#Tue Sep 02 19:45:01 EDT 2014 +eclipse.preferences.version=1 +encoding/pyhaystack.py=utf-8 diff --git a/build/lib/pyhaystack.py b/build/lib/pyhaystack.py index 4a0a6c2..0581b67 100644 --- a/build/lib/pyhaystack.py +++ b/build/lib/pyhaystack.py @@ -1,230 +1,253 @@ -#!python -# -*- coding: utf-8 -*- -""" -File : pyhaystack.py (2.x) -This module allow a connection to a haystack server -Feautures provided allow user to fetch data from the server and eventually, to post to it. - -See http://www.project-haystack.org for more details - -Project Haystack is an open source initiative to streamline working with data from the Internet of Things. We standardize semantic data models and web services with the goal of making it easier to unlock value from the vast quantity of data being generated by the smart devices that permeate our homes, buildings, factories, and cities. Applications include automation, control, energy, HVAC, lighting, and other environmental systems. - -""" -__author__ = 'Christian Tremblay' -__version__ = '1.2' -__license__ = 'AFL' - -import requests -import json -import pandas as pd -import matplotlib.pyplot as plt -import csv -import re,datetime - - -class HaystackConnection(): - """ - Abstact class / Make a connection object to haystack server using requests module - A class must be made for different type of server. See NiagaraAXConnection(HaystackConnection) - """ - def __init__(self, url, username, password): - """ - Set local variables - Open a session object that will be used for connection, with keep-alive feature - """ - self.baseURL = url - self.queryURL = '' - self.USERNAME = username - self.PASSWORD = password - self.COOKIE = '' - self.isConnected = False - self.s = requests.Session() - def authenticate(self): - """ - This function must be overridden by specific server connection to fit particular needs (urls, other conditions) - """ - pass - def getJson(self,urlToGet): - """ - Helper for GET request. Retrieve information as json string objects - """ - if self.isConnected: - try: - req = self.s.get(self.queryURL + urlToGet, headers={'accept': 'application/json; charset=utf-8'}) - #print 'GET : %s | url : %s' % (req.text, urlToGet) - return json.loads(req.text) - except requests.exceptions.RequestException as e: - print 'Request GET error : %s' % e - #else: - #print 'Session not connected to server, cannot make request' - else: - print 'Session not connected to server, cannot make request' - - def getZinc(self,urlToGet): - """ - Helper for GET request. Retrieve information as json string objects - """ - if self.isConnected: - try: - req = self.s.get(self.queryURL + urlToGet, headers={'accept': 'text/plain; charset=utf-8'}) - #print 'GET : %s | url : %s' % (req.text, urlToGet) - return (req.text) - except requests.exceptions.RequestException as e: - print 'Request GET error : %s' % e - else: - print 'Session not connected to server, cannot make request' - - def postRequest(self,url,headers={'token':''}): - """ - Helper for POST request - """ - try: - req = self.s.post(url, params=headers,auth=(self.USERNAME, self.PASSWORD)) - #print 'POST : %s | url : %s | headers : %s | auth : %s' % (req, url, headers,self.USERNAME) Gives a 404 response but a connection ???? - except requests.exceptions.RequestException as e: # This is the correct syntax - print 'Request POST error : %s' % e - - def setHistoriesList(self): - """ - This function retrieves every histories in the server and returns a list of id - """ - print 'Retrieving list of histories (trends) in server, please wait...' - self.hisList = Histories(self) - print 'Done, use getHistoriesList to check for trends' - - def getHistoriesList(self): - return self.hisList.getListofId() - - def getHistory(self, hisId, dateTimeRange='today'): - return HisRecord(self,hisId,dateTimeRange) - - - -class NiagaraAXConnection(HaystackConnection): - """ - This class connects to NiagaraAX and fetch haystack servlet - A session is open and authentication will persist - """ - def __init__(self,url,username,password): - """ - Define Niagara AX specific local variables : url - Calls the authenticate function - """ - HaystackConnection.__init__(self,url,username,password) - self.loginURL = self.baseURL + "login/" - self.queryURL = self.baseURL + "haystack/" - self.requestAbout = "about" - self.authenticate() - - def authenticate(self): - """ - Login to the server - Get the cookie from the server, configure headers, make a POST request with credential informations. - When connected, ask the haystack for "about" information and print connection information - """ - print 'pyhaystack %s | Authentication to %s' % (__version__,self.loginURL) - try: - self.COOKIE = self.s.get(self.loginURL).cookies - except requests.exceptions.RequestException as e: - print 'Problem connecting to server : %s' % e - - #Version 3.8 gives a cookie - if self.COOKIE: - self.COOKIEPOSTFIX = self.COOKIE['niagara_session'] - self.headers = {'cookiePostfix' : self.COOKIEPOSTFIX - } - self.headers = {'token':'', - 'scheme':'cookieDigest', - 'absPathBase':'/', - 'content-type':'application/x-niagara-login-support', - 'Referer':self.baseURL+'login/', - 'accept':'application/json; charset=utf-8' - } - self.auth = self.postRequest(self.loginURL,self.headers) - self.isConnected = True - self.about = self.getJson(self.requestAbout) - self.serverName = self.about['rows'][0]['serverName'] - self.haystackVersion = self.about['rows'][0]['haystackVersion'] - self.axVersion = self.about['rows'][0]['productVersion'] - print 'Connection made with haystack on %s (%s) running haystack version %s' %(self.serverName,self.axVersion,self.haystackVersion) - self.setHistoriesList() - #Maybe a version lower than 3.8 without cookie - #else: - # - # print "Not connected, it's over now" - # self.isConnected = False - -class Histories(): - """ - This class gathers every histories on the Jace - """ - def __init__(self, session, format='json'): - self.hisListName = [] - self.hisListId = [] - requestHistories = "read?filter=his" - if format == 'json': - histories = session.getJson(requestHistories) - #prettyprint(histories) - for each in histories['rows']: - self.hisListName.append(each['id'].split(' ')[1]) - self.hisListId.append(each['id'].split(' ')[0]) - elif format == 'zinc': - histories = session.getZinc(requestHistories) - his_to_csv = histories.replace(u'\xb0C','').split('\n')[2:] - reader = csv.reader(his_to_csv) - for lines in reader: - self.hisListName.append(lines[9].split(' ')[1]) - print 'Found %s, adding to list' % lines[9].split(' ')[0] - self.hisListId.append(lines[9].split(' ')[0]) - - def getListofId(self): - return self.hisListId - -class UnknownHistoryType(Exception): - """ - Utility Exception class needed when dealing with non-standard histories - Analyse_zone for example wich is a custom historical trend - """ - pass - -class HisRecord(): - """ - This class is a single record - - hisId is the haystack Id of the trend - - data is created as DataFrame to be used directly in Pandas - """ - def __init__(self,session,hisId,dateTimeRange): - """ - GET data from server and fill this object with historical info - """ - self.hisId = hisId - self.jsonHis = session.getJson('hisRead?id='+self.hisId+'&range='+dateTimeRange) - # Convert DateTime / Actually TZ is not taken... - for eachRows in self.jsonHis['rows']: - eachRows['ts'] = datetime.datetime(*map(int, re.split('[^\d]', eachRows['ts'].split(' ')[0])[:-2])) - if isfloat(float(eachRows['val'])): - eachRows['val'] = float(eachRows['val']) - try: - self.data = pd.DataFrame(self.jsonHis['rows'],columns=['ts','val']) - self.data.set_index('ts',inplace=True) - #print '%s added to list' % self.hisId - except Exception: - print '%s is an Unknown history type' % self.hisId - - def plot(self): - """ - Draw a graph of the DataFrame - """ - self.data.plot() - -def isfloat(value): - try: - float(value) - return True - except ValueError: - return False -def prettyprint(jsonData): - """ - Pretty print json object - """ +#!python +# -*- coding: utf-8 -*- +""" +File : pyhaystack.py (2.x) +This module allow a connection to a haystack server +Feautures provided allow user to fetch data from the server and eventually, to post to it. + +See http://www.project-haystack.org for more details + +Project Haystack is an open source initiative to streamline working with data from the Internet of Things. We standardize semantic data models and web services with the goal of making it easier to unlock value from the vast quantity of data being generated by the smart devices that permeate our homes, buildings, factories, and cities. Applications include automation, control, energy, HVAC, lighting, and other environmental systems. + +""" +__author__ = 'Christian Tremblay' +__version__ = '0.25' +__license__ = 'AFL' + +import requests +import json +import pandas as pd +import matplotlib.pyplot as plt +import csv +import re,datetime + + +class HaystackConnection(): + """ + Abstact class / Make a connection object to haystack server using requests module + A class must be made for different type of server. See NiagaraAXConnection(HaystackConnection) + """ + def __init__(self, url, username, password): + """ + Set local variables + Open a session object that will be used for connection, with keep-alive feature + baseURL : http://XX.XX.XX.XX/ - Server URL + queryURL : ex. for nhaystack = baseURL+haystack = http://XX.XX.XX.XX/haystack + USERNAME : used for login + PASSWORD : used for login + COOKIE : for persistent login + isConnected : flag to be used for connection related task (don't try if not connected...) + s : requests.Session() object + filteredList : List of histories created by getFilteredHistories + """ + self.baseURL = url + self.queryURL = '' + self.USERNAME = username + self.PASSWORD = password + self.COOKIE = '' + self.isConnected = False + self.s = requests.Session() + self._filteredList = [] + def authenticate(self): + """ + This function must be overridden by specific server connection to fit particular needs (urls, other conditions) + """ + pass + def getJson(self,urlToGet): + """ + Helper for GET request. Retrieve information as json string objects + """ + if self.isConnected: + try: + req = self.s.get(self.queryURL + urlToGet, headers={'accept': 'application/json; charset=utf-8'}) + #print 'GET : %s | url : %s' % (req.text, urlToGet) + return json.loads(req.text) + except requests.exceptions.RequestException as e: + print 'Request GET error : %s' % e + #else: + #print 'Session not connected to server, cannot make request' + else: + print 'Session not connected to server, cannot make request' + + def getZinc(self,urlToGet): + """ + Helper for GET request. Retrieve information as json string objects + """ + if self.isConnected: + try: + req = self.s.get(self.queryURL + urlToGet, headers={'accept': 'text/plain; charset=utf-8'}) + #print 'GET : %s | url : %s' % (req.text, urlToGet) + return (req.text) + except requests.exceptions.RequestException as e: + print 'Request GET error : %s' % e + else: + print 'Session not connected to server, cannot make request' + + def postRequest(self,url,headers={'token':''}): + """ + Helper for POST request + """ + try: + req = self.s.post(url, params=headers,auth=(self.USERNAME, self.PASSWORD)) + #print 'POST : %s | url : %s | headers : %s | auth : %s' % (req, url, headers,self.USERNAME) Gives a 404 response but a connection ???? + except requests.exceptions.RequestException as e: # This is the correct syntax + print 'Request POST error : %s' % e + + def setHistoriesList(self): + """ + This function retrieves every histories in the server and returns a list of id + """ + print 'Retrieving list of histories (trends) in server, please wait...' + self.allHistories = Histories(self) + print 'Done, use getHistoriesList to check for trends' + + def getHistoriesList(self): + return self.allHistories.getListofIdsAndNames() + + def getFilteredHistoriesListWithData(self,regexfilter,dateTimeRange='today'): + """ + This method returns a list of history record based on a filter on all histories of the server + """ + self._filteredList = [] # Empty list + self._his = {'name':'', + 'id':'', + 'data':''} + for eachHistory in self.allHistories.getListofIdsAndNames(): + if re.search(re.compile(regexfilter, re.IGNORECASE), eachHistory['name']): + print 'Adding %s to recordList' % eachHistory['name'] + self._his['name'] = eachHistory['name'] + self._his['data'] = HisRecord(self,eachHistory['id'],dateTimeRange) + self._filteredList.append(self._his.copy()) + return self._filteredList + + + +class NiagaraAXConnection(HaystackConnection): + """ + This class connects to NiagaraAX and fetch haystack servlet + A session is open and authentication will persist + """ + def __init__(self,url,username,password): + """ + Define Niagara AX specific local variables : url + Calls the authenticate function + """ + HaystackConnection.__init__(self,url,username,password) + self.loginURL = self.baseURL + "login/" + self.queryURL = self.baseURL + "haystack/" + self.requestAbout = "about" + self.authenticate() + + def authenticate(self): + """ + Login to the server + Get the cookie from the server, configure headers, make a POST request with credential informations. + When connected, ask the haystack for "about" information and print connection information + """ + print 'pyhaystack %s | Authentication to %s' % (__version__,self.loginURL) + try: + self.COOKIE = self.s.get(self.loginURL).cookies + except requests.exceptions.RequestException as e: + print 'Problem connecting to server : %s' % e + + #Version 3.8 gives a cookie + if self.COOKIE: + self.COOKIEPOSTFIX = self.COOKIE['niagara_session'] + self.headers = {'cookiePostfix' : self.COOKIEPOSTFIX + } + self.headers = {'token':'', + 'scheme':'cookieDigest', + 'absPathBase':'/', + 'content-type':'application/x-niagara-login-support', + 'Referer':self.baseURL+'login/', + 'accept':'application/json; charset=utf-8' + } + self.auth = self.postRequest(self.loginURL,self.headers) + #Need something here to prove connection....egtting response 500 ??? even is connected. + self.isConnected = True + if self.isConnected: + self.about = self.getJson(self.requestAbout) + self.serverName = self.about['rows'][0]['serverName'] + self.haystackVersion = self.about['rows'][0]['haystackVersion'] + self.axVersion = self.about['rows'][0]['productVersion'] + print 'Connection made with haystack on %s (%s) running haystack version %s' %(self.serverName,self.axVersion,self.haystackVersion) + self.setHistoriesList() + #Maybe a version lower than 3.8 without cookie + #else: + # + # print "Not connected, it's over now" + # self.isConnected = False + +class Histories(): + """ + This class gathers every histories on the Jace + """ + def __init__(self, session): + self._allHistories = [] + self._filteredList = [] + self._his = {'name':'', + 'id':'', + 'data':''} + + for each in session.getJson("read?filter=his")['rows']: + self._his['name'] = each['id'].split(' ')[1] + self._his['id'] = each['id'].split(' ')[0] + self._his['data'] = ''#No data right now + self._allHistories.append(self._his.copy()) + + def getListofIdsAndNames(self): + return self._allHistories + + def getDataFrameOf(self, hisId, dateTimeRange='today'): + return HisRecord(self,hisId,dateTimeRange) + + + +class UnknownHistoryType(Exception): + """ + Utility Exception class needed when dealing with non-standard histories + Analyse_zone for example wich is a custom historical trend + """ + pass + +class HisRecord(): + """ + This class is a single record + - hisId is the haystack Id of the trend + - data is created as DataFrame to be used directly in Pandas + """ + def __init__(self,session,hisId,dateTimeRange): + """ + GET data from server and fill this object with historical info + """ + self.hisId = hisId + self.jsonHis = session.getJson('hisRead?id='+self.hisId+'&range='+dateTimeRange) + # Convert DateTime / Actually TZ is not taken... + for eachRows in self.jsonHis['rows']: + eachRows['ts'] = datetime.datetime(*map(int, re.split('[^\d]', eachRows['ts'].split(' ')[0])[:-2])) + if isfloat(float(eachRows['val'])): + eachRows['val'] = float(eachRows['val']) + try: + self.data = pd.DataFrame(self.jsonHis['rows'],columns=['ts','val']) + self.data.set_index('ts',inplace=True) + #print '%s added to list' % self.hisId + except Exception: + print '%s is an Unknown history type' % self.hisId + + def plot(self): + """ + Draw a graph of the DataFrame + """ + self.data.plot() + +def isfloat(value): + try: + float(value) + return True + except ValueError: + return False +def prettyprint(jsonData): + """ + Pretty print json object + """ print json.dumps(jsonData, sort_keys=True, indent=4) \ No newline at end of file diff --git a/build/lib/pyzinc.py b/build/lib/pyzinc.py index 47c0fa4..2ce60d7 100644 --- a/build/lib/pyzinc.py +++ b/build/lib/pyzinc.py @@ -1,69 +1,69 @@ -#!python -# -*- coding: utf-8 -*- -""" -File : pyzinc.py (2.x) -Zinc parser - -See http://www.project-haystack.org for more details - -Project Haystack is an open source initiative to streamline working with data from the Internet of Things. We standardize semantic data models and web services with the goal of making it easier to unlock value from the vast quantity of data being generated by the smart devices that permeate our homes, buildings, factories, and cities. Applications include automation, control, energy, HVAC, lighting, and other environmental systems. - -""" -__author__ = 'Christian Tremblay' -__version__ = '0.02' -__license__ = 'AFL' - -import re -import pyhaystack as ph -import csv - -class Histories(): - """ - This class gathers every histories on the Jace - """ - def __init__(self, session): - self.hisList = [] - requestHistories = "read?filter=his" - his = {'name' : '', - 'id': '' - } - histories = session.getZinc(requestHistories) - his_to_csv = histories.split('\n')[2:] - #his_to_csv = histories.replace(u'\xb0','deg').split('\n')[2:] - reader = unicode_csv_reader(his_to_csv) - for lines in reader: - print lines - his['id'] = lines[4].split(' ')[0] - his['name'] = lines[4].split(' ')[1] - print 'Found %s, adding to list' % his['name'] - self.hisList.append(his) - - def getListofId(self): - return self.hisList - -class HistoryRecord(): - def __init__(self,session, zinc): - self.data = [] - self.trend = {} - for each in zinc: - self.trend['ts']=each.split(',')[0] - self.trend['val']=re.findall(r"[-+]?\d*\.\d+|\d+",each.split(',')[1]) - self.trend['unit']=re.findall(r"\W+",each.split(',')[1]) - self.data.append(self.trend) - - sefl.df = pd.DataFrame(self.data) - - def getDataFrame(self): - return df - -def unicode_csv_reader(unicode_csv_data, dialect=csv.excel, **kwargs): - # csv.py doesn't do Unicode; encode temporarily as UTF-8: - csv_reader = csv.reader(utf_8_encoder(unicode_csv_data), - dialect=dialect, **kwargs) - for row in csv_reader: - # decode UTF-8 back to Unicode, cell by cell: - yield [unicode(cell, 'utf-8') for cell in row] - -def utf_8_encoder(unicode_csv_data): - for line in unicode_csv_data: +#!python +# -*- coding: utf-8 -*- +""" +File : pyzinc.py (2.x) +Zinc parser + +See http://www.project-haystack.org for more details + +Project Haystack is an open source initiative to streamline working with data from the Internet of Things. We standardize semantic data models and web services with the goal of making it easier to unlock value from the vast quantity of data being generated by the smart devices that permeate our homes, buildings, factories, and cities. Applications include automation, control, energy, HVAC, lighting, and other environmental systems. + +""" +__author__ = 'Christian Tremblay' +__version__ = '0.02' +__license__ = 'AFL' + +import re +import pyhaystack as ph +import csv + +class Histories(): + """ + This class gathers every histories on the Jace + """ + def __init__(self, session): + self.hisList = [] + requestHistories = "read?filter=his" + his = {'name' : '', + 'id': '' + } + histories = session.getZinc(requestHistories) + his_to_csv = histories.split('\n')[2:] + #his_to_csv = histories.replace(u'\xb0','deg').split('\n')[2:] + reader = unicode_csv_reader(his_to_csv) + for lines in reader: + print lines + his['id'] = lines[4].split(' ')[0] + his['name'] = lines[4].split(' ')[1] + print 'Found %s, adding to list' % his['name'] + self.hisList.append(his) + + def getListofId(self): + return self.hisList + +class HistoryRecord(): + def __init__(self,session, zinc): + self.data = [] + self.trend = {} + for each in zinc: + self.trend['ts']=each.split(',')[0] + self.trend['val']=re.findall(r"[-+]?\d*\.\d+|\d+",each.split(',')[1]) + self.trend['unit']=re.findall(r"\W+",each.split(',')[1]) + self.data.append(self.trend) + + sefl.df = pd.DataFrame(self.data) + + def getDataFrame(self): + return df + +def unicode_csv_reader(unicode_csv_data, dialect=csv.excel, **kwargs): + # csv.py doesn't do Unicode; encode temporarily as UTF-8: + csv_reader = csv.reader(utf_8_encoder(unicode_csv_data), + dialect=dialect, **kwargs) + for row in csv_reader: + # decode UTF-8 back to Unicode, cell by cell: + yield [unicode(cell, 'utf-8') for cell in row] + +def utf_8_encoder(unicode_csv_data): + for line in unicode_csv_data: yield line.encode('utf-8') \ No newline at end of file diff --git a/pyhaystack.py b/pyhaystack.py index 4a0a6c2..ba21bca 100644 --- a/pyhaystack.py +++ b/pyhaystack.py @@ -1,230 +1,257 @@ -#!python -# -*- coding: utf-8 -*- -""" -File : pyhaystack.py (2.x) -This module allow a connection to a haystack server -Feautures provided allow user to fetch data from the server and eventually, to post to it. - -See http://www.project-haystack.org for more details - -Project Haystack is an open source initiative to streamline working with data from the Internet of Things. We standardize semantic data models and web services with the goal of making it easier to unlock value from the vast quantity of data being generated by the smart devices that permeate our homes, buildings, factories, and cities. Applications include automation, control, energy, HVAC, lighting, and other environmental systems. - -""" -__author__ = 'Christian Tremblay' -__version__ = '1.2' -__license__ = 'AFL' - -import requests -import json -import pandas as pd -import matplotlib.pyplot as plt -import csv -import re,datetime - - -class HaystackConnection(): - """ - Abstact class / Make a connection object to haystack server using requests module - A class must be made for different type of server. See NiagaraAXConnection(HaystackConnection) - """ - def __init__(self, url, username, password): - """ - Set local variables - Open a session object that will be used for connection, with keep-alive feature - """ - self.baseURL = url - self.queryURL = '' - self.USERNAME = username - self.PASSWORD = password - self.COOKIE = '' - self.isConnected = False - self.s = requests.Session() - def authenticate(self): - """ - This function must be overridden by specific server connection to fit particular needs (urls, other conditions) - """ - pass - def getJson(self,urlToGet): - """ - Helper for GET request. Retrieve information as json string objects - """ - if self.isConnected: - try: - req = self.s.get(self.queryURL + urlToGet, headers={'accept': 'application/json; charset=utf-8'}) - #print 'GET : %s | url : %s' % (req.text, urlToGet) - return json.loads(req.text) - except requests.exceptions.RequestException as e: - print 'Request GET error : %s' % e - #else: - #print 'Session not connected to server, cannot make request' - else: - print 'Session not connected to server, cannot make request' - - def getZinc(self,urlToGet): - """ - Helper for GET request. Retrieve information as json string objects - """ - if self.isConnected: - try: - req = self.s.get(self.queryURL + urlToGet, headers={'accept': 'text/plain; charset=utf-8'}) - #print 'GET : %s | url : %s' % (req.text, urlToGet) - return (req.text) - except requests.exceptions.RequestException as e: - print 'Request GET error : %s' % e - else: - print 'Session not connected to server, cannot make request' - - def postRequest(self,url,headers={'token':''}): - """ - Helper for POST request - """ - try: - req = self.s.post(url, params=headers,auth=(self.USERNAME, self.PASSWORD)) - #print 'POST : %s | url : %s | headers : %s | auth : %s' % (req, url, headers,self.USERNAME) Gives a 404 response but a connection ???? - except requests.exceptions.RequestException as e: # This is the correct syntax - print 'Request POST error : %s' % e - - def setHistoriesList(self): - """ - This function retrieves every histories in the server and returns a list of id - """ - print 'Retrieving list of histories (trends) in server, please wait...' - self.hisList = Histories(self) - print 'Done, use getHistoriesList to check for trends' - - def getHistoriesList(self): - return self.hisList.getListofId() - - def getHistory(self, hisId, dateTimeRange='today'): - return HisRecord(self,hisId,dateTimeRange) - - - -class NiagaraAXConnection(HaystackConnection): - """ - This class connects to NiagaraAX and fetch haystack servlet - A session is open and authentication will persist - """ - def __init__(self,url,username,password): - """ - Define Niagara AX specific local variables : url - Calls the authenticate function - """ - HaystackConnection.__init__(self,url,username,password) - self.loginURL = self.baseURL + "login/" - self.queryURL = self.baseURL + "haystack/" - self.requestAbout = "about" - self.authenticate() - - def authenticate(self): - """ - Login to the server - Get the cookie from the server, configure headers, make a POST request with credential informations. - When connected, ask the haystack for "about" information and print connection information - """ - print 'pyhaystack %s | Authentication to %s' % (__version__,self.loginURL) - try: - self.COOKIE = self.s.get(self.loginURL).cookies - except requests.exceptions.RequestException as e: - print 'Problem connecting to server : %s' % e - - #Version 3.8 gives a cookie - if self.COOKIE: - self.COOKIEPOSTFIX = self.COOKIE['niagara_session'] - self.headers = {'cookiePostfix' : self.COOKIEPOSTFIX - } - self.headers = {'token':'', - 'scheme':'cookieDigest', - 'absPathBase':'/', - 'content-type':'application/x-niagara-login-support', - 'Referer':self.baseURL+'login/', - 'accept':'application/json; charset=utf-8' - } - self.auth = self.postRequest(self.loginURL,self.headers) - self.isConnected = True - self.about = self.getJson(self.requestAbout) - self.serverName = self.about['rows'][0]['serverName'] - self.haystackVersion = self.about['rows'][0]['haystackVersion'] - self.axVersion = self.about['rows'][0]['productVersion'] - print 'Connection made with haystack on %s (%s) running haystack version %s' %(self.serverName,self.axVersion,self.haystackVersion) - self.setHistoriesList() - #Maybe a version lower than 3.8 without cookie - #else: - # - # print "Not connected, it's over now" - # self.isConnected = False - -class Histories(): - """ - This class gathers every histories on the Jace - """ - def __init__(self, session, format='json'): - self.hisListName = [] - self.hisListId = [] - requestHistories = "read?filter=his" - if format == 'json': - histories = session.getJson(requestHistories) - #prettyprint(histories) - for each in histories['rows']: - self.hisListName.append(each['id'].split(' ')[1]) - self.hisListId.append(each['id'].split(' ')[0]) - elif format == 'zinc': - histories = session.getZinc(requestHistories) - his_to_csv = histories.replace(u'\xb0C','').split('\n')[2:] - reader = csv.reader(his_to_csv) - for lines in reader: - self.hisListName.append(lines[9].split(' ')[1]) - print 'Found %s, adding to list' % lines[9].split(' ')[0] - self.hisListId.append(lines[9].split(' ')[0]) - - def getListofId(self): - return self.hisListId - -class UnknownHistoryType(Exception): - """ - Utility Exception class needed when dealing with non-standard histories - Analyse_zone for example wich is a custom historical trend - """ - pass - -class HisRecord(): - """ - This class is a single record - - hisId is the haystack Id of the trend - - data is created as DataFrame to be used directly in Pandas - """ - def __init__(self,session,hisId,dateTimeRange): - """ - GET data from server and fill this object with historical info - """ - self.hisId = hisId - self.jsonHis = session.getJson('hisRead?id='+self.hisId+'&range='+dateTimeRange) - # Convert DateTime / Actually TZ is not taken... - for eachRows in self.jsonHis['rows']: - eachRows['ts'] = datetime.datetime(*map(int, re.split('[^\d]', eachRows['ts'].split(' ')[0])[:-2])) - if isfloat(float(eachRows['val'])): - eachRows['val'] = float(eachRows['val']) - try: - self.data = pd.DataFrame(self.jsonHis['rows'],columns=['ts','val']) - self.data.set_index('ts',inplace=True) - #print '%s added to list' % self.hisId - except Exception: - print '%s is an Unknown history type' % self.hisId - - def plot(self): - """ - Draw a graph of the DataFrame - """ - self.data.plot() - -def isfloat(value): - try: - float(value) - return True - except ValueError: - return False -def prettyprint(jsonData): - """ - Pretty print json object - """ +#!python +# -*- coding: utf-8 -*- +""" +File : pyhaystack.py (2.x) +This module allow a connection to a haystack server +Feautures provided allow user to fetch data from the server and eventually, to post to it. + +See http://www.project-haystack.org for more details + +Project Haystack is an open source initiative to streamline working with data from the Internet of Things. We standardize semantic data models and web services with the goal of making it easier to unlock value from the vast quantity of data being generated by the smart devices that permeate our homes, buildings, factories, and cities. Applications include automation, control, energy, HVAC, lighting, and other environmental systems. + +""" +__author__ = 'Christian Tremblay' +__version__ = '0.26' +__license__ = 'AFL' + +import requests +import json +import pandas as pd +import matplotlib.pyplot as plt +import csv +import re,datetime + + +class HaystackConnection(): + """ + Abstact class / Make a connection object to haystack server using requests module + A class must be made for different type of server. See NiagaraAXConnection(HaystackConnection) + """ + def __init__(self, url, username, password): + """ + Set local variables + Open a session object that will be used for connection, with keep-alive feature + baseURL : http://XX.XX.XX.XX/ - Server URL + queryURL : ex. for nhaystack = baseURL+haystack = http://XX.XX.XX.XX/haystack + USERNAME : used for login + PASSWORD : used for login + COOKIE : for persistent login + isConnected : flag to be used for connection related task (don't try if not connected...) + s : requests.Session() object + filteredList : List of histories created by getFilteredHistories + """ + self.baseURL = url + self.queryURL = '' + self.USERNAME = username + self.PASSWORD = password + self.COOKIE = '' + self.isConnected = False + self.s = requests.Session() + self._filteredList = [] + def authenticate(self): + """ + This function must be overridden by specific server connection to fit particular needs (urls, other conditions) + """ + pass + def getJson(self,urlToGet): + """ + Helper for GET request. Retrieve information as json string objects + """ + if self.isConnected: + try: + req = self.s.get(self.queryURL + urlToGet, headers={'accept': 'application/json; charset=utf-8'}) + #print 'GET : %s | url : %s' % (req.text, urlToGet) + return json.loads(req.text) + except requests.exceptions.RequestException as e: + print 'Request GET error : %s' % e + #else: + #print 'Session not connected to server, cannot make request' + else: + print 'Session not connected to server, cannot make request' + + def getZinc(self,urlToGet): + """ + Helper for GET request. Retrieve information as json string objects + """ + if self.isConnected: + try: + req = self.s.get(self.queryURL + urlToGet, headers={'accept': 'text/plain; charset=utf-8'}) + #print 'GET : %s | url : %s' % (req.text, urlToGet) + return (req.text) + except requests.exceptions.RequestException as e: + print 'Request GET error : %s' % e + else: + print 'Session not connected to server, cannot make request' + + def postRequest(self,url,headers={'token':''}): + """ + Helper for POST request + """ + try: + req = self.s.post(url, params=headers,auth=(self.USERNAME, self.PASSWORD)) + #print 'POST : %s | url : %s | headers : %s | auth : %s' % (req, url, headers,self.USERNAME) Gives a 404 response but a connection ???? + except requests.exceptions.RequestException as e: # This is the correct syntax + print 'Request POST error : %s' % e + + def setHistoriesList(self): + """ + This function retrieves every histories in the server and returns a list of id + """ + print 'Retrieving list of histories (trends) in server, please wait...' + self.allHistories = Histories(self) + print 'Done, use getHistoriesList to check for trends' + + def getHistoriesList(self): + return self.allHistories.getListofIdsAndNames() + + def getFilteredHistoriesListWithData(self,regexfilter,dateTimeRange='today'): + """ + This method returns a list of history record based on a filter on all histories of the server + """ + self._filteredList = [] # Empty list + self._his = {'name':'', + 'id':'', + 'data':''} + for eachHistory in self.allHistories.getListofIdsAndNames(): + if re.search(re.compile(regexfilter, re.IGNORECASE), eachHistory['name']): + print 'Adding %s to recordList' % eachHistory['name'] + self._his['name'] = eachHistory['name'] + self._his['data'] = HisRecord(self,eachHistory['id'],dateTimeRange) + self._filteredList.append(self._his.copy()) + return self._filteredList + + + +class NiagaraAXConnection(HaystackConnection): + """ + This class connects to NiagaraAX and fetch haystack servlet + A session is open and authentication will persist + """ + def __init__(self,url,username,password): + """ + Define Niagara AX specific local variables : url + Calls the authenticate function + """ + HaystackConnection.__init__(self,url,username,password) + self.loginURL = self.baseURL + "login/" + self.queryURL = self.baseURL + "haystack/" + self.requestAbout = "about" + self.authenticate() + + def authenticate(self): + """ + Login to the server + Get the cookie from the server, configure headers, make a POST request with credential informations. + When connected, ask the haystack for "about" information and print connection information + """ + print 'pyhaystack %s | Authentication to %s' % (__version__,self.loginURL) + try: + self.COOKIE = self.s.get(self.loginURL).cookies + except requests.exceptions.RequestException as e: + print 'Problem connecting to server : %s' % e + + #Version 3.8 gives a cookie + if self.COOKIE: + self.COOKIEPOSTFIX = self.COOKIE['niagara_session'] + self.headers = {'cookiePostfix' : self.COOKIEPOSTFIX + } + self.headers = {'token':'', + 'scheme':'cookieDigest', + 'absPathBase':'/', + 'content-type':'application/x-niagara-login-support', + 'Referer':self.baseURL+'login/', + 'accept':'application/json; charset=utf-8' + } + self.auth = self.postRequest(self.loginURL,self.headers) + #Need something here to prove connection....egtting response 500 ??? even is connected. + if self.s.get(self.requestAbout).status_code == 200: + self.isConnected = True + else: + self.isConnected = False + print 'Connection failure, check credentials' + if self.isConnected: + self.about = self.getJson(self.requestAbout) + self.serverName = self.about['rows'][0]['serverName'] + self.haystackVersion = self.about['rows'][0]['haystackVersion'] + self.axVersion = self.about['rows'][0]['productVersion'] + print 'Connection made with haystack on %s (%s) running haystack version %s' %(self.serverName,self.axVersion,self.haystackVersion) + self.setHistoriesList() + #Maybe a version lower than 3.8 without cookie + #else: + # + # print "Not connected, it's over now" + # self.isConnected = False + +class Histories(): + """ + This class gathers every histories on the Jace + """ + def __init__(self, session): + self._allHistories = [] + self._filteredList = [] + self._his = {'name':'', + 'id':'', + 'data':''} + + for each in session.getJson("read?filter=his")['rows']: + self._his['name'] = each['id'].split(' ')[1] + self._his['id'] = each['id'].split(' ')[0] + self._his['data'] = ''#No data right now + self._allHistories.append(self._his.copy()) + + def getListofIdsAndNames(self): + return self._allHistories + + def getDataFrameOf(self, hisId, dateTimeRange='today'): + return HisRecord(self,hisId,dateTimeRange) + + + +class UnknownHistoryType(Exception): + """ + Utility Exception class needed when dealing with non-standard histories + Analyse_zone for example wich is a custom historical trend + """ + pass + +class HisRecord(): + """ + This class is a single record + - hisId is the haystack Id of the trend + - data is created as DataFrame to be used directly in Pandas + """ + def __init__(self,session,hisId,dateTimeRange): + """ + GET data from server and fill this object with historical info + """ + self.hisId = hisId + self.jsonHis = session.getJson('hisRead?id='+self.hisId+'&range='+dateTimeRange) + # Convert DateTime / Actually TZ is not taken... + for eachRows in self.jsonHis['rows']: + eachRows['ts'] = datetime.datetime(*map(int, re.split('[^\d]', eachRows['ts'].split(' ')[0])[:-2])) + if isfloat(float(eachRows['val'])): + eachRows['val'] = float(eachRows['val']) + try: + self.data = pd.DataFrame(self.jsonHis['rows'],columns=['ts','val']) + self.data.set_index('ts',inplace=True) + #print '%s added to list' % self.hisId + except Exception: + print '%s is an Unknown history type' % self.hisId + + def plot(self): + """ + Draw a graph of the DataFrame + """ + self.data.plot() + +def isfloat(value): + try: + float(value) + return True + except ValueError: + return False +def prettyprint(jsonData): + """ + Pretty print json object + """ print json.dumps(jsonData, sort_keys=True, indent=4) \ No newline at end of file diff --git a/pyhaystack.pyc b/pyhaystack.pyc index 0505947..c5fb1cd 100644 Binary files a/pyhaystack.pyc and b/pyhaystack.pyc differ