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