-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathEvohome_Schedule_Backup_v0.3.py
157 lines (139 loc) · 6.19 KB
/
Evohome_Schedule_Backup_v0.3.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# Evohome Schedule Backup v0.3
# Copyright (c) 2019 Evsdd
# Python 3.7
# Requires pyserial module which can be installed using 'python -m pip install pyserial'
# Prototype program to backup the complete schedule from the Evotouch controller
#
# 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 required modules
from __future__ import print_function
import serial
import time
import datetime
import struct
import textwrap
import zlib
from array import array
##### Start of user configuration setup
##### Note: file and serial port settings below are all for Windows OS
##### Set backup filename
output_backup = open("e:\\Python\\Evohome_Backup_Test.txt", "w")
##### Configure serial port
ComPort = serial.Serial('COM4') # open port
ComPort.baudrate = 115200 # set baud rate (HGI80=115200)
ComPort.bytesize = 8 # Number of data bits = 8
ComPort.parity = 'N' # No parity
ComPort.stopbits = 1 # Number of Stop bits = 1
ComPort.timeout = 2 # Read timeout = 1sec
##### Evohome controller ID
ControllerID = 0x51d74
##### End of user configuration setup
##### Additional configuration setup (you don't need to alter these)
GatewayID = 0x4802DA
Max_zones = 12 # Maximum number of zones
Com_SCHD = 0x0404 # Evohome Command SCHEDULE
# Create device values required for message structure
ControllerTXT = '{:02d}:{:06d}'.format((ControllerID & 0xFC0000) >> 18, ControllerID & 0x03FFFF)
GatewayTXT = '{:02d}:{:06d}'.format((GatewayID & 0xFC0000) >> 18, GatewayID & 0x03FFFF)
print('ControllerID=0x%06X (%s)' % (ControllerID, ControllerTXT))
##### End of additional configuration setup
# message send and response confirmation
def msg_send_back(msg_type,msg_comm,msg_pay,msg_addr1='--:------',msg_addr2='--:------',msg_addr3='--:------',msg_delay=1,msg_resp=0):
send_data = bytearray('{0:s} --- {1:s} {2:s} {3:s} {4:04X} {5:03d} {6:s}'.format(msg_type, msg_addr1, msg_addr2, msg_addr3, msg_comm, int(len(msg_pay)/2), msg_pay), 'utf-8') + b'\r\n'
print('Send:[{:s}]'.format(send_data.decode().strip()))
time.sleep(msg_delay) ## wait before sending message to avoid overloading serial port
No = ComPort.write(send_data)
if msg_resp: # wait for response command from addr2 device
send_time = time.time()
resp = False
j = 0 # retry counter
RQ_zone = int(msg_pay[1:2], 16)
while (resp == False):
try:
data = ComPort.readline().decode().replace("\x11","").rstrip() # Wait and read data
if data: # Only proceed if line read before timeout
print(data)
msg_type = data[4:6] # Extract message type
dev1 = data[11:20] # Extract deviceID 1
dev2 = data[21:30] # Extract deviceID 2
dev3 = data[31:40] # Extract deviceID 3
cmnd = data[41:45] # Extract command
RP_zone = int(data[51:52], 16) # Extract first 2 bytes of payload and convert to int
if (cmnd == '%04X' % msg_comm and dev1 == msg_addr2):
resp = True
print("Send success!")
if RP_zone == RQ_zone:
response = data[62:len(data)]
else: # if controller responds with different zone we've reached the zone limit
response = 'FF'
else:
if (j == 5): # retry 5 times
resp = True
print("Send failure!")
response = ''
else:
if ((time.time() - send_time) > 1): # Wait 1sec before each re-send
j += 1
print('Re-send[{0:d}][{1:s}]'.format(j, send_data.decode().strip()))
No = ComPort.write(send_data) # re-send message
send_time = time.time()
except Exception as e:
print('Error from getscheduleHGI80: ',e)
continue
return response
# decode zlib compressed payload
def decode_schedule(message):
#def decode_schedule(message,zone):
i = 0
try:
data = zlib.decompress(bytearray.fromhex(message))
Status = True
except zlib.error:
Status = False
if Status:
for record in [data[i:i+20] for i in range(0, len(data), 20)]:
(zone, day, time, temp, unk) = struct.unpack("<xxxxBxxxBxxxHxxHH", record)
print('ZONE={0:d} DAY={1:d} TIME={2:02d}:{3:02d} TEMP={4:.2f}'.format(zone+1, day+1, *divmod(time, 60), temp/100), file=output_backup)
return Status
##### Controller startup commands
time.sleep(2) ## wait for serial port to stabilise
Zone = 1
# Request all zone schedules from controller and backup to file
while Zone <= Max_zones:
Complete = False
while (not Complete): # Ensure that full schedule for each zone has been sucessfully decoded (occasionally contain corrupted characters)
Pack_Total = 0
Sched = ''
Packet = 1
while (Packet <= Pack_Total or Pack_Total == 0):
payload = '{0:02X}20000800{1:02d}{2:02d}'.format(Zone-1, Packet, Pack_Total)
response = msg_send_back(msg_type='RQ', msg_addr1=GatewayTXT, msg_addr2=ControllerTXT, msg_comm=Com_SCHD, msg_pay=payload,msg_delay=0,msg_resp=1)
if response == 'FF': # end of zones indicator
Complete = True
Zone = Max_zones+1
break
else:
if response:
Pack_Total = int(response[0:2])
Sched += (response[2:len(response)])
else:
break
Packet += 1
if not Complete:
Complete = decode_schedule(Sched)
Zone += 1
else:
break
output_backup.close()
print('Backup Complete!')