From b1e690c44c273e111e39aa297337f7eccc670ab3 Mon Sep 17 00:00:00 2001 From: Ben Franske Date: Thu, 13 Nov 2014 17:23:23 -0600 Subject: [PATCH] Added initial JSON support --- getStat.py | 2 ++ pystat.py | 62 +++++++++++++++++++++++++++++++++++------- schedStat.py | 76 ++++++++++++++++++++++++++++++++++++++++++---------- setStat.py | 12 +++++++++ 4 files changed, 129 insertions(+), 23 deletions(-) diff --git a/getStat.py b/getStat.py index 1e56e5a..0f78b54 100755 --- a/getStat.py +++ b/getStat.py @@ -4,6 +4,7 @@ import sys import string from optparse import OptionParser +import os def main(): usage = "usage: %prog [options]" @@ -29,6 +30,7 @@ def main(): print ("Set Point: " + info['setpoint']) print ("Mode: " + info['mode']) print ("Fan Mode: " + info['fanMode']) + print ("Schedule Mode: "+ info['scheduleControl']) t.close() if __name__ == "__main__": diff --git a/pystat.py b/pystat.py index aa37aa3..f32d239 100755 --- a/pystat.py +++ b/pystat.py @@ -1,6 +1,6 @@ # pyStat Python module -#Copyright 2010-2011 Ben Franske (ben@franske.com http://www.benfranske.com) +#Copyright 2010-2014 Ben Franske (ben@franske.com http://www.benfranske.com) # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,6 +16,7 @@ # along with this program. If not, see . import serial +import string from time import localtime, strftime, strptime global tstat_address global local_address @@ -140,6 +141,24 @@ def setfan(self, setfan): elif not setfan: status = self.sendBasicSet(tstat_address,local_address,self.commands['setfan'],0) return + def setschedcontrol(self, control): + # Set whether the schedule should be in HOLD (false) or RUN (true) mode + if string.lower(control) == "run": + status = self.sendBasicSet(tstat_address,local_address,self.commands['schedulecontrol'],1) + elif string.lower(control) == "hold": + status = self.sendBasicSet(tstat_address,local_address,self.commands['schedulecontrol'],0) + return + def setmode(self, mode): + # Set whether the system should be in off, heat, cool, or auto mode + if string.lower(mode) == "off": + status = self.sendBasicSet(tstat_address,local_address,self.commands['setmode'],"O") + elif string.lower(mode) == "heat": + status = self.sendBasicSet(tstat_address,local_address,self.commands['setmode'],"H") + elif string.lower(mode) == "cool": + status = self.sendBasicSet(tstat_address,local_address,self.commands['setmode'],"C") + elif string.lower(mode) == "auto": + status = self.sendBasicSet(tstat_address,local_address,self.commands['setmode'],"A") + return def setclocktopc(self): # Set the time, date and day of week on the thermostat to match the local system time on the computer status = self.sendBasicSet(tstat_address,local_address,self.commands['settime'],strftime("%H:%M:%S",localtime())) @@ -168,11 +187,35 @@ def currentsettings(self): elif subdata[0] == "SPC": parsedData['setpointCooling']=subdata[1] elif subdata[0] == "M": - parsedData['mode']=subdata[1] + if subdata[1] == "O": + parsedData['mode']="off" + elif subdata[1] == "H": + parsedData['mode']="heat" + elif subdata[1] == "C": + parsedData['mode']="cool" + elif subdata[1] == "A": + parsedData['mode']="auto" + elif subdata[1] == "EH": + parsedData['mode']="emergency-heat" elif subdata[0] == "FM": - parsedData['fanMode']=subdata[1] + if subdata[1] == "1": + parsedData['fanMode']="on" + elif subdata[1] == "0": + parsedData['fanMode']="auto" elif subdata[0] == "SC": - parsedData['scheduleControl']=subdata[1] + if subdata[1] == "1": + parsedData['scheduleControl']="run" + elif subdata[1] == "0": + parsedData['scheduleControl']="hold" + settings = self.getLine(tstat_address,local_address,self.commands['schedulecontrol'],"?") + data = settings.split() + for i in data: + subdata = i.split("=") + if subdata[0] == "SC": + if subdata[1] == "1": + parsedData['scheduleControl']="run" + elif subdata[1] == "0": + parsedData['scheduleControl']="hold" return parsedData def getperiodsched(self, day, period): # Return an array with the schedule entries for a given day and period (day and period given numerically in RCS format) @@ -192,26 +235,27 @@ def getperiodsched(self, day, period): parsedData['schedTime']=strptime(pythonDOW + subdata[1][0:4],"%w%H%M") parsedData['setpointHeating']=subdata[1][4:6] parsedData['setpointCooling']=subdata[1][6:8] - parsedData['period']=period + parsedData['rcs_period_number']=period + parsedData['rcs_day_number']=day return parsedData def getdaysched(self, day): # Return an array with the daily schedule for the given day (given numerically where Sun=1, Sat=7) daysched = {} for i in range(1,5): - daysched[i]=self.getperiodsched(day, i) + daysched['period'+str(i)]=self.getperiodsched(day, i) return daysched def getweeksched(self): # Return an array with the complete weekly schedule (where Sun=1, Sat=7) weeksched = {} for i in range(1,8): - weeksched[i]=self.getdaysched(i) + weeksched['day'+str(i)]=self.getdaysched(i) return weeksched def setperiodsched(self, schedule): # Accepts an array with the entries for a given day and period in the same format as getperiodsched returns # Note that schedTime is in the python struct_time format (with only hour, minute and weekday used) day=int(strftime("%w",schedule['schedTime']))+1 time=strftime("%H%M",schedule['schedTime']) - status = self.sendBasicSet(tstat_address,local_address,self.commands['scheduleentry'] + str (day) + '/' + str(schedule['period']),time + schedule['setpointHeating'] + schedule['setpointCooling']) + status = self.sendBasicSet(tstat_address,local_address,self.commands['scheduleentry'] + str (day) + '/' + str(schedule['rcs_period_number']),time + schedule['setpointHeating'] + schedule['setpointCooling']) return def setdaysched(self, schedule): # Accepts a multi-dimensional array with the entries for all periods of a given day in the same format as getdaysched returns @@ -224,6 +268,6 @@ def setweeksched(self, schedule): # Accepts a multi-dimensional array with the entries for all periods of all days in the same format as getweeksched returns # Note that schedTime is in the python struct_time format (with only hour, minute and weekday used) daysched = {} - for i in range(1,5): + for i in range(1,8): self.setdaysched(schedule[i]) return diff --git a/schedStat.py b/schedStat.py index 3e18394..b6973bc 100755 --- a/schedStat.py +++ b/schedStat.py @@ -3,6 +3,7 @@ import pystat import sys import string +import json from optparse import OptionParser from lxml import etree from time import localtime, strftime, strptime @@ -14,12 +15,12 @@ def convertWeekToXML(weekSched): day_number = {} day_name = {} - for i in weekSched: + for i in sorted(weekSched): day[i] = etree.SubElement(root, "day") # Get the current day number from the time_struct of the first period - day_number[i] = etree.SubElement(day[i], "day_number").text = str(int(strftime("%w",weekSched[i][1]['schedTime']))+1) + day_number[i] = etree.SubElement(day[i], "day_number").text = str(int(strftime("%w",weekSched[i]['period1']['schedTime']))+1) # Save the current day name to make the XML more human-readable but this is NOT used anythere, only the day_number is! - day_name[i] = etree.SubElement(day[i], "day_name").text = strftime("%A",weekSched[i][1]['schedTime']) + day_name[i] = etree.SubElement(day[i], "day_name").text = strftime("%A",weekSched[i]['period1']['schedTime']) period = {} period_number = {} @@ -27,15 +28,30 @@ def convertWeekToXML(weekSched): period_setpointCooling = {} period_schedTime = {} - for k in weekSched[i]: + for k in sorted(weekSched[i]): period[k] = etree.SubElement(day[i], "period") - period_number[k] = etree.SubElement(period[k], "period_number").text = str(weekSched[i][k]['period']) + period_number[k] = etree.SubElement(period[k], "period_number").text = str(weekSched[i][k]['rcs_period_number']) period_schedTime[k] = etree.SubElement(period[k], "schedTime").text = strftime("%H%M",weekSched[i][k]['schedTime']) period_setpointHeating[k] = etree.SubElement(period[k], "setpointHeating").text = str(weekSched[i][k]['setpointHeating']) period_setpointCooling[k] = etree.SubElement(period[k], "setpointCooling").text = str(weekSched[i][k]['setpointCooling']) return root +def convertWeekToJSON(weekSched): + # Returns a JSON string version of the weekSched array + jsonWeekSched = {} + for day in weekSched: + jsonWeekSched[day] = {} + for period in weekSched[day]: + jsonWeekSched[day][period] = {} + for item in weekSched[day][period]: + jsonWeekSched[day][period][item] = {} + if item=='schedTime': + jsonWeekSched[day][period][item]=strftime("%H%M",weekSched[day][period]['schedTime']) + else: + jsonWeekSched[day][period][item]=weekSched[day][period][item] + return jsonWeekSched + def convertXMLToWeek(weekXML): # Returns a week-style array of the XML data weekSched = {} @@ -67,34 +83,66 @@ def convertXMLToWeek(weekXML): i += 1 return weekSched -def saveSched(fileName): +def saveSched(fileName,format): +# t = pystat.thermostat('/dev/ttyUSB0') +# weeksched=t.getweeksched() +# t.close() + weeksched=getSched() + if format=="xml": + etree.ElementTree(convertWeekToXML(weeksched)).write(fileName, pretty_print=True) + return + +def loadSched(fileName,format): + weekXML = etree.parse(fileName).getroot() t = pystat.thermostat('/dev/ttyUSB0') - weeksched=t.getweeksched() + if format=="xml": + t.setweeksched(convertXMLToWeek(weekXML)) t.close() - etree.ElementTree(convertWeekToXML(weeksched)).write(fileName, pretty_print=True) return -def loadSched(fileName): - weekXML = etree.parse(fileName).getroot() +def getSched(): t = pystat.thermostat('/dev/ttyUSB0') - t.setweeksched(convertXMLToWeek(weekXML)) + weeksched=t.getweeksched() t.close() + return weeksched + +def stoutSched(format): + weeksched=getSched() + if format=="xml": + etree.ElementTree(convertWeekToXML(weeksched)).write(sys.stdout, pretty_print=True) + if format=="json": + print json.dumps(convertWeekToJSON(weeksched),indent=4,sort_keys=True) return def main(): usage = "usage: %prog [options]" - parser = OptionParser(usage=usage, version="%prog 0.1") + parser = OptionParser(usage=usage, version="%prog 0.2") parser.add_option("-s", "--save", action="store", type="string", dest="saveFile", metavar="FILE", help="Save the current weekly schedule to FILE") parser.add_option("-l", "--load", action="store", type="string", dest="loadFile", metavar="FILE", help="Load the weekly schedule from FILE") + parser.add_option("-o", "--stout", + action="store_true", dest="stout", + help="Send the current weekly schedule to standard output") + parser.add_option("-j", "--json", + action="store_true", dest="json", default=False, + help="Use JSON format input/output") + parser.add_option("-x", "--xml", + action="store_true", dest="xml", default=True, + help="Use XML format input/output") (options, args) = parser.parse_args() + if options.json: + format="json" + elif options.xml: + format="xml" if options.saveFile: - saveSched(options.saveFile) + saveSched(options.saveFile,format) elif options.loadFile: - loadSched(options.loadFile) + loadSched(options.loadFile,format) + if options.stout: + stoutSched(format) if __name__ == "__main__": main() diff --git a/setStat.py b/setStat.py index 9c562e1..689de3d 100755 --- a/setStat.py +++ b/setStat.py @@ -14,6 +14,12 @@ def main(): parser.add_option("-f", "--fan", action="store", type="string", dest="fan", metavar="STATUS", help="Set fan STATUS to auto or on") + parser.add_option("-s", "--schedule-control", + action="store", type="string", dest="control", + metavar="CONTROL", help="Set system schedule CONTROL to run or hold") + parser.add_option("-m", "--mode", + action="store", type="string", dest="mode", + metavar="MODE", help="Set system MODE to off, heat, cool, or auto") parser.add_option("-c", "--clock", action="store_true", dest="setclock", help="Set the thermostat clock to match the PC") @@ -26,6 +32,12 @@ def main(): t.setfan(0) elif string.lower(options.fan) == "on": t.setfan(1) + if options.control: + if string.lower(options.control) == "hold" or string.lower(options.control) == "run": + t.setschedcontrol(string.lower(options.control)) + if options.mode: + if string.lower(options.mode) == "off" or string.lower(options.mode) == "heat" or string.lower(options.mode) == "cool" or string.lower(options.mode) == "auto": + t.setmode(string.lower(options.mode)) if options.setclock: t.setclocktopc() t.close()