Skip to content

Commit

Permalink
Basic functionality in place and working
Browse files Browse the repository at this point in the history
  • Loading branch information
Ben Franske committed Jan 17, 2011
0 parents commit 2e095f7
Show file tree
Hide file tree
Showing 4 changed files with 401 additions and 0 deletions.
36 changes: 36 additions & 0 deletions getStat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#! /usr/bin/python

import pystat
import sys
import string
from optparse import OptionParser

def main():
usage = "usage: %prog [options]"
parser = OptionParser(usage=usage, version="%prog 0.1")
parser.add_option("-t", "--temp",
action="store_true", dest="temp",
help="Get the current temperature")
parser.add_option("-s", "--setpoint",
action="store_true", dest="sp",
help="Get the current setpoint")
parser.add_option("-b", "--basic",
action="store_true", dest="basicinfo",
help="Get the basic statistics")
(options, args) = parser.parse_args()
t = pystat.thermostat('/dev/ttyUSB0')
if options.temp:
print (t.currentsettings()['temperature'])
if options.sp:
print (t.currentsettings()['setpoint'])
if options.basicinfo:
info = t.currentsettings()
print ("Temperature: " + info['temperature'])
print ("Set Point: " + info['setpoint'])
print ("Mode: " + info['mode'])
print ("Fan Mode: " + info['fanMode'])
t.close()

if __name__ == "__main__":
main()

229 changes: 229 additions & 0 deletions pystat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
# pyStat Python module

#Copyright 2010-2011 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
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

import serial
from time import localtime, strftime, strptime
global tstat_address
global local_address

# Thermostat address is 1 by default
tstat_address=1

# Local address is 0 by default
local_address=0

# pyStat is a Python module for sending and receiving information from
# the Residential Control Systems (RCS) TR-60 (and possibly other RCS)
# thermostats.
#
# Example usage:
#
# Create a thermostat object that can send and receive information from
# the thermostat
# t = pystat.thermostat('/dev/ttyUSB0')
# Adjust the thermostat setpoint to 72 degrees)
# t.setpoint(72)
# Close the thermostat object and release the serial port
# t.close()
#

class thermostat:

commands = {
'setpoint' : 'SP',
'setpointheat' : 'SPH',
'setpointcool' : 'SPC',
'setmode' : 'M',
'setfan' : 'F',
'tempformat' : 'CFM',
'textmsg' : 'TM',
'settime' : 'TIME',
'setdate' : 'DATE',
'setdayofweek' : 'DOW',
'schedulecontrol' : 'SC',
'scheduleentry' : 'SE',
'returnstatus' : 'R'}

def sendBasicSet(self,tstat_address,local_address,cmd,value):
# Sends a standard command to the thermostat
if tstat_address is None: return "Invalid thermostat address"
if local_address is None: return "Invalid local address"
tstat_address = str(tstat_address)
local_address = str(local_address)
value = str(value)
msg = "A=" + tstat_address + " O=" + local_address + " " + cmd + "=" + value + "\r"
self.send_msg(msg,0)
return

def getLine(self,tstat_address,local_address,cmd,value):
# Send a status request message and return one line
if tstat_address is None: return "Invalid thermostat address"
if local_address is None: return "Invalid local address"
tstat_address = str(tstat_address)
local_address = str(local_address)
value = str(value)
msg = "A=" + tstat_address + " O=" + local_address + " " + cmd + "=" + value + "\r"
line = ''
line = self.send_msg(msg,1)
return line

def send_msg(self,message,expectdata=0):
# This method is responsible for handling all outgoing serial messages.
try:
if not self.ser.isOpen():
self.ser.open()
except serial.SerialException:
raise
return "Could not open serial port"
self.ser.write(message)
line = ''
# Only some of the commands return data
if expectdata is 1:
while True:
char = self.ser.read(1)
line += char
if char == '\r':
break
return line

def __init__(self, serPort=None, verbose=False):

# Serial port initialization
try:
if serPort is None:
raise Exception('Serial port not selected')
else:
if type(serPort) is str:
port = serPort
else:
raise Exception('User specified serial port is not a string.')
self.ser = serial.Serial(port,9600,8,'N',1,timeout=1)
except serial.SerialException:
raise
except NameError:
self.ser = None
raise

# --- Low-level/internal methods ---

def close(self):
# Use close to release the serial port and socket interface.
try:
self.ser.close()
except:
pass

# --- High-level methods ---

def setpoint(self, setpoint):
# Adjust the thermostat setpoint for the currently selected mode (heating or cooling)
status = self.sendBasicSet(tstat_address,local_address,self.commands['setpoint'],setpoint)
return
def setfan(self, setfan):
# Put the fan in auto mode (FALSE) or manual on mode (TRUE)
if setfan:
status = self.sendBasicSet(tstat_address,local_address,self.commands['setfan'],1)
elif not setfan:
status = self.sendBasicSet(tstat_address,local_address,self.commands['setfan'],0)
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()))
status = self.sendBasicSet(tstat_address,local_address,self.commands['setdate'],strftime("%m/%d/%y",localtime()))
status = self.sendBasicSet(tstat_address,local_address,self.commands['setdayofweek'],str(int(strftime("%w",localtime())) + 1))
return
def currentsettings(self):
# Return an array with the current settings of the thermostat
settings = self.getLine(tstat_address,local_address,self.commands['returnstatus'],1)
data = settings.split()
parsedData = {}
for i in data:
subdata = i.split("=")
if subdata[0] == "A":
parsedData['address']=subdata[1]
elif subdata[0] == "O":
parsedData['originator']=subdata[1]
elif subdata[0] == "Z":
parsedData['zone']=subdata[1]
elif subdata[0] == "T":
parsedData['temperature']=subdata[1]
elif subdata[0] == "SP":
parsedData['setpoint']=subdata[1]
elif subdata[0] == "SPH":
parsedData['setpointHeating']=subdata[1]
elif subdata[0] == "SPC":
parsedData['setpointCooling']=subdata[1]
elif subdata[0] == "M":
parsedData['mode']=subdata[1]
elif subdata[0] == "FM":
parsedData['fanMode']=subdata[1]
elif subdata[0] == "SC":
parsedData['scheduleControl']=subdata[1]
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)
# Sun=1, Sat=7
# Note that schedTime is returned in the python struct_time format (with only hour, minute and weekday set)
settings = self.getLine(tstat_address,local_address,self.commands['scheduleentry'] + str(day) + '/' + str(period),'?')
data = settings.split()
parsedData = {}
pythonDOW = str(day-1)
for i in data:
subdata = i.split("=")
# if subdata[0] == "A":
# parsedData['address']=subdata[1]
# elif subdata[0] == "O":
# parsedData['originator']=subdata[1]
if subdata[0] == self.commands['scheduleentry'] + str(day) + '/' + str(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
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)
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)
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'])
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
# Note that schedTime is in the python struct_time format (with only hour, minute and weekday used)
daysched = {}
for i in range(1,5):
self.setperiodsched(schedule[i])
return
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):
self.setdaysched(schedule[i])
return
101 changes: 101 additions & 0 deletions schedStat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#! /usr/bin/python

import pystat
import sys
import string
from optparse import OptionParser
from lxml import etree
from time import localtime, strftime, strptime

def convertWeekToXML(weekSched):
# Returns an XML etree version of the weekSched array
root = etree.Element("schedule")
day = {}
day_number = {}
day_name = {}

for i in 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)
# 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'])

period = {}
period_number = {}
period_setpointHeating = {}
period_setpointCooling = {}
period_schedTime = {}

for k in 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_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 convertXMLToWeek(weekXML):
# Returns a week-style array of the XML data
weekSched = {}
i = 1
for day in weekXML:
k = 1
daySched = {}
for period in day:
periodSched = {}
# Day number is here
if period.tag == 'day_number':
day_number = int(period.text)
for element in period:
# Period number, time and setpoints are here
if element.tag == 'period_number':
periodSched['period'] = int(element.text)
if element.tag == 'setpointCooling':
periodSched['setpointCooling'] = element.text
if element.tag == 'setpointHeating':
periodSched['setpointHeating'] = element.text
if element.tag == 'schedTime':
schedTime = element.text
pythonDOW = str(day_number-1)
periodSched['schedTime'] = strptime(pythonDOW + schedTime,"%w%H%M")
if period.tag == 'period':
daySched[k] = periodSched
k += 1
weekSched[i] = daySched
i += 1
return weekSched

def saveSched(fileName):
t = pystat.thermostat('/dev/ttyUSB0')
weeksched=t.getweeksched()
t.close()
etree.ElementTree(convertWeekToXML(weeksched)).write(fileName, pretty_print=True)
return

def loadSched(fileName):
weekXML = etree.parse(fileName).getroot()
t = pystat.thermostat('/dev/ttyUSB0')
t.setweeksched(convertXMLToWeek(weekXML))
t.close()
return

def main():
usage = "usage: %prog [options]"
parser = OptionParser(usage=usage, version="%prog 0.1")
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")
(options, args) = parser.parse_args()
if options.saveFile:
saveSched(options.saveFile)
elif options.loadFile:
loadSched(options.loadFile)

if __name__ == "__main__":
main()

Loading

0 comments on commit 2e095f7

Please sign in to comment.