Skip to content

Commit 2b4e6d9

Browse files
Merge branch 'rf_experiment' into rf_experiment_v0.9
# Conflicts: # README.md # broadlink/__init__.py # cli/broadlink_cli
2 parents 1cceae7 + 694b442 commit 2b4e6d9

3 files changed

Lines changed: 135 additions & 36 deletions

File tree

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,27 @@ Enter learning mode:
4343
devices[0].enter_learning()
4444
```
4545

46+
Sweep RF frequencies:
47+
```
48+
devices[0].sweep_frequency()
49+
```
50+
51+
Cancel sweep RF frequencies:
52+
```
53+
devices[0].cancel_sweep_frequency()
54+
```
55+
Check whether a frequency has been found:
56+
```
57+
found = devices[0].check_frequency()
58+
```
59+
(This will return True if the RM has locked onto a frequency, False otherwise)
60+
61+
Attempt to learn an RF packet:
62+
```
63+
found = devices[0].find_rf_packet()
64+
```
65+
(This will return True if a packet has been found, False otherwise)
66+
4667
Obtain an IR or RF packet while in learning mode:
4768
```
4869
ir_packet = devices[0].check_data()

broadlink/__init__.py

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,38 @@ def enter_learning(self):
545545
packet[0] = 3
546546
self.send_packet(0x6a, packet)
547547

548+
def sweep_frequency(self):
549+
packet = bytearray(16)
550+
packet[0] = 0x19
551+
self.send_packet(0x6a, packet)
552+
553+
def cancel_sweep_frequency(self):
554+
packet = bytearray(16)
555+
packet[0] = 0x1e
556+
self.send_packet(0x6a, packet)
557+
558+
def check_frequency(self):
559+
packet = bytearray(16)
560+
packet[0] = 0x1a
561+
response = self.send_packet(0x6a, packet)
562+
err = response[0x22] | (response[0x23] << 8)
563+
if err == 0:
564+
payload = self.decrypt(bytes(response[0x38:]))
565+
if payload[0x04] == 1:
566+
return True
567+
return False
568+
569+
def find_rf_packet(self):
570+
packet = bytearray(16)
571+
packet[0] = 0x1b
572+
response = self.send_packet(0x6a, packet)
573+
err = response[0x22] | (response[0x23] << 8)
574+
if err == 0:
575+
payload = self.decrypt(bytes(response[0x38:]))
576+
if payload[0x04] == 1:
577+
return True
578+
return False
579+
548580
def check_temperature(self):
549581
packet = bytearray(16)
550582
packet[0] = 1
@@ -576,19 +608,19 @@ def __init__ (self, host, mac, devtype):
576608
self.type = "Hysen heating controller"
577609

578610
# Send a request
579-
# input_payload should be a bytearray, usually 6 bytes, e.g. bytearray([0x01,0x06,0x00,0x02,0x10,0x00])
611+
# input_payload should be a bytearray, usually 6 bytes, e.g. bytearray([0x01,0x06,0x00,0x02,0x10,0x00])
580612
# Returns decrypted payload
581613
# New behaviour: raises a ValueError if the device response indicates an error or CRC check fails
582614
# The function prepends length (2 bytes) and appends CRC
583615
def send_request(self,input_payload):
584-
616+
585617
from PyCRC.CRC16 import CRC16
586618
crc = CRC16(modbus_flag=True).calculate(bytes(input_payload))
587619

588620
# first byte is length, +2 for CRC16
589621
request_payload = bytearray([len(input_payload) + 2,0x00])
590622
request_payload.extend(input_payload)
591-
623+
592624
# append CRC
593625
request_payload.append(crc & 0xFF)
594626
request_payload.append((crc >> 8) & 0xFF)
@@ -598,9 +630,9 @@ def send_request(self,input_payload):
598630

599631
# check for error
600632
err = response[0x22] | (response[0x23] << 8)
601-
if err:
633+
if err:
602634
raise ValueError('broadlink_response_error',err)
603-
635+
604636
response_payload = bytearray(self.decrypt(bytes(response[0x38:])))
605637

606638
# experimental check on CRC in response (first 2 bytes are len, and trailing bytes are crc)
@@ -610,9 +642,9 @@ def send_request(self,input_payload):
610642
crc = CRC16(modbus_flag=True).calculate(bytes(response_payload[2:response_payload_len]))
611643
if (response_payload[response_payload_len] == crc & 0xFF) and (response_payload[response_payload_len+1] == (crc >> 8) & 0xFF):
612644
return response_payload[2:response_payload_len]
613-
else:
645+
else:
614646
raise ValueError('hysen_response_error','CRC check on response failed')
615-
647+
616648

617649
# Get current room temperature in degrees celsius
618650
def get_temp(self):
@@ -626,7 +658,7 @@ def get_external_temp(self):
626658

627659
# Get full status (including timer schedule)
628660
def get_full_status(self):
629-
payload = self.send_request(bytearray([0x01,0x03,0x00,0x00,0x00,0x16]))
661+
payload = self.send_request(bytearray([0x01,0x03,0x00,0x00,0x00,0x16]))
630662
data = {}
631663
data['remote_lock'] = payload[3] & 1
632664
data['power'] = payload[4] & 1
@@ -652,11 +684,11 @@ def get_full_status(self):
652684
data['min'] = payload[20]
653685
data['sec'] = payload[21]
654686
data['dayofweek'] = payload[22]
655-
687+
656688
weekday = []
657689
for i in range(0, 6):
658690
weekday.append({'start_hour':payload[2*i + 23], 'start_minute':payload[2*i + 24],'temp':payload[i + 39]/2.0})
659-
691+
660692
data['weekday'] = weekday
661693
weekend = []
662694
for i in range(6, 8):
@@ -693,7 +725,7 @@ def set_advanced(self, loop_mode, sensor, osv, dif, svh, svl, adj, fre, poweron)
693725
# For backwards compatibility only. Prefer calling set_mode directly. Note this function invokes loop_mode=0 and sensor=0.
694726
def switch_to_auto(self):
695727
self.set_mode(auto_mode=1, loop_mode=0)
696-
728+
697729
def switch_to_manual(self):
698730
self.set_mode(auto_mode=0, loop_mode=0)
699731

cli/broadlink_cli

Lines changed: 71 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/usr/bin/env python
1+
#!/usr/bin/env python3
22

33
import broadlink
44
import sys
@@ -79,7 +79,8 @@ parser.add_argument("--switch", action="store_true", help="switch state from on
7979
parser.add_argument("--send", action="store_true", help="send command")
8080
parser.add_argument("--sensors", action="store_true", help="check all sensors")
8181
parser.add_argument("--learn", action="store_true", help="learn command")
82-
parser.add_argument("--learnfile", help="learn command and save to specified file")
82+
parser.add_argument("--rfscanlearn", action="store_true", help="rf scan learning")
83+
parser.add_argument("--learnfile", help="save learned command to a specified file")
8384
parser.add_argument("--durations", action="store_true", help="use durations in micro seconds instead of the Broadlink format")
8485
parser.add_argument("--convert", action="store_true", help="convert input data to durations")
8586
parser.add_argument("data", nargs='*', help="Data to send or convert")
@@ -102,27 +103,27 @@ if args.host or args.device:
102103
if args.convert:
103104
data = bytearray.fromhex(''.join(args.data))
104105
durations = to_microseconds(data)
105-
print format_durations(durations)
106+
print(format_durations(durations))
106107
if args.temperature:
107-
print dev.check_temperature()
108+
print(dev.check_temperature())
108109
if args.energy:
109-
print dev.get_energy()
110+
print(dev.get_energy())
110111
if args.sensors:
111112
try:
112113
data = dev.check_sensors()
113114
except:
114115
data = {}
115116
data['temperature'] = dev.check_temperature()
116117
for key in data:
117-
print "{} {}".format(key, data[key])
118+
print("{} {}".format(key, data[key]))
118119
if args.send:
119120
data = durations_to_broadlink(parse_durations(' '.join(args.data))) \
120121
if args.durations else bytearray.fromhex(''.join(args.data))
121122
dev.send_data(data)
122-
if args.learn or args.learnfile:
123+
if args.learn:
123124
dev.enter_learning()
124125
data = None
125-
print "Learning..."
126+
print("Learning...")
126127
timeout = 30
127128
while (data is None) and (timeout > 0):
128129
time.sleep(2)
@@ -133,51 +134,96 @@ if args.learn or args.learnfile:
133134
if args.durations \
134135
else ''.join(format(x, '02x') for x in bytearray(data))
135136
if args.learn:
136-
print learned
137+
print(learned)
137138
if args.learnfile:
138-
print "Saving to {}".format(args.learnfile)
139+
print("Saving to {}".format(args.learnfile))
139140
with open(args.learnfile, "w") as text_file:
140141
text_file.write(learned)
141142
else:
142-
print "No data received..."
143+
print("No data received...")
143144
if args.check:
144145
if dev.check_power():
145-
print '* ON *'
146+
print('* ON *')
146147
else:
147-
print '* OFF *'
148+
print('* OFF *')
148149
if args.checknl:
149150
if dev.check_nightlight():
150-
print '* ON *'
151+
print('* ON *')
151152
else:
152-
print '* OFF *'
153+
print('* OFF *')
153154
if args.turnon:
154155
dev.set_power(True)
155156
if dev.check_power():
156-
print '== Turned * ON * =='
157+
print('== Turned * ON * ==')
157158
else:
158-
print '!! Still OFF !!'
159+
print('!! Still OFF !!')
159160
if args.turnoff:
160161
dev.set_power(False)
161162
if dev.check_power():
162-
print '!! Still ON !!'
163+
print('!! Still ON !!')
163164
else:
164-
print '== Turned * OFF * =='
165+
print('== Turned * OFF * ==')
165166
if args.turnnlon:
166167
dev.set_nightlight(True)
167168
if dev.check_nightlight():
168-
print '== Turned * ON * =='
169+
print('== Turned * ON * ==')
169170
else:
170-
print '!! Still OFF !!'
171+
print('!! Still OFF !!')
171172
if args.turnnloff:
172173
dev.set_nightlight(False)
173174
if dev.check_nightlight():
174-
print '!! Still ON !!'
175+
print('!! Still ON !!')
175176
else:
176-
print '== Turned * OFF * =='
177+
print('== Turned * OFF * ==')
177178
if args.switch:
178179
if dev.check_power():
179180
dev.set_power(False)
180-
print '* Switch to OFF *'
181+
print('* Switch to OFF *')
181182
else:
182183
dev.set_power(True)
183-
print '* Switch to ON *'
184+
print('* Switch to ON *')
185+
if args.rfscanlearn:
186+
dev.sweep_frequency()
187+
print("Learning RF Frequency, press and hold the button to learn...")
188+
189+
timeout = 20
190+
191+
while (not dev.check_frequency()) and (timeout > 0):
192+
time.sleep(1)
193+
timeout -= 1
194+
195+
if timeout <= 0:
196+
print("RF Frequency not found")
197+
dev.cancel_sweep_frequency()
198+
exit(1)
199+
200+
print("Found RF Frequency - 1 of 2!")
201+
print("You can now let go of the button")
202+
203+
input("Press enter to continue...")
204+
205+
print("To complete learning, single press the button you want to learn")
206+
207+
dev.find_rf_packet()
208+
209+
data = None
210+
timeout = 20
211+
212+
while (data is None) and (timeout > 0):
213+
time.sleep(1)
214+
timeout -= 1
215+
data = dev.check_data()
216+
217+
if data:
218+
print("Found RF Frequency - 2 of 2!")
219+
learned = format_durations(to_microseconds(bytearray(data))) \
220+
if args.durations \
221+
else ''.join(format(x, '02x') for x in bytearray(data))
222+
if args.learnfile is None:
223+
print(learned)
224+
if args.learnfile is not None:
225+
print("Saving to {}".format(args.learnfile))
226+
with open(args.learnfile, "w") as text_file:
227+
text_file.write(learned)
228+
else:
229+
print("No data received...")

0 commit comments

Comments
 (0)