11import base64
22import re
3+ from typing import List , Tuple
34
45import click
6+
7+ try :
8+ import heatshrink2
9+ except ModuleNotFoundError :
10+ heatshrink2 = None
11+
512from construct import (
613 Adapter ,
714 Array ,
@@ -87,33 +94,45 @@ def play_pronto(self, pronto: str, repeats: int = 1):
8794 return self .play_raw (* self .pronto_to_raw (pronto , repeats ))
8895
8996 @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.
93114
94115 :param str pronto: Pronto Hex string.
95116 :param int repeats: Number of extra signal repeats.
96117 """
118+
97119 if repeats < 0 :
98120 raise ChuangmiIrException ("Invalid repeats value" )
99121
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 )
104123
105- if len (pronto_data . intro ) == 0 :
124+ if len (intro_pairs ) == 0 :
106125 repeats += 1
107126
108127 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 ):
110129 times .add (pair .pulse )
111130 times .add (pair .gap )
112131
113132 times = sorted (times )
114133 times_map = {t : idx for idx , t in enumerate (times )}
115134 edge_pairs = []
116- for pair in pronto_data . intro + pronto_data . repeat * repeats :
135+ for pair in intro_pairs + repeat_pairs * repeats :
117136 edge_pairs .append (
118137 {"pulse" : times_map [pair .pulse ], "gap" : times_map [pair .gap ]}
119138 )
@@ -127,7 +146,7 @@ def pronto_to_raw(cls, pronto: str, repeats: int = 1):
127146 )
128147 ).decode ()
129148
130- return signal_code , int ( round ( pronto_data . frequency ))
149+ return signal_code , frequency
131150
132151 @command (
133152 click .argument ("command" , type = str ),
@@ -185,6 +204,79 @@ def get_indicator_led(self):
185204 return self .send ("get_indicatorLamp" )
186205
187206
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+
188280class ProntoPulseAdapter (Adapter ):
189281 def _decode (self , obj , context , path ):
190282 return int (obj * context ._ .modulation_period )
0 commit comments