1
1
import base64
2
2
import re
3
+ from typing import List , Tuple
3
4
4
5
import click
6
+ try :
7
+ import heatshrink2
8
+ except :
9
+ heatshrink2 = None
10
+
5
11
from construct import (
6
12
Adapter ,
7
13
Array ,
@@ -87,33 +93,45 @@ def play_pronto(self, pronto: str, repeats: int = 1):
87
93
return self .play_raw (* self .pronto_to_raw (pronto , repeats ))
88
94
89
95
@classmethod
90
- def pronto_to_raw (cls , pronto : str , repeats : int = 1 ):
91
- """Play a Pronto Hex encoded IR command. Supports only raw Pronto format,
92
- starting with 0000.
96
+ def _parse_pronto (
97
+ cls , pronto : str
98
+ ) -> Tuple [List ["ProntoBurstPair" ], List ["ProntoBurstPair" ], int ]:
99
+ """Parses Pronto Hex encoded IR command and returns a tuple containing a list of
100
+ intro pairs, a list of repeat pairs and a signal carrier frequency."""
101
+ try :
102
+ pronto_data = Pronto .parse (bytearray .fromhex (pronto ))
103
+ except Exception as ex :
104
+ raise ChuangmiIrException ("Invalid Pronto command" ) from ex
105
+
106
+ return pronto_data .intro , pronto_data .repeat , int (round (pronto_data .frequency ))
107
+
108
+ @classmethod
109
+ def pronto_to_raw (cls , pronto : str , repeats : int = 1 ) -> Tuple [str , int ]:
110
+ """Takes a Pronto Hex encoded IR command and number of repeats and returns a
111
+ tuple containing a string encoded IR signal accepted by controller and
112
+ frequency. Supports only raw Pronto format, starting with 0000.
93
113
94
114
:param str pronto: Pronto Hex string.
95
115
:param int repeats: Number of extra signal repeats.
96
116
"""
117
+
97
118
if repeats < 0 :
98
119
raise ChuangmiIrException ("Invalid repeats value" )
99
120
100
- try :
101
- pronto_data = Pronto .parse (bytearray .fromhex (pronto ))
102
- except Exception as ex :
103
- raise ChuangmiIrException ("Invalid Pronto command" ) from ex
121
+ intro_pairs , repeat_pairs , frequency = cls ._parse_pronto (pronto )
104
122
105
- if len (pronto_data . intro ) == 0 :
123
+ if len (intro_pairs ) == 0 :
106
124
repeats += 1
107
125
108
126
times = set ()
109
- for pair in pronto_data . intro + pronto_data . repeat * (1 if repeats else 0 ):
127
+ for pair in intro_pairs + repeat_pairs * (1 if repeats else 0 ):
110
128
times .add (pair .pulse )
111
129
times .add (pair .gap )
112
130
113
131
times = sorted (times )
114
132
times_map = {t : idx for idx , t in enumerate (times )}
115
133
edge_pairs = []
116
- for pair in pronto_data . intro + pronto_data . repeat * repeats :
134
+ for pair in intro_pairs + repeat_pairs * repeats :
117
135
edge_pairs .append (
118
136
{"pulse" : times_map [pair .pulse ], "gap" : times_map [pair .gap ]}
119
137
)
@@ -127,7 +145,7 @@ def pronto_to_raw(cls, pronto: str, repeats: int = 1):
127
145
)
128
146
).decode ()
129
147
130
- return signal_code , int ( round ( pronto_data . frequency ))
148
+ return signal_code , frequency
131
149
132
150
@command (
133
151
click .argument ("command" , type = str ),
@@ -185,6 +203,79 @@ def get_indicator_led(self):
185
203
return self .send ("get_indicatorLamp" )
186
204
187
205
206
+ class ChuangmiRemote (ChuangmiIr ):
207
+ """Class representing new type of Chuangmi IR Remote Controller identified by model
208
+ "chuangmi-remote-h102a03_".
209
+
210
+ The new controller uses different format for learned IR commands, which actually is
211
+ the old format but with additional layer of compression.
212
+ """
213
+
214
+ @classmethod
215
+ def pronto_to_raw (cls , pronto : str , repeats : int = 1 ) -> Tuple [str , int ]:
216
+ """Takes a Pronto Hex encoded IR command and number of repeats and returns a
217
+ tuple containing a string encoded IR signal accepted by controller and
218
+ frequency. Supports only raw Pronto format, starting with 0000.
219
+
220
+ :raises ChuangmiIrException if heatshrink2 package is not installed.
221
+
222
+ :param str pronto: Pronto Hex string.
223
+ :param int repeats: Number of extra signal repeats.
224
+ """
225
+
226
+ if heatshrink2 is None :
227
+ raise ChuangmiIrException ("heatshrink2 library is missing" )
228
+ raw , frequency = super ().pronto_to_raw (pronto , repeats )
229
+ return (
230
+ base64 .b64encode (
231
+ heatshrink2 .encode ("learn{}" .format (raw ).encode ())
232
+ ).decode (),
233
+ frequency ,
234
+ )
235
+
236
+
237
+ class ChuangmiRemoteV2 (ChuangmiIr ):
238
+ """Class representing new type of Chuangmi IR Remote Controller identified by model
239
+ "chuangmi-remote-v2".
240
+
241
+ The new controller uses different format for learned IR commands, which compresses
242
+ an ASCII list of comma separated edge timings.
243
+ """
244
+
245
+ @classmethod
246
+ def pronto_to_raw (cls , pronto : str , repeats : int = 1 ) -> Tuple [str , int ]:
247
+ """Takes a Pronto Hex encoded IR command and number of repeats and returns a
248
+ tuple containing a string encoded IR signal accepted by controller and
249
+ frequency. Supports only raw Pronto format, starting with 0000.
250
+
251
+ :raises ChuangmiIrException if heatshrink package is not installed.
252
+
253
+ :param str pronto: Pronto Hex string.
254
+ :param int repeats: Number of extra signal repeats.
255
+ """
256
+
257
+ if heatshrink2 is None :
258
+ raise ChuangmiIrException ("heatshrink2 library is missing" )
259
+
260
+ if repeats < 0 :
261
+ raise ChuangmiIrException ("Invalid repeats value" )
262
+
263
+ intro_pairs , repeat_pairs , frequency = cls ._parse_pronto (pronto )
264
+
265
+ if len (intro_pairs ) == 0 :
266
+ repeats += 1
267
+
268
+ timings = []
269
+ for pair in intro_pairs + repeat_pairs * repeats :
270
+ timings .append (pair .pulse )
271
+ timings .append (pair .gap )
272
+ timings [- 1 ] = 0
273
+
274
+ timings = "{}\0 " .format ("," .join (map (str , timings ))).encode ()
275
+
276
+ return base64 .b64encode (heatshrink2 .encode (timings )).decode (), frequency
277
+
278
+
188
279
class ProntoPulseAdapter (Adapter ):
189
280
def _decode (self , obj , context , path ):
190
281
return int (obj * context ._ .modulation_period )
0 commit comments