-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathir_hub.py
More file actions
executable file
·705 lines (670 loc) · 34.7 KB
/
Copy pathir_hub.py
File metadata and controls
executable file
·705 lines (670 loc) · 34.7 KB
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
#!/usr/bin/env python3
# created with help of ChatGPT
import threading, struct, time, evdev, subprocess, pigpio, math, spidev, random, socket, os, requests
from datetime import datetime
out_pin = 22
ps3_bd_remote_mac = "00:1E:3D:10:CB:F0"
rgb_val = 10 # 0..255
standby_timeout = 1800 # 1/2 h standby time used for BD remote and radio socket
device_path = None
state = "IDLE"
prev_state = "IDLE"
led_is_on = False
mapping = None
pi = None
adb_shell = None
ps3_sleep_timer = None
ps3_battery_level = 5 # 0 to 5, assume full battery level on startup
rs_sleep_timer = None
alt_func = True
toggle_bit = 0
press_lock = threading.Lock()
ir_lock = threading.Lock()
state_map = {"1":"PROJECTOR", "2":"TV", "3":"LIGHT", "4":"DVD", "5":"SWITCH", "POWER":"IDLE"}
state_rgb = {"PROJECTOR":[0, 0, rgb_val], "TV":[0, rgb_val, 0], "LIGHT":[rgb_val, rgb_val, rgb_val],
"DVD":[rgb_val, rgb_val, 0], "SWITCH":[rgb_val, 0, rgb_val], "IDLE":[0, 0, 0]}
repeat_key = ("VOL+", "VOL-", "CH+", "CH-", "UP", "DOWN", "LEFT", "RIGHT")
# scancode to readable button name for Playstation BD remote
playstation_map = {22:"EJECT", 0:"1", 1:"2", 2:"3", 3:"4", 4:"5", 5:"6", 6:"7", 7:"8", 8:"9", 9:"0",
100:"AUIDO", 101:"ANGLE", 99:"SUBTITLE", 15:"CLEAR", 40:"TIME", 129:"RED", 130:"GREEN", 128:"BLUE",
131:"YELLOW", 112:"DISPLAY", 26:"TOPMENU", 64:"POPUP", 14:"RETURN", 1048668:"OPTIONS", 2097245:"BACK",
268435540:"UP", 1073741910:"DOWN", 2147483735:"LEFT", 536870997:"RIGHT", 2059:"OK", 8388703:"VIEW",
4194398:"X", 262234:"L1", 65624:"L2", 33554513:"L3", 524379:"R1", 131161:"R2", 67108946:"R3",
323:"PLAYSTATION", 16777296:"SELECT", 134217811:"START", 51:"REWIND", 50:"PLAY", 52:"FORWARD",
48:"PREVIOUS", 56:"STOP", 49:"NEXT", 96:"SLOWREWIND", 57:"PAUSE", 97:"SLOWFORWARD"}
playstation_convert = {"EJECT":"POWER", "L1":"LIGHT", "AUIDO":"MUTE", "POPUP":"MENU",
"SLOWREWIND":"SOURCE", "SLOWFORWARD":"SELECT", "OPTIONS":"VOL+", "VIEW":"VOL-", "BACK":"CH+", "X":"CH-",
"TOPMENU":"LIST", "PLAYSTATION":"HOME"}
# key mapping from universal remote to other device remote
map_TV = {"VOL+":"BAR VOL+", "VOL-":"BAR VOL-", "MUTE":"BAR MUTE", "DISPLAY":"BAR MUTE",
"UP":"TV UP", "DOWN":"TV DOWN", "LEFT":"TV LEFT", "RIGHT":"TV RIGHT", "OK":"TV OK",
"1":"TV 1", "2":"TV 2", "3":"TV 3", "4":"TV 4", "5":"TV 5", "6":"TV 6", "7":"TV 7", "8":"TV 8",
"9":"TV 9", "0":"TV 0", "RETURN":"TV RETURN", "MENU":"TV MENU", "CH+":"TV CH+", "CH-":"TV CH-",
"TEXT":"TV TEXT", "LIST":"TV LIST", "SOURCE":"TV SOURCE"}
map_DVD = {"VOL+":"BAR VOL+", "VOL-":"BAR VOL-", "MUTE":"BAR MUTE", "DISPLAY":"BAR MUTE",
"SOURCE":"BEAM SOURCE", "R1":"BEAM OK",
"UP":"DVD UP", "DOWN":"DVD DOWN", "LEFT":"DVD LEFT", "RIGHT":"DVD RIGHT", "OK":"DVD OK",
"1":"DVD 1", "2":"DVD 2", "3":"DVD 3", "4":"DVD 4", "5":"DVD 5", "6":"DVD 6", "7":"DVD 7", "8":"DVD 8",
"9":"DVD 9", "0":"DVD 0", "MENU":"DVD MENU", "PAUSE":"DVD PAUSE", "PREVIOUS":"DVD PREVIOUS", "NEXT":"DVD NEXT",
"RETURN":"DVD RETURN", "HOME":"DVD HOME", "TEXT":"DVD POPUPMENU", "LIST":"DVD OPTIONS",
"PLAY":"DVD PLAY", "FORWARD":"DVD FORWARD", "REWIND":"DVD REWIND", "STOP":"DVD STOP",
"REC":"DVD EJECT", "ANGLE":"DVD EJECT"}
# up:103,down:108,left:105,right:106,ok:96,back:158,menu:127,play:64,forward:208,rewind:168,apps:746 (input-event-codes.h)
# HOME: only via "adb shell input keyevent 3" -> implement as special case
map_PROJECTOR = {"VOL+":"BAR VOL+", "VOL-":"BAR VOL-", "MUTE":"BAR MUTE", "DISPLAY":"BAR MUTE",
"R2":"BAR SURROUNDON", "R3":"BAR SURROUNDOFF", "SOURCE":"BEAM SOURCE", "R1":"BEAM OK",
"UP":"FIRETVBEAM 103", "DOWN":"FIRETVBEAM 108", "LEFT":"FIRETVBEAM 105", "RIGHT":"FIRETVBEAM 106",
"OK":"FIRETVBEAM 96", "RETURN":"FIRETVBEAM 158", "MENU":"FIRETVBEAM 127", "REWIND":"FIRETVBEAM 168",
"PLAY":"FIRETVBEAM 164", "FORWARD":"FIRETVBEAM 208", "START":"FIRETVBEAM 746", "HOME":"FIRETVBEAM HOME",
"1":"FIRETVBEAM 1", "2":"FIRETVBEAM 2", "3":"FIRETVBEAM 3", "4":"FIRETVBEAM 4", "5":"FIRETVBEAM 5", "6":"FIRETVBEAM 6"}
map_LIGHT = {"UP":"LED BRIGHTER", "DOWN":"LED DIMMER", "LEFT":"LED DIMMER", "RIGHT":"LED BRIGHTER",
"VOL+":"LED BRIGHTER", "VOL-":"LED DIMMER", "CH+":"LED BRIGHTER", "CH-":"LED DIMMER",
"RED":"LED RED", "GREEN":"LED GREEN", "YELLOW":"LED YELLOW", "BLUE":"LED BLUE",
"DISPLAY":"LED RED1", "LIST":"LED CYAN", "MENU":"LED MAGENTA", "RETURN":"LED ORANGE",
"L2":"LED ORANGE1", "L3":"LED GREEN1", "R1":"LED GREY", "R2":"LED BLUE1", "R3":"LED MAGENTA1",
"OK":"LED WHITE", "PLAY":"LED SMOOTH"}
map_select = {"UP":"LED BRIGHTER", "DOWN":"LED DIMMER", "PLAY":"LED SMOOTH",
"VOL+":"BAR BASS+", "VOL-":"BAR BASS-", "CH+":"BAR TREB+", "CH-":"BAR TREB-",
"HOLD":"TV HDMI1", "STOP":"TV HDMI2", "TEXT":"TV HDMI3", "REC":"TV HDMI4",
"REWIND":"DVD AUDIO", "FORWARD":"DVD SUBTITLE", "SOURCE":"BEAM SOURCE", "R1":"BEAM OK",
"RED":"LED RED", "GREEN":"LED GREEN", "BLUE":"LED BLUE", "YELLOW":"LED YELLOW"}
def playstation_remote_input():
global ps3_sleep_timer, ps3_battery_level
(HCI_ACLDATA_PKT, HCI_EVENT_PKT) = (0x02, 0x04)
sock = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_RAW, 1)
sock.bind((0,))
sock.setsockopt(0, 2, struct.pack("LLLH", (1 << HCI_EVENT_PKT) | (1 << HCI_ACLDATA_PKT), 0xffffffff, 0xffffffff, 0))
while True:
try:
pkt = sock.recv(2048)
except socket.timeout:
pass
if pkt[0] == HCI_ACLDATA_PKT and pkt[-7:-2] == bytes.fromhex("ffffffffff"):
if len(pkt) > 21:
raw_battery_value = pkt[21] & 0x0F # assuming 0-5 value in the low 4 bits
if raw_battery_value > 0 and raw_battery_value <= 5:
ps3_battery_level = raw_battery_value
if pkt[-8] == 255 and pkt[-2] == 0:
sock.settimeout(None) # on button up even do blocking recv again
else:
value = int.from_bytes(pkt[11:-7], byteorder="big")
button_name = playstation_map.get(value, f"UNKNOWN ({value})")
button_name = playstation_convert.get(button_name, button_name)
threading.Thread(target=on_button_press, args=(button_name,)).start()
if button_name in repeat_key: # check if repeat operation is allowed for this key
if sock.gettimeout() == None:
sock.settimeout(0.7) # initial time-out for long press
elif sock.gettimeout() == 0.7:
sock.settimeout(0.2) # repeating auto presses time-out
# disconnect PS3 BD remote after some time of inactivity to avoid battery drain (goes into sleep if disconnected)
if ps3_sleep_timer is not None:
ps3_sleep_timer.cancel()
ps3_sleep_timer = threading.Timer(standby_timeout, lambda: subprocess.run(["bluetoothctl", "disconnect", ps3_bd_remote_mac]))
ps3_sleep_timer.start()
def socket_input():
socket_path = "/tmp/ir_hub_script.sock"
if os.path.exists(socket_path):
os.remove(socket_path)
server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
server_socket.bind(socket_path)
server_socket.listen(1)
while True:
client_socket, _ = server_socket.accept()
with client_socket:
while True:
data = client_socket.recv(1024)
if not data:
break
on_button_press(data.decode('utf-8').strip())
def on_button_press(button_name):
global state, prev_state, alt_func, mapping, led_is_on, rs_sleep_timer
with press_lock:
# special key: SELECT
if button_name == "SELECT" and state != "IDLE":
alt_func = not alt_func
# special key: LIGHT
if button_name == "LIGHT": # in general, toggles LED state and does not change color or brightness
if state == "IDLE": # special case 1: if in IDLE, do the same as button "3"
button_name = "3"
elif state == "LIGHT": # special case 2: if in LIGHT, do the same as button "POWER"
button_name = "POWER"
else:
ir_send_in_thread("LED POWEROFF", 1) if led_is_on else ir_send_in_thread("LED POWERON", 1)
led_is_on = not led_is_on
# special case: in LIGHT or SWITCH mode assign numbers to state selection
if state in ("LIGHT", "SWITCH") and button_name in state_map:
alt_func = True
# special keys: POWER and numbers with select state
if (alt_func or button_name == "POWER") and button_name in state_map:
alt_func = False # clear SELECT state
if state == "IDLE" and button_name != "POWER":
switch_radio_socket("On") # always send command to switch on (even if it is still on)
if rs_sleep_timer is not None and rs_sleep_timer.is_alive():
rs_sleep_timer.cancel() # socket was still On
else:
# during cold start delay indicate battery level of remote with color coding
if ps3_battery_level < 3:
for _ in range(6): # blinking LED loop (6 times 2*0.5 equals 6 s, ignoring set_rgb() delay)
set_rgb([255, 0, 0]) # RED at highest power to indicate low battery level of remote
time.sleep(0.5)
set_rgb([0, 0, 0]) # LED off
time.sleep(0.5)
else:
if ps3_battery_level < 4:
set_rgb([255, 0, 255]) # YELLOW at highest power to indicate mid battery level of remote
else:
set_rgb([0, 0, 255]) # GREEN at highest power to indicate high battery level of remote
time.sleep(6) # give devices some time to cold start
if state == state_map[button_name]:
print("Help action requested -> do transition again")
state = prev_state;
if button_name == "POWER":
mapping = None
alt_func = True # special case: per definition True, to be able to select mode right away
led_is_on = False
ir_send_in_thread("TV POWEROFF") # need to be first (avoid interference with IR signal)
if state in ("TV", "SWITCH"):
time.sleep(3) # if TV was on, add delay (avoid interference with IR signal)
if state in ("PROJECTOR", "DVD", "TV", "SWITCH"):
ir_send_in_thread("BAR POWER") # doesn't matter if incorrect, switches off after 15 minutes idle anyway
if state in ("PROJECTOR", "DVD"):
threading.Thread(target=switch_projector_off).start()
ir_send_in_thread("DVD POWEROFF")
ir_send_in_thread("LED POWEROFF")
# switch off radio socket after some time of inactivity
if rs_sleep_timer is not None:
rs_sleep_timer.cancel()
rs_sleep_timer = threading.Timer(standby_timeout, switch_radio_socket, args=["Off"])
rs_sleep_timer.start()
elif button_name == "1": # PROJECTOR -----
mapping = map_PROJECTOR
led_is_on = False
ir_send_in_thread("TV POWEROFF") # need to be first (avoid interference with IR signal)
if state in ("TV", "SWITCH"):
time.sleep(3) # if TV was on, add delay (avoid interference with IR signal)
threading.Thread(target=switch_bar_on, args=("BLUETOOTH",)).start()
if not state in ("DVD"):
threading.Thread(target=switch_projector_on_with_input_select, args=("PROJECTOR", "HDMI1",)).start()
else:
pass #ir_send_in_thread("BEAM HDMI1", 1)
ir_send_in_thread("LED POWEROFF")
ir_send_in_thread("DVD POWEROFF")
elif button_name == "2" or button_name == "5": # TV/SWITCH -----
mapping = map_TV
led_is_on = True
ir_send_in_thread("LED POWEROFF")
if state in ("PROJECTOR", "DVD"):
threading.Thread(target=switch_projector_off).start()
time.sleep(3) # if TV was on, add delay (avoid interference with IR signal)
ir_send_in_thread("DVD POWEROFF")
if button_name == "2":
if state in ("SWITCH"):
ir_send_in_thread("TV CH+", 1)
else:
threading.Thread(target=switch_tv_on, args=("TV", "CH+",)).start() # "TV TV" does not work correctly
if button_name == "5":
if state in ("TV"):
ir_send_in_thread("TV HDMI2")
else:
threading.Thread(target=switch_tv_on, args=("SWITCH", "HDMI2",)).start()
threading.Thread(target=switch_bar_on, args=("OPTICAL",)).start()
elif button_name == "3": # LIGHT -----
mapping = map_LIGHT
led_is_on = True
ir_send_in_thread("TV POWEROFF") # need to be first (avoid interference with IR signal)
if state in ("TV", "SWITCH"):
time.sleep(3) # if TV was on, add delay (avoid interference with IR signal)
if state in ("PROJECTOR", "DVD", "TV", "SWITCH"):
ir_send_in_thread("BAR POWER") # doesn't matter if incorrect, switches off after 15 minutes idle anyway
ir_send_in_thread("LED POWERON")
if state in ("PROJECTOR", "DVD"):
threading.Thread(target=switch_projector_off).start()
ir_send_in_thread("DVD POWEROFF")
ir_send_in_thread("LED WHITE") # initial state of LED
threading.Thread(target=led_max_brightness).start()
elif button_name == "4": # DVD -----
mapping = map_DVD
led_is_on = False
ir_send_in_thread("TV POWEROFF") # need to be first (avoid interference with IR signal)
if state in ("TV", "SWITCH"):
time.sleep(3) # if TV was on, add delay (avoid interference with IR signal)
ir_send_in_thread("DVD POWERON")
threading.Thread(target=switch_bar_on, args=("BLUETOOTH",)).start()
if not state in ("PROJECTOR"):
threading.Thread(target=switch_projector_on_with_input_select, args=("DVD", "HDMI2",)).start()
else:
pass #ir_send_in_thread("BEAM HDMI2", 1)
ir_send_in_thread("LED POWEROFF")
prev_state = state
state = state_map[button_name]
else:
if alt_func:
ir_send_in_thread(f"{map_select.get(button_name, 'UNKNOWN')}", 1)
elif mapping:
ir_send_in_thread(f"{mapping.get(button_name, 'UNKNOWN')}", 1)
if alt_func and state != "IDLE":
set_rgb([255, 255, 255]) # WHITE at highest power to indicate select mode
else:
set_rgb(state_rgb[state]) # always update RGB LED
def switch_tv_on(cur_state, input):
ir_send_in_thread("TV POWERON") # immediate attempt
time.sleep(5)
if state in (cur_state): # only switch input if exactly the same state
ir_send_in_thread(f"TV {input}", 1)
time.sleep(10) # after cold start, it takes long until it starts
if state in ("TV", "SWITCH"): # only continue if still in TV state
ir_send_in_thread("TV POWERON") # try again after a while
# after cold start, do not switch input since it may be anoying after warm start
def switch_projector_on_with_input_select(cur_state, input):
ir_send_in_thread("BEAM POWER")
#time.sleep(5)
#if state in (cur_state): # only continue if still in PROJECTOR state
# ir_send_in_thread(f"BEAM {input}", 1)
def switch_projector_off():
for i in range(7):
ir_send_in_thread("BEAM POWER", 1)
time.sleep(0.1)
def switch_bar_on(input):
ir_send_in_thread(f"BAR {input}") # powers it on, too
time.sleep(15)
ir_send_in_thread("BAR SURROUNDON") # we always want surround sound activated
def led_max_brightness():
for i in range(10):
ir_send_in_thread("LED BRIGHTER", 1)
time.sleep(0.3)
for i in range(3): # make it a bit dimmer since max brightness is too intense
ir_send_in_thread("LED DIMMER", 1)
time.sleep(0.3)
def ir_send_in_thread(button_name, repeat = 2):
threading.Thread(target=ir_send, args=(button_name, repeat,)).start()
def ir_send(button_name, repeat):
with ir_lock:
if not "UNKNOWN" in button_name:
print(f"IR send {button_name}")
device, command = button_name.strip().upper().split()
if device == "FIRETVBEAM":
send_keyevent(command)
else:
send_command(device, command, repeat)
def switch_radio_socket(state):
try:
print(f"switch_radio_socket: Power {state}")
requests.get("http://radiosocket/cm", params={"cmnd": f"Power {state}"})
except:
pass
def adb_connect(ip_address): # returns True on success
global adb_shell
while True:
try:
r = subprocess.run(["adb", "connect", f"{ip_address}:5555"], capture_output=True, text=True)
if "connected" in r.stdout or "already connected" in r.stdout:
if not adb_shell or adb_shell.poll() is not None:
terminate_adb_shell()
adb_shell = subprocess.Popen(["adb", "shell"],
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
else:
terminate_adb_shell()
except Exception as e:
terminate_adb_shell()
time.sleep(5) # check adb status every 5 seconds
def terminate_adb_shell():
global adb_shell
if adb_shell:
adb_shell.terminate()
adb_shell = None
def send_keyevent(keycode):
if adb_shell and adb_shell.poll() is None:
if keycode == "HOME": # special case for HOME
adb_shell.stdin.write(f"input keyevent 3\n")
elif keycode == "1": # special case to start ARD Mediathek
adb_shell.stdin.write("am start -n de.swr.ard.avp.mobile.android.amazon/.TvActivity\n")
elif keycode == "2": # special case to start Kodi
adb_shell.stdin.write("am start -n org.xbmc.kodi/.Splash\n")
elif keycode == "3": # special case to start SmartTube
adb_shell.stdin.write("am start -n org.smarttube.stable/com.liskovsoft.smartyoutubetv2.tv.ui.main.SplashActivity\n")
elif keycode == "4": # special case to start Youtube
adb_shell.stdin.write("am start -n com.amazon.firetv.youtube/dev.cobalt.app.MainActivity\n")
else:
adb_shell.stdin.write(f"sendevent /dev/input/event1 1 {keycode} 1 && sendevent /dev/input/event1 0 0 0 &&"
f"sendevent /dev/input/event1 1 {keycode} 0 && sendevent /dev/input/event1 0 0 0\n")
adb_shell.stdin.flush()
def set_rgb(rgb_leds):
try:
spi = spidev.SpiDev()
spi.open(bus=0, device=0)
spi.mode = 0b00
spi.max_speed_hz = 1000000
spi.writebytes(rgb_leds)
spi.close()
except:
pass
def carrier_frequency(out_pin, frequency, duty_cycle, duration, ir_signal):
one_cycle_time = 1_000_000.0 / frequency
on_duration = round(one_cycle_time * duty_cycle)
off_duration = round(one_cycle_time * (1.0 - duty_cycle))
total_cycles = round(duration / one_cycle_time)
for i in range(total_cycles):
ir_signal.append(pigpio.pulse(1 << out_pin, 0, on_duration))
ir_signal.append(pigpio.pulse(0, 1 << out_pin, off_duration))
def ir_sling(out_pin, frequency, duty_cycle, leading_pulse_duration, leading_gap_duration,
one_pulse, zero_pulse, one_gap, zero_gap, send_trailing_pulse, trailing_gap, code, repeat, rc6_mode):
# generate waveform
ir_signal = []
if rc6_mode:
T = 444 # base unit
carrier_frequency(out_pin, frequency, duty_cycle, 6 * T, ir_signal) # RC6 header: 6T mark, 2T space
ir_signal.append(pigpio.pulse(0, 0, 2 * T))
carrier_frequency(out_pin, frequency, duty_cycle, T, ir_signal) # start bit (1T mark, 1T space), high then low
ir_signal.append(pigpio.pulse(0, 0, T))
for i in range(3): # the mode bits mb2 ... mb0: low then high
ir_signal.append(pigpio.pulse(0, 0, T))
carrier_frequency(out_pin, frequency, duty_cycle, T, ir_signal)
if toggle_bit == 0: # header trailer bit is toggle bit (2T)
ir_signal.append(pigpio.pulse(0, 0, 2 * T))
carrier_frequency(out_pin, frequency, duty_cycle, 2 * T, ir_signal)
else:
carrier_frequency(out_pin, frequency, duty_cycle, 2 * T, ir_signal)
ir_signal.append(pigpio.pulse(0, 0, 2 * T))
# encode all bits using Manchester, where each bit is 2T
for i, bit in enumerate(code):
if bit == '0': # high then low
carrier_frequency(out_pin, frequency, duty_cycle, T, ir_signal)
ir_signal.append(pigpio.pulse(0, 0, T))
else: # low then high
ir_signal.append(pigpio.pulse(0, 0, T))
carrier_frequency(out_pin, frequency, duty_cycle, T, ir_signal)
ir_signal.append(pigpio.pulse(0, 0, 6 * T)) # signal free time is set to 6T, which is 2.666 ms
else:
carrier_frequency(out_pin, frequency, duty_cycle, leading_pulse_duration, ir_signal)
ir_signal.append(pigpio.pulse(0, 0, leading_gap_duration))
for char in code:
if char == '0':
carrier_frequency(out_pin, frequency, duty_cycle, zero_pulse, ir_signal)
ir_signal.append(pigpio.pulse(0, 0, zero_gap))
elif char == '1':
carrier_frequency(out_pin, frequency, duty_cycle, one_pulse, ir_signal)
ir_signal.append(pigpio.pulse(0, 0, one_gap))
if send_trailing_pulse:
carrier_frequency(out_pin, frequency, duty_cycle, one_pulse, ir_signal)
if trailing_gap:
ir_signal.append(pigpio.pulse(0, 0, trailing_gap))
# transmit waveform
pi = pigpio.pi()
if pi.connected:
pi.set_mode(out_pin, pigpio.OUTPUT)
pi.wave_clear()
pi.wave_add_generic(ir_signal)
wave_id = pi.wave_create()
if wave_id >= 0:
for i in range(repeat):
pi.wave_send_once(wave_id)
while pi.wave_tx_busy():
time.sleep(0.01)
pi.wave_delete(wave_id)
pi.stop()
def send_command(device, command, repeat=1):
global toggle_bit
# header 9079 4405 -> leadingPulseDuration, leadingGapDuration
# one 638 1612 -> onePulse, oneGap
# zero 638 473 -> zeroPulse, zeroGap
# ptrail 642 -> sendTrailingPulse
(leading_pulse_duration, leading_gap_duration) = (0, 0)
(one_pulse, zero_pulse) = (0, 0)
(one_gap, zero_gap) = (0, 0)
frequency = 38000
duty_cycle = 0.5
rc6_mode = False # default: no RC6
send_trailing_pulse = 1 # default: send trailing pulse
trailing_gap = 0
curkey = []
if device == "BAR": # Philips soundbar HTL2163B
# bits:21, flags:RC6|CONST_LENGTH, gap:107636, toggle_bit_mask:0x10000, rc6_mask:0x10000
frequency = 36000
rc6_mode = True
toggle_bit ^= 1
bar_keys = {
"POWER": "1110111111110011", # 0x0EEFF3
"COAX": "1110111111000110", # 0x0EEFC6
"OPTICAL": "1110111110010011", # 0x0EEF93
"AUX": "1110111111000111", # 0x0EEFC7
"AUDIOIN": "1110111101111001", # 0x0EEF79
"USB": "1110111110000001", # 0x0EEF81
"BLUETOOTH": "1110111110010110", # 0x0EEF96
"HDMIARC": "1110111101111000", # 0x0EEF78
"PREV": "1110111110100101", # 0x0EEFA5
"PLAY": "1110111111010011", # 0x0EEFD3
"NEXT": "1110111110100100", # 0x0EEFA4
"VOL+": "1110111111101111", # 0x0EEFEF
"VOL-": "1110111111101110", # 0x0EEFEE
"MUTE": "1110111111110010", # 0x0EEFF2
"TREB+": "1110111111100111", # 0x0EEFE7
"TREB-": "1110111111100110", # 0x0EEFE6
"BASS+": "1110111111101001", # 0x0EEFE9
"BASS-": "1110111111101000", # 0x0EEFE8
"SOUND": "1110111110101110", # 0x0EEFAE
"SURROUNDON": "1110111110101101", # 0x0EEFAD
"SURROUNDOFF": "1110111110101111", # 0x0EEFAF
"SYNC+": "1110111100000100", # 0x0EEF04
"SYNC-": "1110111100000101", # 0x0EEF05
"DIM": "1110111100010110", # 0x0EEF16
"NIGHT": "1110111100100011", # 0x0EEF23
}
curkey = bar_keys.get(command, [])
elif device == "BEAM": # Ultimea P20 projector
# bits:32, flags:SPACE_ENC|CONST_LENGTH, header:9044 4428
# one:619 1629, zero:619 507, ptrail:621, repeat:9017 2205
# gap: 107794, toggle_bit_mask:0x0
leading_pulse_duration = 9044
leading_gap_duration = 4428
one_pulse = 619
zero_pulse = 619
one_gap = 1629
zero_gap = 507
trailing_gap = 10000
beam_keys = {
"POWER": "00011101111010100011000011001111", # 0x1DEA30CF
"MUTE": "00011101111010101011000001001111", # 0x1DEAB04F
"REW": "00011101111010100011100011000111", # 0x1DEA38C7
"PLAY": "00011101111010100001100011100111", # 0x1DEA18E7
"FORW": "00011101111010101011100001000111", # 0x1DEAB847
"UP": "00011101111010100000100011110111", # 0x1DEA08F7
"LEFT": "00011101111010101000100001110111", # 0x1DEA8877
"OK": "00011101111010100100100010110111", # 0x1DEA48B7
"RIGHT": "00011101111010101100100000110111", # 0x1DEAC837
"DOWN": "00011101111010100010100011010111", # 0x1DEA28D7
"SOURCE": "00011101111010100111000010001111", # 0x1DEA708F
"MENU": "00011101111010100110100010010111", # 0x1DEA6897
"EXIT": "00011101111010101110100000010111", # 0x1DEAE817
"VOL-": "00011101111010100111100010000111", # 0x1DEA7887
"VOL+": "00011101111010101111100000000111", # 0x1DEAF807
}
curkey = beam_keys.get(command, [])
elif device == "LED": # Osram LED stribe
# flags:SPACE_ENC|CONST_LENGTH, header:9067 4439
# one:604 1645, zero:604 519, ptrail: 604, repeat:9066 2198
# gap: 108051, toggle_bit_mask:0x0
leading_pulse_duration = 9067
leading_gap_duration = 4439
one_pulse = 604
zero_pulse = 604
one_gap = 1645
zero_gap = 519
trailing_gap = 110000
led_keys = {
"POWEROFF": "10000000011111110110000110011110", # 0x807F619E
"POWERON": "10000000011111111110000100011110", # 0x807FE11E
"BRIGHTER": "10000000011111111010000101011110", # 0x807FA15E
"DIMMER": "10000000011111110010000111011110", # 0x807F21DE
"RED": "10000000011111111001000101101110", # 0x807F916E
"GREEN": "10000000011111110001000111101110", # 0x807F11EE
"BLUE": "10000000011111110101000110101110", # 0x807F51AE
"WHITE": "10000000011111111101000100101110", # 0x807FD12E
"FLASH": "10000000011111111111000100001110", # 0x807FF10E
"STROBE": "10000000011111111110100100010110", # 0x807FE916
"FADE": "10000000011111111101100100100110", # 0x807FD926
"SMOOTH": "10000000011111111100100100110110", # 0x807FC936
"RED1": "10000000011111111011000101001110", # 0x807FB14E
"ORANGE": "10000000011111111010100101010110", # 0x807FA956
"ORANGE1": "10000000011111111001100101100110", # 0x807F9966
"YELLOW": "10000000011111111000100101110110", # 0x807F8976
"GREEN1": "10000000011111110011000111001110", # 0x807F31CE
"CYAN": "10000000011111110010100111010110", # 0x807F29D6
"GREY": "10000000011111110001100111100110", # 0x807F19E6
"BLUE1": "10000000011111110111000110001110", # 0x807F718E
"BLUE2": "10000000011111110000100111110110", # 0x807F09F6
"MAGENTA": "10000000011111110110100110010110", # 0x807F6996
"MAGENTA1": "10000000011111110101100110100110", # 0x807F59A6
"MAGENTA2": "10000000011111110100100110110110", # 0x807F49B6
}
curkey = led_keys.get(command, [])
elif device == "TV": # Toshiba 42XV635D
# bits:32, flags:SPACE_ENC|CONST_LENGTH, header:9018 4440
# one: 619 1621, zero:619 507, ptrail: 620, repeat:9021 2185
# gap: 107862, toggle_bit_mask:0x0
leading_pulse_duration = 9000
leading_gap_duration = 4500
one_pulse = 619
zero_pulse = 619
one_gap = 1621
zero_gap = 507
tv_keys = {
"OK": "00000010111111011000010001111011", # 0x02FD847B
"UP": "00000010111111011001100001100111", # 0x02FD9867
"DOWN": "00000010111111011011100001000111", # 0x02FDB847
"RIGHT": "00000010111111010000001011111101", # 0x02FD02FD
"LEFT": "00000010111111010100001010111101", # 0x02FD42BD
"CH+": "00000010111111011101100000100111", # 0x02FDD827
"CH-": "00000010111111011111100000000111", # 0x02FDF807
"VOL+": "00000010111111010101100010100111", # 0x02FD58A7
"VOL-": "00000010111111010111100010000111", # 0x02FD7887
"MUTE": "00000010111111010000100011110111", # 0x02FD08F7
"1": "00000010111111011000000001111111", # 0x02FD807F
"2": "00000010111111010100000010111111", # 0x02FD40BF
"3": "00000010111111011100000000111111", # 0x02FDC03F
"4": "00000010111111010010000011011111", # 0x02FD20DF
"5": "00000010111111011010000001011111", # 0x02FDA05F
"6": "00000010111111010110000010011111", # 0x02FD609F
"7": "00000010111111011110000000011111", # 0x02FDE01F
"8": "00000010111111010001000011101111", # 0x02FD10EF
"9": "00000010111111011001000001101111", # 0x02FD906F
"0": "00000010111111010000000011111111", # 0x02FD00FF
"AD": "00000010111111010100011010111001", # 0x02FD46B9
"EXIT": "00000010111111011100001000111101", # 0x02FDC23D
"TEXT": "00000010111111011110100000010111", # 0x02FDE817
"SOURCE": "00000010111111010010100011010111", # 0x02FD28D7
"EXT1": "00000010111111011000110001110011",
"EXT2": "00000010111111010100110010110011",
"EXT3": "00000010111111011100110000110011",
"HDMI1": "00000010111111010001110011100011",
"HDMI2": "00000010111111011001110001100011",
"HDMI3": "00000010111111010101110010100011",
"HDMI4": "00000010111111011101110000100011",
"QUICK": "00000010111111011100011000111001", # 0x02FDC639
"GUIDE": "00000010111111011010001001011101", # 0x02FDA25D
"INFO": "00000010111111010110100010010111", # 0x02FD6897
"RETURN": "00000010111111010010011011011001", # 0x02FD26D9
"RED": "00000010111111010001001011101101", # 0x02FD12ED
"GREEN": "00000010111111011001001001101101", # 0x02FD926D
"YELLOW": "00000010111111010101001010101101", # 0x02FD52AD
"BLUE": "00000010111111011101001000101101", # 0x02FDD22D
"STEREO": "00000010111111011100100000110111", # 0x02FDC837
"SUBTITLE": "00000010111111010011000011001111", # 0x02FD30CF
"ASPECTRATIO": "00000010111111011001101001100101", # 0x02FD9A65
"MENU": "00000010111111011101101000100101", # 0x02FDDA25
"TV": "00000010111111010010001011011101", # 0x02FD22DD
"LIST": "00000010111111011110001000011101", # 0x02FDE21D
"PAUSE": "00000010111111010100010010111011", # 0x02FD44BB
"PREVIOUS": "00000010111111010010101011010101", # 0x02FD2AD5
"REWIND": "00000010111111011100101000110101", # 0x02FDCA35
"FORWARD": "00000010111111011011000001001111", # 0x02FDB04F
"NEXT": "00000010111111011010100001010111", # 0x02FDA857
"POWER": "00000010111111010100100010110111", # 0x02FD48B7
"POWEROFF": "00000010111111011111111000000001", # 0x02FDFE01
"POWERON": "00000010111111010111111010000001", # 0x02FD7E81
}
curkey = tv_keys.get(command, [])
elif device == "DVD":
# bits:12, flags:SPACE_ENC|CONST_LENGTH, header:2448 552
# one:1240 557, zero:640 557, post_data_bits:8, post_data: 0x47
# gap: 45087 -> 14400?, toggle_bit_mask:0x0
leading_pulse_duration = 2448
leading_gap_duration = 552
one_pulse = 1240
zero_pulse = 640
one_gap = 557
zero_gap = 557
send_trailing_pulse = 0
trailing_gap = 14400
dvd_keys = {
"POWER": "10101000101101000111", # 0xA8B 0x47
"POWEROFF": "11110100101101000111",
"POWERON": "01110100101101000111",
"OK": "10111100101101000111", # 0xBCB 0x47
"UP": "10011100101101000111", # 0x9CB 0x47
"DOWN": "01011100101101000111", # 0x5CB 0x47
"RIGHT": "00111100101101000111", # 0x3CB 0x47
"LEFT": "11011100101101000111", # 0xDCB 0x47
"AUDIO": "00100110101101000111", # 0x26B 0x47
"SUBTITLE": "11000110101101000111", # 0xC6B 0x47
"1": "00000000101101000111", # 0x00B 0x47
"2": "10000000101101000111", # 0x80B 0x47
"3": "01000000101101000111", # 0x40B 0x47
"4": "11000000101101000111", # 0xC0B 0x47
"5": "00100000101101000111", # 0x20B 0x47
"6": "10100000101101000111", # 0xA0B 0x47
"7": "01100000101101000111", # 0x60B 0x47
"8": "11100000101101000111", # 0xE0B 0x47
"9": "00010000101101000111", # 0x10B 0x47
"0": "10010000101101000111", # 0x90B 0x47
"RETURN": "11000010101101000111", # 0xC2B 0x47
"PLAY": "01011000101101000111", # 0x58B 0x47
"PAUSE": "10011000101101000111", # 0x98B 0x47
"STOP": "00011000101101000111", # 0x18B 0x47
"HOME": "01000010101101000111", # 0x42B 0x47
"POPUPMENU": "10010100101101000111", # 0x94B 0x47
"EJECT": "01101000101101000111", # 0x68B 0x47
"RED": "11100110101101000111", # 0xE6B 0x47
"GREEN": "00010110101101000111", # 0x16B 0x47
"YELLOW": "10010110101101000111", # 0x96B 0x47
"BLUE": "01100110101101000111", # 0x66B 0x47
"TOPMENU": "00110100101101000111", # 0x34B 0x47
"OPTIONS": "11111100101101000111", # 0xFCB 0x47
"PREVIOUS": "11101010101101000111", # 0xEAB 0x47
"NEXT": "01101010101101000111", # 0x6AB 0x47
"REWIND": "11011000101101000111", # 0xD8B 0x47
"FORWARD": "00111000101101000111", # 0x38B 0x47
"DISPLAY": "10000010101101000111", # 0x82B 0x47
"SEN": "00110010101101000111", # 0x32B 0x47
"HEART": "01111010101101000111", # 0x7AB 0x47
}
curkey = dvd_keys.get(command, [])
if curkey:
ir_sling(out_pin, frequency, duty_cycle,
leading_pulse_duration, leading_gap_duration,
one_pulse, zero_pulse, one_gap, zero_gap,
send_trailing_pulse, trailing_gap, curkey, repeat, rc6_mode)
if __name__ == '__main__':
subprocess.Popen(["sudo", "pigpiod", "-m"]) # start pigpiod using "Disable alerts (sampling)" for lower CPU usage
set_rgb(state_rgb[state]) # initial update of RGB LED (should be "IDLE" state)
threading.Thread(target=adb_connect, args=("firetv1",)).start()
threading.Thread(target=playstation_remote_input).start()
threading.Thread(target=socket_input).start()
# sudo systemctl enable ir_hub.service -----------------------------------------
# ir_hub.service:
# [Unit]
# Description=Infraed hub with Python in mypitools
# After=network.target
#
# [Service]
# ExecStart=/usr/bin/python3 /home/pi/mypitools/ir_hub.py
# WorkingDirectory=/home/pi/mypitools/
# Restart=always
# User=root
# Environment=PYTHONUNBUFFERED=1
#
# [Install]
# WantedBy=multi-user.target
#
# send_ir.sh: ------------------------------------------------------------------
# #!/bin/bash
# echo -n "$1" | sudo socat - UNIX-CONNECT:/tmp/ir_hub_script.sock