Skip to content

Commit

Permalink
Added initial JSON support
Browse files Browse the repository at this point in the history
  • Loading branch information
Ben Franske committed Nov 13, 2014
1 parent 2e095f7 commit b1e690c
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 23 deletions.
2 changes: 2 additions & 0 deletions getStat.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import sys
import string
from optparse import OptionParser
import os

def main():
usage = "usage: %prog [options]"
Expand All @@ -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__":
Expand Down
62 changes: 53 additions & 9 deletions pystat.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# pyStat Python module

#Copyright 2010-2011 Ben Franske ([email protected] http://www.benfranske.com)
#Copyright 2010-2014 Ben Franske ([email protected] 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
Expand All @@ -16,6 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import serial
import string
from time import localtime, strftime, strptime
global tstat_address
global local_address
Expand Down Expand Up @@ -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()))
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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
76 changes: 62 additions & 14 deletions schedStat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -14,28 +15,43 @@ 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 = {}
period_setpointHeating = {}
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 = {}
Expand Down Expand Up @@ -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()
Expand Down
12 changes: 12 additions & 0 deletions setStat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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()
Expand Down

0 comments on commit b1e690c

Please sign in to comment.