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
| master_key = b'MR1FGUGSq0YLnZpI2kjABw=='
client_key = b'PASSWORD'
@app.route("/api")
def api():
arg = str(next(iter(request.args)))
arg = arg.split('_')
hardware_id = arg[1]
tempkey = client_key + master_key[len(client_key):]
arc4 = ARC4(tempkey)
args = arc4.decrypt(base64.b16decode(arg[2])).decode()
args = parse_qs(args)
# [('u', 'IT0XXXX'), ('p', 'PasswordXXXXXX'), ('cv', '0'), ('hw', '4'), ('fw', '30040030'), ('init', '1'), ('pv', '208'), ('hv', '0'), ('th', '0'), ('tc', '60'), ('ot0', '0xdead'), ('ot1', '0xdead'), ('ot3', '0xdead'), ('ot17', '0xdead'), ('ot18', '0xdead'), ('ot19', '0xdead'), ('ot25', '0xdead'), ('ot26', '0xdead'), ('ot27', '0xdead'), ('ot28', '0xdead'), ('ot34', '0xdead'), ('ot56', '0xdead'), ('ot125', '0xdead'), ('otime', '4787'), ('kp', '20.00'), ('ti', '600.00'), ('td', '-1.00'), ('frc', '0'), ('out', '0.00'), ('orx', '0'), ('ot', '0'), ('pd', '-315.35'), ('id', '0.00'), ('dd', '-0.02'), ('oo', '0'), ('ta', '0'), ('dim', '100'), ('sl', '2'), ('dv', '1'), ('csv', '50'), ('lrn', '-1'), ('to', '1'), ('ts', '4787'), ('sd', '0'), ('hlp', 'Tb=0.0_BE=0_SC=0_ID5=dead_tr=17_ctl=INIT_ID14=dead_ID0=0')]
print(args)
# <CAL>
# vXXXX -- version (server generated crc32?)
# 2XXXY -- record count?
# sDHHMMTTTW -- standard record: day, hour, minute, temperature, DHW (all in base10 ascii)
# xIBBBBBBBBEEEEEEEETTTW -- exception record: id (base10), begin (hex), end (hex), temperature (base10), DHW (base10)
# </CAL> --
# <SVSET></SVSET> -- 50-250 # set temperature
# <BVSET></BVSET> -- 500-1000 # set outside temperature
# <PVSET></PVSET> -- floating point # sets measured temperature?
# <PAUSE>0</PAUSE> -- 0 or 1 # set away from home
# <WEER/> -- - # unused markup tag
# <RMSG/> -- - # unused markup tag
# <TZ>+120</TZ> -- # Contains the (signed) time zone offset in minutes.
# <FW>1</FW> -- 0/1 # FW update
# <TA></TA> -- -25-25 # temperature adjustment/calibration
# <DIM></DIM> -- 0-100 # display dimming
# <SLS></SLS> -- 0-2 # status led setting:
# 0 = Status LED is off.
# 1 = Status LED displays only errors.
# 2 = Status LED glows on boiler activity, when new temperature is set and flashes to indicate errors (Default).
# <SD></SD> -- 0/1 # display function on/off
# <PID>
# <KP>20.0</KP> -- The proportional term (Kp)
# <TI>600.0</TI> -- the integral term (Ti)
# <TD>-1.00</TD> -- differential term (Td)
# <FORCE></FORCE> -- <99 # ? manual temperature override?
# </PID>
# <LRN>
# <US></US> -- floating point # ?
# </LRN>
# <INIT>
# <SV></SV> -- -50-250 # ?
# <DHW>1</DHW> -- 0/1 # Domestic HOT Water on/off
# <SRC></SRC> -- 0-5 # CRASH: 0, MANUAL: 1, SERVER: 2, STD_WEEK: 3, EXCEPTION: 4, PAUSE: 5
# <LOCALE>nl-NL</LOCALE> -- de-DE, fr-FR, fr-BE, nl-NL, nl-BE, en-GB
# </INIT>
# <SRV></SRV> -- 1-255? # 1 will disable server communication?
# <TH></TH> -- 0-10 # The throttle factor property specifies the value of the server polling delay. A factor of 1 translates to delays of 10, 15, 20, 25, etc. seconds. A factor of 0 disables the throttling and defaults to 5 seconds.
# <TS></TS> -- 0 # Time (GMT) in seconds
# Time (GMT) in seconds
ts = calendar.timegm(time.gmtime())
# time zone offset in minutes (signed).
tz = (-time.timezone) / 60
response=f'<ITHERMOSTAT><SRC>1</SRC><TZ>{tz}</TZ><TS>{ts}</TS><BVSET>-100</BVSET><PAUSE>0</PAUSE><SVSET>200</SVSET><DIM>60</DIM><TH>0</TH></ITHERMOSTAT>'
arc4 = ARC4(tempkey)
response = base64.b16encode(arc4.encrypt(response))
r = Response(response=response, status=200, mimetype="application/octet-stream")
return r |