From d8d6758c3370f4adc636aed7b935a492754f6062 Mon Sep 17 00:00:00 2001 From: Peter Hinch Date: Wed, 11 Mar 2020 11:39:58 +0000 Subject: [PATCH] RMT tested. --- TRANSMITTER.md | 88 +++++++++++++++++++-------------------- images/circuits.fzz | Bin 2933 -> 3603 bytes images/gate.png | Bin 0 -> 39059 bytes ir_rx/test.py | 2 +- ir_tx/__init__.py | 99 ++++++++++++++++++-------------------------- ir_tx/nec.py | 5 +-- ir_tx/philips.py | 5 --- ir_tx/test.py | 29 +++++++------ 8 files changed, 102 insertions(+), 126 deletions(-) create mode 100644 images/gate.png diff --git a/TRANSMITTER.md b/TRANSMITTER.md index 88c3ed7..8a83314 100644 --- a/TRANSMITTER.md +++ b/TRANSMITTER.md @@ -5,7 +5,7 @@ # 1. Hardware Requirements The transmitter requires a Pyboard 1.x (not Lite), a Pyboard D or an ESP32. -Output is via an IR LED which will need a transistor to provide sufficient +Output is via an IR LED which needs a simple circuit to provide sufficient current. Typically these need 50-100mA of drive to achieve reasonable range and data integrity. A suitable 940nm LED is [this one](https://www.adafruit.com/product/387). @@ -13,10 +13,10 @@ On the Pyboard the transmitter test script assumes pin X1 for IR output. It can be changed, but it must support Timer 2 channel 1. Pins for pushbutton inputs are arbitrary: X3 and X4 are used. The driver uses timers 2 and 5. -On ESP32 pin 23 is used for IR output and pins 18 and 19 for pushbuttons. The -ESP32 solution has limitations discussed in [section 5.2](./TRANSMITTER.md#52-esp32). +On ESP32 the demo uses pins 21 and 23 for IR output and pins 18 and 19 for +pushbuttons. These pins may be changed. -## 1.1 Wiring +## 1.1 Pyboard Wiring I use the following circuit which delivers just under 40mA to the diode. R2 may be reduced for higher current. @@ -33,20 +33,40 @@ The driver assumes circuits as shown. Here the carrier "off" state is 0V, which is the driver default. If using a circuit where "off" is required to be 3.3V, the constant `_SPACE` in `ir_tx.__init__.py` should be changed to 100. -# 2. Installation +## 1.2 ESP32 Wiring -The transmitter is a Python package. This minimises RAM usage: applications -only import the device driver for the protocol in use. +The ESP32 RMT device does not currently support the carrier option. A simple +hardware gate is required to turn the IR LED on when both the carrier pin and +the RMT pin are high. A suitable circuit is below. +![Image](images/gate.png) -Copy the following to the target filesystem: - 1. `ir_tx` Directory and contents. +The transistor type is not critical. A gate could be built with two similarly +connected N-channel MOSFETS. The 1KΩ resistors would not be required. The +MOSFETS would require a low RDSon at Vgs == 3.3V. A ZVN4210A seems suitable +but I haven't tried it. + +# 2. Dependencies and installation + +## 2.1 Dependencies The device driver has no dependencies. +On ESP32 a firmware version >= V1.12 is required. The Loboris port is not +supported owing to the need for the RMT device. + The demo program requires `uasyncio` from the official library and `aswitch.py` -from [this repo](https://github.com/peterhinch/micropython-async). The demo is -of a 2-button remote controller with auto-repeat. It may be run by issuing: +from [this repo](https://github.com/peterhinch/micropython-async). + +## 2.2 Installation + +The transmitter is a Python package. This minimises RAM usage: applications +only import the device driver for the protocol in use. +Copy the following to the target filesystem: + 1. `ir_tx` Directory and contents. + +The demo is of a 2-button remote controller with auto-repeat. It may be run by +issuing: ```python from ir_tx.test import test ``` @@ -61,10 +81,6 @@ It implements a class for each supported protocol, namely `NEC`, `SONY_12`, common abstract base class in `__init__.py`. The application instantiates the appropriate class and calls the `transmit` method to send data. -The ESP32 platform is marginal in this application because of imprecision in -its timing. The Philips protocols are unsupported as they require unachievable -levels of precision. Test results are discussed [here](./TRANSMITTER.md#52-esp32). - #### Common to all classes Constructor args: @@ -112,9 +128,6 @@ value. #### Philips classes -These are only supported on Pyboard hosts. An `RuntimeError` will be thrown on -an attempt to instantiate a Philips class on an ESP32. - The RC-5 protocol supports a 5 bit address and 6 or 7 bit (RC5X) data. The driver uses the appropriate mode depending on the `data` value provided. @@ -124,17 +137,15 @@ Both send a `toggle` bit which remains constant if a button is held down, but changes when the button is released. The application should implement this behaviour, setting the `toggle` arg of `.transmit` to 0 or 1 as required. -# 4. Test results - -# 5. Principle of operation +# 4. Principle of operation -## 5.1 Pyboard +## 4.1 Pyboard The classes inherit from the abstract base class `IR`. This has an array `.arr` to contain the duration (in μs) of each carrier on or off period. The `transmit` method calls a `tx` method of the subclass which populates this array. This is done by two methods of the base class, `.append` and `.add`. The -former takes a list of times (in μs) and appends them to the array. A bound +former takes a list of times (in ) and appends them to the array. A bound variable `.carrier` keeps track of the notional on/off state of the carrier: this is required for bi-phase (manchester) codings. @@ -155,32 +166,17 @@ The duty ratio is changed by the Timer 5 callback `._cb`. This retrieves the next duration from the array. If it is not `STOP` it toggles the duty cycle and re-initialises T5 for the new duration. -## 5.2 ESP32 - -This is something of a hack because my drivers work with standard firmware. - -A much better solution will be possible when the `esp32.RMT` class supports the -`carrier` option. A fork supporting this is -[here](https://github.com/mattytrentini/micropython). You may want to adapt the -base class to use this fork: it should be easy and would produce a solution -capable of handling all protocols. - -A consequence of this hack is that timing is imprecise. In testing NEC -protocols were reliable. Sony delivered some erroneous bitsreams but may be -usable. Philips protocols require timing precision which is unachievable; these -are unsupported. +## 4.2 ESP32 -The ABC stores durations in Hz rather than in μs. This is because the `period` -arg of `Timer.init` expects an integer number of ms. Passing a `freq` value -enables slightly higher resolution timing. In practice timing lacks precision -with the code having a hack which subtracts a nominal amount from each value to -compensate for the typical level of overrun. +The carrier is output continuously at the specified duty ratio. A pulse train +generated by the RMT instance drives a hardware gate such that the IR LED is +lit only when both carrier and RMT are high. -The carrier is generated by PWM instance `.pwm` with its duty cycle controlled -by software timer `._tim` in a similar way to the Pyboard Timer 5 described -above. The ESP32 duty value is in range 0-1023 as against 0-100 on the Pyboard. +The carrier is generated by PWM instance `.pwm` running continuously. The ABC +constructor converts the 0-100 duty ratio specified by the subclass to the +0-1023 range used by ESP32. -# 6. References +# 5. References [General information about IR](https://www.sbprojects.net/knowledge/ir/) diff --git a/images/circuits.fzz b/images/circuits.fzz index 9ad4333f0e9e99873c79b027a9a509527c339da7..ad48376633883df0873fd8ea95d6eae757485228 100644 GIT binary patch delta 3599 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 V$p8Qg0001!Mhhth#0>xd002V*@U#E` delta 2918 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 Q*m0BI4KD^J3jhEB09gsE@c;k- diff --git a/images/gate.png b/images/gate.png new file mode 100644 index 0000000000000000000000000000000000000000..31b11f2b02da16b4caf0af034cf143bf1da33c3d GIT binary patch literal 39059 zcmc$`bySsW*EdQkf+$KUNGQ@>0+LcnONmGdNJxitBhn%w(jcuMARyh+UDDkk-3{No z-TV38_c`PIasE4d3s|NNHD)&v$gTr>|;mz%@?Uy$({sZ z-QuZN0>A8fu4&&;nbLbCo<@h3?5$0Rb5D(ckklJ5)dvg9+tco_$3?+%)X2!)wV-)KFYqhcfoqUPG%RZtg-MDvPj>FFn6aQu3VrY|D=b2COyQKnjhRjeb4Sx&%FOLhMD@Jg za6B+R`A@M{S~h*mPwvuoniR!IVc8lAwXQ?tuViejMH8hfWIA0#gQn{-iWhE5p@*4@ zC4qx7w5U;TWQ&R9a?+eGZR*2%w-D(Z{f9?j|ZvhLkcMprCP=$9{)>(v+MS<#tzz8@3f@n*a~ zhKJMZRJkVQM$6JT?=ID@4j1gmB?`JcI2d=Jn4X?iqmsDWDHDkNs*;k@6FIs0qs2s{ zrS2rxoemzlz!?w0yLY`pNO^2d53N!13kuZB?C2|<_ZV_M`8GfP`?MFu7INVmtM zw!IK*IkV-s^!uAKyCI#pyUmIheCEaw(#JlRaKCDmKWJ%bk&R;WYHof~=CHAzNV}2Q zesQ+pVfW{^)ZZ80U44s3oSMm?AbiHfZ90(pIFvl9!|+-53)QK2TYluu{v{>6wLTj6L2f}^@@n^R)fcN$|Y zF`3fooZ8{gsckLa6kyFs*s2V?p3?J0!>rPF&__Y_O_!Efox$(L8qbMR#=y)Id({vx zMxu-0Y_;_k`fsu^yVklsBwo@Yw{$UAJu#o4p_K&ER9%p>UMGF@K|QhQij;9D5f`1u6`_-`Efs0WNm3aWgyG42V1dO@C?4|>G$fE{FvK08uF8BZJ|t?# zX#dDC++wBCn2XI}U9;O2l|T4fa(+iih)G+9HOK6I)i#xRo$Lz<^g`5kD5^p}wVn!X zA=Kt_)2Z2Ew)Ud_9$43|jS-^~65RxW7;B4vg%Mz_6cp3lDRk*dB-uL z<;pM|$^=jjRa7Gn`Q&+MQf{R5;B5|BgnAEVU0wT?v^S2zAt3>dzZuQ z^1hH3gZivP=m@iR_-5=YH%I5UW_G?AFZGo-TbHrlsx`QJT6Vx76lW32a>75i9{z}i zlbqUWMO&3{%*3RmprF>U=2h{mv=nuDJ`TFq$(`N-0=bbG%>~NvD?#ds?a8GJYT>7#NR=9++&j zUV36cIh(~06-@K1l3eKu&95xo2Mg=`UQG7?c@Nzn^}$e!jN5|P;scC+199WTxzZ|F4UIxNm67m&6{xz&A`8MghJV5^DiwHe=| z(m(y*zBc0NUBMo6p)~I}dZH07lX%q^ZzeADeZI;=Q>s%tn)y1aZ!-*PWAp(^PgtJb z5M65Z^zy%h6BhkQe8dO+CMP?4m}Ce^SXh|i(n?>3LWW|BB&Iu!eB4sv;(GbUzJld& zKDC129)XVA9^oJ1&XZRL25i&WJSIb%_V=i$N{<#|o%V-~q!bjcHyhnh^FxZ&VEm^~ z1P{zd-X;^czib*VFdF`8f?wx%z3H%6tQ9KQ?uEx_k@@c&tyuZ^hw#p|o$>rmr&~?Y z58p5p7YTq~gL z8yFbi5fY|t_~D`6^|ep8~p;3^nyx9!5+*ZJ}>D(XqG#keutMu$l^hnj7|W2BH^ZShRH9gx*H1r50! zj9;#S&z~zTKhuizj~jSRp%g|(+w-3o4+gR5_f`hddYwL(4#3L$2jJ88=IRLlfo?YE z>T!NVi_)K|TK?i&0OS1zS_aZCBwOUYy~9r!-IyWH0hFCB1i5jjo~uyZ(U7i2Fs;IQnvKwyq) z*0ai`9S1d^rrGJ7@^!uKxt6Q9*uFF$0R)TYc!hm-yqBWa5Gb;)OuEn+FJo{2fceGG zsoKjFOg2vCQ~X0i7pMIwIuAyA(`2IA^)Ee$4Y@}4@Nk2%f<)+O4*&IFj$G^m zb2Jh)XJ|ny(#FPgERjJyDUUggI^$%>gZY@5L+F*$HzzO7BVm;bocCVGbMy~>dE|3A zAF1>8X%ZSrqQ^NOyshreLPu&w2B&Tf%FUZMyF0bM9Vjhi=r!Pb48DCEXsax~i% zir)R>r5K90w>MgR^7_Dc^*TVPP5>s^Z(W_6n`5(EdaCWbO#PWzqw!$UqYFT8W0&wn zd$KPMHdFQMeui|pSV;hm+j0kv0s<$i1p;TgeaJQhtSofAL?!wDj#K}&ELe_k@yCxj z0n&Ola%W|}%Oui1%2m7{J5vaD%r7G%S0* zDpH|*X3_c;d|UWqbhH#4hNVdlkt))0WhJGk&3%mhwP7(J8>b4&-9M9y8=5E-lA(&j zdB3XKKa{6Wd;9inf#ap>)3)Ea9;9$qr(>6O`YJVeF6_$3<>83j7INifRLj}U74w7| zid6q_1)=%G{alZ;*jk^$36h+J%9Qaxphw2T@Wr)%JA>gm5#(9UeL= zDJwZb*WtBU#mKFCK;kKB*nh;w8K0EPRM(6y9lbFmBEs+rc}QASztmwWwd~eYEQ` zL+?TAc&cKN+ooGIr;%8F{oWvPamlTE&qqwpOYlOI4=du*N$@!jH;R}7X%H;f-6D8uG4y# zgPqI?COuAY1CyV^{=Iiu?g0vSC|RBR>aEf6k65|S!#Z(Ep7OBdG!Fs(nVD+|uWxMR zuxr-+^$UBF`06Dr+-vt}?p^g`#PVZy@Cd~!&a;`94wj@6N(_Zg_QSDO!1-Xcv*ui( zm-pPh>ijhc`ibzoaIk~%1v!_T{$#?q*>FDJWKsurmRk7Q?%^m&;>^pIWXy_-b$*n$ zz8JQvLy9AX<*`n-_-DBG&z=$GhPsFFOuB|AiTR;f4aRpY_h%8;`BlooX*$|1U%vCu z(K95rxO-{;5=zD1fJX2wcJCh_rbI8U6UDzzfJyl-T7L{WTzup&H*(s?2x=BTE+>-%Vw^*8IG5k0SRE%o?K5;R=j zMB&kXnZmWEpi2lxr1D`Qv}9Jk+uzQOu1xgYPuSeFvbPv**S~P)r8E!G455EpVgLXp z1=@~m{KJR>qps~nQcD8$PtC1*9exxX$Ng$Yr-g%GM+#^$h|gcmb|;C+#(Z*#+DePm zW_Y;Zw7WWtKU>O5N)sNSVObfWoFT9EJHJcl#LUaq@zJA4Q@c|;M^B`rs2)5h>UI=+ zceRD5eTc&2^pUgk!>z5Y^QKGl+zX^U7O5bleAljwDSNY`RA9(_VI%0gt6T4XLt-Jy zp>u1aYoU$wmsyP`Do}aPuV3k^juuF5Y;3R!7l<+{>LODfhb};YqdLYNuDt) zbXtW3CL2LbhEfp;ENgYi;9Fk9W;A?!(JkS$#DeiH^q^H}kr&=<}$EkwR0>H-CPMMs}Ef z-MRD74y&0=qW}aOm8@qW(0S1gS@(I}Pk9L$7dGZUv+A5z@GTI=fBpIu&FLD2P+WhO z+RjdwFf<0;nc3O1V(P2#Aau=ygDWd}d3gp#M#NDG5fKsqcedrqqxSBQlCE6Lgz+d$ z>zwP5-IlAM?T5;a!ni|0d+*H@SezltUgFl`!?$1|_ zK5PBZMGY0)za@z8Y`t%G^V_#?0taKZ#D(8GKD*FabtMP_hFLth{lM(jhtVDD^6{h3 z$}CnPK6{!99n;MoLS1@!n?NS=9Jlm7GpUQ_YF8rtes5(`5;`@7+wKj}jedeUWY`hY z^Ie@20C1x>CXVsI_uFtR5c&h%kI3I`q5XF2XJ(g)E*JKd`=ML+uI-Gj*?Z)2;r$h) zMkNl`SwtzF-*^=nwA=)YdrQE84*Ds|9RbJ9ze-LdnU=+h=ov%t^4;tAIpw zau-CY%I5aIg2v@CafHC6iAtB{=HH_2%{uNWrM~jjY76a!*_SVt*vv7Xe z%(sR`L^MnYx}ZGI)5C<*RrU21sBKRGOgbLTM-ErH@n>d!gkJ$|OB~h)#qD@^dUcr7 z^|*!he6Z;~nae~8E(uqS0Y@GnKG(h-=eIHGTPwNMz1bQ(58wO|4=R6glZdGCaBCLl zbj)!!q`s+%6F39VlR7z`adG${PVhc_ZpU1JORkb|`NQcUcbxK)(Q=DigJ;DFx_mx7 zltc}pM?Sz;s3d(~9-+f$c9=Lq>nSh)q&*I2vIKO$AERrh2h|s8ppr)JB@oZH&?e5k zBenSA)F=6gQSGyk+cBH|9Dx0LPvX# ztB`8s5KFOITR#7rfNYbP&tYrb0@@OhLxEixX|G29dCc6wM85cO@nOsIa9M+Wnc$0} z@idLopR(oYhpuJy`;LUnRHI2UW^#Q$naY)IODkj2O-4*}E7MU&YP`_7Cna1HXcHVo z(SO2`e9gy)Ir@pucEQ-Q2-XTM7xd<*oUt7wUnPyGGW_CD-?!LO2J+41&^YKss`6U%j*jZ2U-Q5l-UIH!A}s@Sn%LLqgF4Y?F~_ zP9y9Nr^U|r5!UL{s99NLvBn_eE){ zXyl?z=eSmonyxl0372sVP(fBloryg|*pQ2r#G5u!dHBf$t?lgwt)J-6C*g3mX;=oQ zTzI0o0*{E)T%s&?I2h6oMI|0A57&+_iGeDmi0uZ)57?;Zb>Y(wdHPM;ux++fE>LCT z4yRucmI*}mmbb0+M=)zVbABW0ptSmy?v;w_d`hSg+EnRc!ffH7wtn}QM-@BkE@~=E zD4A-x?uwQC%l!da2<=a6PnHP64o0)-ljG5{PHMAduPm=V_;cynom(Fc2xnZ83utu( z8ikpTdz<4eeYo(IC#N`j>#1tAfj%IdNQaG=I~44Wp>yxlH#D$YjPaHo$kMS@H_g60 z+?+NWFWdV#vFQ5u{bnc|Fdx zN(3%e$4fO?C>`3x;6nd-A}D1(w>b?rVR^O|6Fu?`J4l=l*0rWyrfsSv#qv9_0B}gt zZ+ahj>h7^I&J3P~C-CVfx$*=!PD-VCY2pB?${jXXHoH4KS{d!Y*l;~RTHIY5J;jbFy9h95+_^8P0C;(6YbTfiMwJcSX2ik5Mi=7j6^G6?sWo4fc@t_jA3c;`G z-33EPn!R{b&dQ4IgT~Ua(7vVnIp0C|E1*lA<*RVNumyU`P~`Nj+CJaW%tJG^V&h{W z?%;Po&IEBgGXp~)HX(!NuF;Jd*LL~h%YIo?>?a~tB?4vj9kX^WKcYHrg?N<;FKvvG zW9BC7miad|>0DbZeS>fC08^kyLDjlU5Tkteh-KEw2`sj+ndvg8V{S@`ZcX!B$#j`4 zCs*edEWlhFwyX%ep3#Q?!;OZYF$h2lhwXqFgUZ*MYI3Ky5;%*T%i%2oyUo>|<+$!~ zTj-$&POIJ-4Wvmuf^S zPQTZ|Gx@`|>Dt4wzV{?NlkM9&?$bWkX?qs8f_Uwm0(ib?bj2qY44Xy%)cj&$NBrwU zOms?v_>yfh@5bn7#2GmK$;7YtAj@L1NFY$Ogd4FTIc(#ei!qhCjkdO0-ynO5kb*mY zqy@uI{3Q~p(CBaT)k|uDcD8?D`h#Vwuiy^i608jKX_DVFPg1)hqpJG4KeOTd z?BuLZGSn^pCF^ynAsg#SFNFkwfWGC5Zqge?uL(~UI>e-8sJ=VDVJJ5BD{$JuX$tC;_8JTfo zJ3g0dhLA*t7vV+9KUpQ5=;Zhml?90xJTaVCJ)KO}^DbedtI{cU&X-y;a^ z;MyJD8qjK%M+zAY3%F2|?LG)jotpokqigmj%PmORVnNm&fn! z?#{@|V2L2op~&_nryT9dhYsU!h#8-Kr&|bqdl)q4O!*eeA6&edJ!h}6M{)21?`O7Z zh6@_vqVb?g;h_=`><%vN-o1NQw`Y+aLsXN2FPQ#6SM%T3_P_p>1tt5~h8Igg`d3_- z*WRwlreh#Kl_EAGx+YP``Bb{}XtJz6V>B?Tt8#0IkD&8yUmm_T?#(}5Iyt3Do{Q`c zvagx%{>M3=R&@o3{KG4U>21d=9I4JmYG z^J~k{?wfu!rq6 z)%R;EFZT|_g(5z8*vlmG;qRzVvMAe1GblC0c1!bARJoJQo`HiNnv7?Gd&SWV1^v=L zmO7Q>cv04{@h`InHnPv-MUiJbT7Ye6fFa8z7-6SMMpFdx}NJ**9RA3eH-wA`g z)t&e#(ehkWlj*;#_y0EH1#Yv*Vp(@+R5-FHx*yY{fPNtz!O&CX=JL>P$#a^5xEi5s z(Bu6;3i}un(;918B?Xu+h|1g=7x80fV!p%~slkZ&tYH_(G=THkoHF@6O;qx9`c?CGC;&i(Wv1x!c z6K0IId7)qS4+uyC^9ih(DJ$Nm1is(~B!Ci3;P59o>Yry@R2>;!TwKN_sUNkhUK2Km zj*sX_^sq1hL}RxyfGeT61Y93r)GPqG=S;@}Hy&wHAZ}>=8+}-Evx$n^u^p12T@O^_ zGMM8Q^nO#Oy_i-6rzD8A3cTeE@X1B2)0lE@AfIp$M9$Y%0GK#I_5wew8Tis^u4=Zk zzSo3?qa!bhy1Kd=w}SN=vQouKE8v@VeApCtVwU`m2g@Gmh|rp zh2l`XMKyO49A~b;p8(fB5RJ0baxc|PZ=+bvhVbNrQom}?53lj%%a^Q#tda-FN`Kh1(B>QygUEmigt*SSJfkJG&Otb^Gpjh8=lJ7F7yG)mF6yZSvsBZpl zFA>S+5X$g1Vxod*T^W=v3uBVgbN-1sKR)LrEPD0ZuGQ95;R87hx@_IB36FP|#en#* z{5g%u%xsV23y-xZ3jkC0C}XjZ+vVJn)^?%2*07)_Pyg{BP(qfrfj%b#3=-I^LHE`E z7X9(#uRuaZ5Z^B+B_*k3S>tG}Z-^TkPmQ9Rmq_t^d}vu$)475r`=>FaMl(>>zhiebL9I+7BjY zb|uu57eJ%AMHn@TzCQpB#~dh(#JiUE_TLX3*1w~UG>1{`R2n*z6=LELn-T>{l!K^P zcnO!p7$mfj)1#fUov)dhf;;V;F~ep9>t!n}*`Se(Fc*&{$Mf0J=zIs5UH`CyKgmlhAiu@>>0izF=s@kEyx>9GFvi+xk0cs+Q@#|scFn?W-k zNSB=;QkjhWq=(a!{>bMNvb>n0pr(A_UoY_i@j?mI8MIu}n8rSzOCLXcr~%s=_7x72 z%Lx@eFS$ti0aD%^kQY5rMG${wb+VcwrKcJ0%$YUhi6lWdD&6Vi=X2QyW6A6#Qj~^% z5<=aYsN~nqRjYnOZAS%w#kxN&Eo}=#guVhJ1-ParSb~6{pcI)%maAkBf7YA=)BK&K z7CPS+N&N834yYBpu7_-0f(LOPx=TPW66WR%+>hpoQ4BldT0(dxmRQrlofZJ+sy&Lm zPN$|ZltSU@Q#|m7s5m%!DXL%W{gF*Lr3bb?f3i`1{qPv?_-}Jy0E*n`N8$biOdxu- zoI8jj_W1psmynq;WlDWLEQm zb*bi%*|8=)(=MWeSxfZx^{H36^5GD(7=Xf)nwt7FUG_G}PN)+e7jB5EWBfJZ=l46U z;(wIslJ)hhc;hkC7w6^+1H|!R<3lph;Ysuky-v;@zTx2#O%_p6(ay>YNhPI^ z)wFO?P8WAO1Ym7Y&Yd7_)(P?rpY;p|FvV7$jhTiZtB3LDeszH>L7HFgy)C&1TO`Hf z;vAx6J`F*{eTAlK@Fq2g#17Y!D}7Bab{w?^M2geC{k74@U}ZUsg`K7{kWu2LzHr-#Wi`0bHBj4kJRV{NOcQaW8DALa-c|nV4iX%I(do5PutynzCU{ zVrq4}W4KHZG3i(befta^l86;>vhi95-K)AHLJ140-cYj|8=1U=)zr9dlNK0rJTRQL zu~}_TIN@xIWHCf`S4M_1+*T;0vQX+wMvJm9G)H~S#!AA5hMr#}JwD9Jex&-m+@2Y% z`Xmr&0jd!`KZkq)QlOq^KU*(dB=_#pYH-?LRfh~hyyXNE2LeepJ$v*D&XxUQZg7UF zxwzOyAC1H$W9Dav>8W81qXyA5Rj>!0ls1IJ7dBR3_k$pP{+j?u(~p0Q>y(bf*gp z^Ax!KzL&`zJ-6rEc0eX#2RUce-o-kD4&z;2hSJX^D>n-)yM*!{PL&K&L7;)+?|lE* zr(_If=;HJ*N_PE|ogTN?Z7(Vhz#d#&&_^KuCi|D5`fibl-O=vhQ*;ZEH@99qVUC7v$xYul<5 z0F$CeyNR;_m_Ggo8{Q zUv8HK3j?JpoiE`d`x-VpObM9ML;KZc zfJcG+s<5f%m0(k`JbW1Qn8NK7MASB++H65aCEtAX0iruo@GJX^d_-zdCiHMIJ-vH? zy}+Eqy><0c43A|eq=uJz({R8XwS`+FO^HxLt#6sF$9trQ(@hlW`NnsljW+1zbpr(y zI$7ns$i^03l0t-rDFEvrFl-7BPr>OE7Z(S3ah(lB$Mwns?O%0uLHu%xfyi#b5C;nu zYUn+P=K10fBlZX`&zK0H1xUy+?pcIph??11XV6R2BmIk!1N@Gvvwe+yV3mU_X<+mT z08+AB8;O8Zb@h@@a0q>1JV-6|gi^Ay20#$tqmVo>YxcP=^aLu#yePi4T-XV6Fy{cp zAlD`v&EX5E2&t!5ZOp|_Gvvu2{n7%FWkuD(l0Jg8xW6x#1-Tvtp`-gaL`;;_)V@$+ z!vM3fne^U=vL*_ys-wvg9`vSnO|$gtb`tb|17c4Bi9l=$iROL$^r;?1X82GzK}1Yb z2`=kNnx!@Z+s#x623}oVosn1jbo1}&Z7EM&pADtWGas~gU^Y|(_Zu__;- zTf;_)))3)Fo}v4fpZbF536@dCRwF4AnL!d%u+-4LmnN%)!9hXd?4aWQC{ZS){s13G zJnyS!DF+g-22&0KJ}D+7&?UjB{92-nPyGQAgi2ob_Vz{zdk6s*VN%b>@T+@-gv0tW zt|K+XX=foy*2{w+I=XC@YRupMk01=qs2|H3(>xmj-?tI zhoYkWF)JsPP`tu%*?s&(B?A5t zu9!GVwtf$;{H!jhI7=f(g^s_N4pj0d5OB*|Uu&bT3Tv8u&n-JqUnh@pX&Wz41fH!2ui}hvByf zoxGWfM9TksE^nKaP*gD+(AAnj|0gM4%W^%esN1vY5{To^+ZWnc>@Nt&6eBb;Ro_L? zJ#gQ_Ri~B5yQ6n^jN~fnMAg6Kmc#r1nKJxO*8jhg|Nn3Qn#pWyX?fX`Ea6zGeep9m z_$rh{#bEV-kFrp}W-^rtFpEb!z;gkVhyu;H!0lvTuT%Du3bJ(?lA&z;eBB=iu*w!@ zHaIvrgZQZxy<4c<5xJ;Vmy(XoA7&hoat!Uixus?LJ^wo0`fu?oe?fDV@9Cjg{Ie@pBb|_tZ(q~ zS?w-484f;?m7N2c1oeUw%COa-mc!O>Wrde{&rf&2xIsu0G+_|DQe1rlcmIsX?mHCs zpWp|qd>UTg)n#8%-no-AW@($5*~PJRX#R%AdZ-`3L!rYk zQ-R6yHCb0mu1>!TwI(_&;KYnW`*H2sirvH8-4{|f7uy%SI<@>Qe@6jZ<*;9UUUjHP zGU+bt18oz1v%Syf`FR@$AoE&tT#|hEGv{>^2cASXp%941&{-WNQo%$)PDW9q*@N3` zv3p0eEkRitr?!ada;!qdAUSTs6K$iva%1v~_i(EIx*^pUo6RZri=X*ck zMA6aF5CljU2`06y2%DOk5}jQsQ^<7Q2rpgH3iVD+da1NKp5rx1YkMK&Q>iT=NtG1n z-C`iZI43BS_|UhWuOPZu7u9@pVhwcDUDKb{<;->3URa7gm>YFvEw#ZCKR?h$igM;l4YJWVLr!W>mm-{$F?X+TSZwM9l78?m}+5CDD z-P`Bhb|*#id53mwT8b)4%HhUOtF^0^L5@VSoxvpB*t4aL=PP^#%ahd}UbjHtM`Fvq zjf1v{7*B&U*H2O*cyGW3g~FoLg^=PO9Vc`*N)DQyA`Si{6BBXO?7*XUq-+6N)oFY%v8<(1)2sDJ@UIw{sp9(x1T~fcoZx&Qf}NJ&uKdjD@wOgOUY)B z4M7cHU0kgvjAna@c3tR@5+IB#3`hmv=6nadLECW}6Xl;lDgjV=Wvb@El6ZD!IdJA_ zA;W&utyLD{yYp>g4S|@3Qu6XhPzSUCVv=OgLQqzR@_ax$K!*SlB*%feYz_bJ8m=(m z1Z44hP2YbdmVn<{0*OW|rC~$f{H~Bt0)TixUQ4X@|IT~C{vonWPae8;)lp2$8uN0> z+jm3{+rSn?@ec}O+}*2K_Pb88uP_QC2ux~S`T{BAik-=e-k&CYB8Du|c%^$H7{Rr= zU!biYCCGgK_z}j&2m{#}tp({cikMKAmzRC35_kmf-TNh+4Iqp6&b7`szPig4?(Hl3 zO`FYBpRV z-)@4M31U0Z@o|n;ISK^7(>-E(jPG-CeS&WJaNI-K-Y>q1;)Dm8azh+a2B)AE7+a_( zL~<5KT^{FRa37IKI5c-XSjF?f4WLM1J-=@;xJOIthXmu0)E!d%+Fxjzf*x^keo%b` zqAg6ZbsT_(2fQ;3u>s(2-P13LlSdH^;#Fag=2;#NU<5cP0r~{=aDG^BBM2y*GqR(; zyL$WAhr42@)wI;qHz+8&rfPkTKmZD&dS#91&;&umtm!~RD$b8r5HkRuR(`Yk;w%(u zZ2`#0vgu=t*l;W4L09_*6J?M_i>S^v+%-*E8Y|^|1%Ca4!o3^`CHRoHAt|6Bu!R9~ zRyqulA>#=kY!mT$Kn{LsxPUg%ZU1&mI$;T`!Y6GYIEKJbkbwz^&NO=@6r zSB3NZ?IAzC{XNR7m(VF(H&KDlZY35dtM+?HIVHeVaGDMV9diQ?H0(SqmXs$C~$!z*apz?7xf} zQ$z=ZeKoYUztcEWV00bCl~jn1J!c|{dGHz%!ven`A7R(}0!wkEnUCLk_!_yu6JRiW zAXHxSo6Xm2hoRB8Z!sWj+`-u;AO;60iEMZw9qcJ2m0?FoLT2 z`6ndjc`Rh%e>#D0!NT}%+``f;YU)#5&*VGqGq2grxtgh)Fwg62Qu;{lL3^(dB`*b-{I@^uzW@y+tK*rWgVhB zf};C-mI?-NZ_wM%=g98h(MMFkEU&yE2#yQz|z-Vbh~rY+QWe`MM#TXi1YJyuE9sIMxni zT}3sKtY>*2Wy)%qlS{?92wg&vodjLjxvD8;YU{|#WtUIq6ZpMWPFMhT@if4pFn5^k zTjp>_u!sTW&`k`H_kFk`(vtF3NBOHPC{<_YkC6d}1=bhRj&|x7t~V6fQC25B{97X~ zNSvscIS>AHR9=3Mf_IxtgK~e~2FbJIMmCXtaJ={wwpO1)7sayOy`Bb^O`HX*b^~#D zNtTC3Ls?<<{Jz9@1Un?3lMl3lRxnGNIlN#0!)E9hW zVuuJ$qDY8%Ao~(F4`L7m_y_9MkCh65Pf#y>N>`*|>02%MR@FaCA~3#N3wqKmaCU*f z0#9!emJWmu4oPQyAo56WINy1gnaKfe1`yptP~QSM-&lv8YtO-~4cM#GP<>tjFCXhK z@{S@7VDkVQ-EXWuz3e*bfw0fRi zgX)>U%&+Z0P4IbCxqwKA7a#UN(qc-6Vt5#XYx%4SzZ*GwSY`y4BXs0gQni;pF!%(%o3IpQR(hM6GDO`_OhrJ2PMAiiO&S58wbn;uV)q_^ zs{pWrrM&P51rf(S@ORl8Q+m5(aYXaRi=^k|#LqpLX;fELjR+17mLVZHv5ACv%0aj2 zl@KWJa(`);Th9)Ey!-_gGx4la;t%&y1p{KWg?=@iH0Z^MJnwO~qC*@vxn{2n*LuOR zv)I*j;QZo6%tc{EB2>a|&`lAHu|8fN6kLh$f$|L@&;+2J-Q(p&oNH((^^rR6(Uwd6 za(xIxnsC`@05yP&Oio-J0~{A5ClC0TD+C5Q;ba_W_HrP@nxK7vp$)+^G59s`arDMV zCRt6QL0kI)xECi|4@KK;o)B{0vH+`CUZ3klKx}HcVcibNeik2oi{^{z%J>8GyZf)z z?K4>NxaImd^2uVFvcOv0I{FD2)W47Aud#G2?`!U!+LvqDmTD3UdVno?aRGwOZ-!^z z9TnTYWoG)md-oc|4OScSjM0==qqequ_N!5sjA3I|e?5e6I0(383;`iln}mOYt;B?^ zoSe%1?x%+r1r8HEimKyv?&+z11UIodP?SCqWpXK|V%~hQbo0|ay02KzFdjW+I#kz2 zbvwGk;J+*MmVxXOk?3_TX)NluyFz=e9#?H!K1gxg=lppfWZLnfh0kT9z>@zY!B*jh zbL9k!Q~U&FFNf{wM?z67_&Nd*k^&=Qd99OC%+A5TFm-T}c*$`k?9*rU3f;TVgp{_W z(v$*V@+|ArWhDku*DuJLK2pnkpR=5-VxFgukWNxz$exRkPCHym3BB(mqlIKcr5fAX zWPWFBs6l-Ak+kXWuVHM_?QfKvoy$ND&-$bW=;j7FISbe()M?Dmf7E2=z%C;OJr2|E z^}u)fG#Jlo{_`9zO&mnRv{Ptrgx%^T-7jdlxmDp7OCRm63?jci19<9l@3ZgE73a8n zR`z=H7$c-XYQyEvO+Op6)KVs}X_IgK2AJQMknx!noYRv5Irw@9NSDk5f=^9PCt{?4 zM7f0pONK&XQQJ94gecguuB^cjaY6Y3N3nb^2nkFsEg8c%L-+>rjpIfIv!GxxZd8eBRU}iw+S_Hpwd@2^>eXT}11L`)pw&lBcry@j z(RCrC2~@}$$b`j2g%l*tWj-@aLz*PwS?Jd0W{oZz%;#t!c{Bhy$X7$aO3Xq!1Vwju zsRz*AlUI(8j$Z~>zUfp80hN6A5DdaFJII@1FL4=nd%_iiK29Cq5>AW0*~y>M(%Jdh zPC!r)R_X2Aw|MyYb?|?N;bn)Mwe(6jq5F z&mZ+*d%*;EYF$TE)O&nCemI}sbuQ|wPPNzJ;cRHU-o?b{aqzwypx!pXJRmI%jBgOJ zYBS{*;8@0+6u!uNv+|7n%^wwZsMv@^8X$aestk6dfD&^#0FfX_oCd(#jhIjN=EriI zett?v&`WA%4gE>Aq^t~E1PFd(y6pL9EkJG(@Kqtc)zuz(*^DVYbYOOfr%6YMBZfvq zdE)1IUofLbsU3uw%VG9AEG~`-blRuiS7(3cyDY{#U{H<}nVUu=R=} z2^fV6G#?(Xaw`{c0&)MQr{h)wu`k|2*+QbnKDG#yHL=%~gpUmk)0;b(hr|rHNF?T4 zBQPle*PHYQeF_VESwIV7#1rOkz{S+(AiHjclp|#63h6dr6ti& zW)2P|l$nzYSz=FPxUWh1)D2K& zJv3foEw&5k{e@D;Ki@L20QQ(JyidY}fmDfWvlVTx#7TKF!OAEpx(rAtG3 zDOgvo+<-X-`e(V|DSbyyi-&I3=P++d@al#`WpBLK%9w(1RccX!dGr~dGwQb$KV<4_YY zC=r_h5@#njG0UMGVu@f}x`chV{Y?ju7fj?kQB-GUn-w53fJLYX@LPC70*iovz}w>D;u0rDb=cG*C^LYA?IT}a4^`z^*y$>s zhK5*lEC|!Fo=(qqDxGR;Ya5=svhA*q$3|Y3s}lVc`p00Il$8WZ6*!08b)7Zd`J zmL;L{1G$(2zxbmoeAf(uAb2dsbf;>(5H1Q6<$mA|_P~faoc&wpBc^xw?SFrB=Z^)3 zhfhY<0WcbdCy+7XxrGHH29Lczst_s@MP{$TwQw@ZC}{mtQyE+FryFgkshGvM-u>CIQcdYzzZOEMWg-#6&F zFA4+4#x(EUyJuzV5IN8Y(?nk5uLP{f8&L?`9*=_InWM6w_!gBxEl2YPEP#nu&b3Ny zR&eSuD8V4Mh(HE+tYMlL?SULkxSfSxsAHW;m9YP3*p0K|07As=?Tg?W5&Ti=fhl75 zM}rRclp$gU))Yyepy3VX>abE8nG9DR|H(pvLca_+0s#AIVO+jUJi`5oBU&69wYY&n zDxN68aPQCJSCR4Y%v>ZP&8@9BCHXbHZQ8DF0SOKQJ04qv%W^`qwfHwTXhOdl8YF;m zsbEO9MX`Kps!By16AVzuk2YWthd z3sc3Uapar)&CmI8SEPPii7Xl zaBd{P`b5>h_VxbpgAJuKf!TFw*QarElETvR#DN{cElipvA0VPRwY)3^rx(7Lg(ZJ4 zt;ZM{t;M=}75SPTI)Xsa%kE2i&I7qR3ILDbwP5fE(#8nrG;4)#L;4IE5xg5WP<7R0 z3V1(${Aguu{WlggQ4#&=)2ExG3|G^Xj_TL8EjXf$2Xn|E4+Y+o?Q$=5)+ubjl);sN z;9v~8o{Bs`a8<{D7*Vpu1wKhrBi~H~rIHGaPQZ0+kZUZz%&U$k3L{G5C`e!;GBQ%@ z;b$)@=l~6U?{TemR)TNy6J5U^da}I+#$ZiHN4BS|rsf@fhxPi&$wUY*p)EYq2k8*j zME7KWt>NZM0od?*n2M{axTJ5k0Q(bj*bP!n{ zii(WQ(6i@~Z-&dJv1fsccpjC+i9RC=BrSgWHDXF{at;6cQ_p8OTU+)m6eA_K`$9+| zF-0JP{RE0B0keL=c6LLFEgmrN&$&fTLAw&cU^nl9P+@R)f#D zwZAW!($fuR4sDwx7ZHH*L&&` z?mCTuQLKmWORnUSXiw=ZpPKxs6^&Fk6yh(6A|iQ<<2+P88QW)xC__AQ$J1Ywz9EjJ z@YybY+h=Bdz$Z!*0B98H2p$y2F$81c5Fz^ud_j=AUIGG#oeu8mFDS{`2B1x}!^r#* zU;*_~8*0!nUV+Dlq-x<{J_UhCuk8f9Z9q^+5kT@5_D6>>Mp^K(?oxu&LiEW1e09r1 z(3*=JF?At?cnXRmk}CtZz9}7PlY~KpF;18V6M{GVkky5*LN%og84j-$ zvN_k;(*VBNpszv=Bo3y+%-kG5V1p-Iu~17x#ARW!^}4vBKqIKz()RQ#FzjTq}~ zt_}oROVy4xJ<-#n_a~>6F~Sx)Nni8$v(QXo63WU1J097Z6wU<#V+0EI7Q6TJ ze>=VROwSPjrSsGF^N2qvUK@J%E|h?&w(S-;&Q3R;j#6C2vMLT+;8Nn|pPLvIgpLk` zxtw9Hg=i&1uMON-w>cG!s5)wZ<9wRCqHlp zA1KINE`c-yl>W{1^nDn4`Mv%Os>g>$A+C7?Y+B3k4wI!D9flfN4c-Dc;OKHBoX)QaLE*1F7GTGjk> z+bwn0ZycNtK9+FGvL-0V(|;+Ffty>W_10DDy8;U4&YWwWs>@yEi5_|1x*PN3FCxEv~6{Ha5&L(as+e&H8SXZJ`a(oqmSC zvHVzG&Hzz@hnW;71pQstdR=P5Ih~w5@wrk#{@PCQT<^9*Qy+9lZ(=Tuzp$t*;}2dM z(?+|!I-j+fFP}J2zG5}`I<@(I%h6Q5B*ow>eHwS%y{8Qw;+aUIRfl6^!cHgcsXOxU z1cg;+d_p9M*s%n~6UM_#R#HznJh-=!roOl!(i%2sl_SX}eO?ppE$p`6-@h8szg#AO zH{D<-(U!YO@`9ry-t}Ze0)o--0$*rq_Pa0+9=U>s^N$13bjgpaJ9sb-#6t+`l7X~rluM5en10OMF)`5CF?l+cW9eIl6bl9AwaF_RA~Mg~ z^z&xG>Fm5TL66ni{`akWhJMfN`WwcIvBn4U^GTNfUcBuiwt(c12zqKkovjqML(*?I1;6<}F=4X;~VOdc68}eeF_#iIS~)RK1kywpy-?=44DvFC2sq5f){-YEtRH9Sg28%-C7&Q5=VB(MfNe zcM=_Xb!NX)AzKSuq=A3um;DUX6!GQPO;d0BOrL6>?VB0$Q7QWsR;;P4nk##fI@H!Q za%I>*J#^p0eH>?%qmy%9RqTnK>tt0=d8T8zq}9A=-*k}WWR}@04rXTNvXu$Hm8#OE z5+>ilMWu#1i_7t+-R_+GBz*0BcMhM0h5cO9ijcuo&zI|Pe+CE|7h#2L+k3W6NvuW*^>~NUqO;G@;^JiuA9CmNV zdtQ-mtauOoVoXCoH)IW+ za(BvBXazbgxN>|if7#TY2+FIV@GCP_R&jc`YVCFDH1q!Tg`{gFAQNwp1SgUmGA;Do z5gZ)6G{rW4>P)6n1Fxy~YLn~0OF1sC&!d$+Y0?J{9Kb%*uIH-dwmg?JHb?VN^ZaPa z%~-jCrlyETEwPOIbDi>5r#1BSZ0?PBIZAw;oO4=onM3{zYA*8v=id8bX+h`mg(;qC zjep#tyvzU#Fm!xBR)#K5bS7~UNnf@@>+gSb7i#WR6gGHf{ax5`$s=`o!qLNvGU4SK zLwLGSY<2KJ63TaEW!=p*I-ohE?mgS1dCL3H-}Plt_X_BczJaT#SMypaA1@M({jvBf z?ZR1yOt+-Lgd+tPy62E!!KHY)zd9iy8zMzF&`q`>i zEgH(sq_ZenlX0!k$_gsk()%p6qH>01M?ugM$GZao;E7AAX zbb0gUEallP6cmg?==YAHr<8FU%Cen_F(bR#*ut^DvPG&>d$UE}0J_%85&s3keTZD@!9YK!8=Fr4yo>Ei z@7S|MP^}WcVo0@$+9tq3@2w2!T^9bbBIe13qo# zxm7JmpMi2(&mRv3%jX!hA?@&(z0Z|IA5c#DoPXaw+`cASa^P5k;(7H<=sX3$l%rDb zO@)p099ohL&>h>^4;2WGOiyb8vN>Dm#E3@VHJjqkPkxe80kaaTgiPsqy4b}>p-I6I@AAAIcqofn)#1$G>qd2F=PdDs8 z_pf+Z|MI9_nwc5B_|#zqhBcE^9pGMzT>w$-?2Wq}07HnjDVAS8ajJC}(r|6ybfp+m z8yg)(3~r0Cnn8}4@r37?1D}F|X@Tp235gA?cA+0fnw8UKsy_l!bm@L=2aycQXr}h% zGNT4V^9T3t@w6N%NVSbXTEx~GCof)8t=%SuPl*7j4ZUlrKr#BeSMJs}>T9olS&+&|}-M_{;Ja_CDLP4J%*M1d#JGTz}RMMw*P zeDy`Q4z(*@8l%N%AP5}kXsQoQV$~vl2T;|Nt8+ezZi(GYMn%45%a%_?E~emAG~i## zK<434z|wZ7!l&rFwJHyS2zyx6Xk6fUuVC+|%Iar}R{%LMe_BNgLeuqQX^Bs1AZrOv z>f*xikrSI1@xzUb`}^o9N|Mn(cN*tT`+Ff8?A`iFUWuUZDJhC<_y zZoskc*WQ4@zv0oFgw_Le6#(hmTuNoS#yUDWkovbFsB?WiMPKNA?@O;2cXa9xX{PRa zsA=%*Fd-*_MROcgC@sr^2dUCSP5U#g_kEN-=QtW+r9h}KH@?xm_JM(c$?jZkkXEGz z%;z@blHQ4`>=C*4YjKjH*vICCr}OH$sE?S&2iYRATjcckw|g8Wl=DOoLbG*{qoUKm z6Sx`liofk(k5~#qstp-ELJo>Al!|M~2<$lwQjlyFROGG@$Dyi9gSBf@^@@RO1;_-a z*Gz5C2{pOo;SoN=Mo1>M?5U`%LZSL$g3mL+UR$BnZMg#!Wt z-XeeEx@r=AheLC;w^E)O<{8QJ^>66a)xIRkxFwJr139UNg;uLz`by;=F&|r|hHL!_ zlBg1}xrV{{O2C=DmHnpH?=L$r-Qk;ls*z(q{`Kn{l!h%tyd99A80ia45cKox+ea0t zUYDL26t0>yj7eK1R*qf&53EN}ReUOTv#hA7I9usoXg6*hfI*`T<3cptP>70dLCqC& zIIv8+7b6J87{YscYT#XEm9X0i=HnSA5isvv%*+iS8VT(U>62fZRT|kr-w)&-y>;sr z+8gD!j#7*G8-w8V)RZPHn<%w4wjL>l@H#rzwD^GZg{R}r1lxD6h5AlOOH^uV>E#ty z^*bU#Z{wOrqOeN%f#s0ypN2^ zh>3{7`UXqlb8Y%JifqJ8NyR|iu{30{Yq#48t;gWHbfz+k)gY?!9gA^T`1NZHpoXaU zu8ym@xnhYYEy-xPxP+o_v{I| zdzT(e(T}B-eSDdArm>1U2%`}*^Ej5=hw2{*5ulAsaUFrAp+>K^S~;xPr?Ak zTg}<{{=K3dSG|ma#zp0!88rWMO-21t7P55fvGMdP)h?e5Ku>k>W}Ua1`q(R5P#lB? z4e~H%)}O?)*VkvTu&}tzE;Dz}L_kxWa)at>d~7T`_=1AVGUTkRG36ztr3dXB8O~E| z)LtFL%;R#q{P;gw0Mzz-6#5ib3N5RHupcRj6x-WTFTABvz%RCsjE@^Rep2w9podlB zo}HIGC*WrXgqO9iMM_A}vCH)UZ12(DcqRj90NAOMKpU`p8G86vCg8>V7=n6^$4`jQ zHj-lo6x$rTva&KkWChZ+4qeU9`tVX;$PY#2=)8|a)RzBxWCtNZxSPgy);m~+g%&q{ zViJ7Iz3QP4ik{!C_VMwZ;l+3U*k5`nb+gbU+n`D{NhP=Y0S9-hjMC+2gVl64HY#pI zLWd+I?i~B~koOW|biE6676m5ReoOYxQt6*Jyv-ZmJeL#^DNUuw8bftkUR08qg2LyA z*A}$;Bqs511b=l%T-qt1E5tcpZRnulCdE|BdGh@G_aBdDN)9JiRS(>sed8!`FD6^m zBJaB0xYOy(=#tVh*>^!E=MKEIDf%oZXcEtLt?l}tm54>jmgn|kHc~Nh->L_#_>D3f z>^x?+HaO{YKffHc^|k;XHG^RvTUVC(&Cc6b6^zqUQ+uUa^M{%gVEcXcQ7FW}8I~Ga@)V+_+&!33WXhlV4z1G2>|B9Wx}h*< zkSj2P=Deh9ADTbZ8D!VD6#icR8dxDr_&$VC6gf`sP~q8Pa4Ie&e|(SdZaO-j3PrP> zI}THp<_L0><{V5h6nuEMubXtgEc-Z#t^wXPeLor31)|MOR@WT1{mfrFa!w~~= zAJ@MThq?XZ{hL!T^Qn)8Y5aT55)wL!{|3;#dw$>UU5kg>;&$`ysDHzb1}} zxb8!rMIjjUVheFG8$#O#AquJ{;^|a2Ca%QvTDzp=HZ+5e?_WeJ66+62G-+$9!#8aP=+$m*e>YIf; z^Lwk@Y6%W_RwHJ+b_+gua!W{wGnZXvk96VjA6Z!{Ed~znwPX-Xos^xhYtmH!(kmt^2VmeA3jf?SYE1=-bXc(+Vq54juM3!Nbvih z?V&+<7s=jD5dDn~i9gyjMs#23Q+T5Kn;6L4mu_P#=RYG0Int%LO`p5dY<-HSydvd| zGWP+HBq^r8Kh0l$KQNoisPLnp%(H(JQZ`$)wd<+TEuYiO0lr!hZFanvSuMuh2AN0g zb!{Z%s#InAdz$p{{UrV5?&-EKB~|6#bI%Y zOeUybwlVr(@z`RV=lpxGNEfd3GWFS!(L1@tH_7{jZjFBTFDqx_GnTk5u)AuI>r^?Z z7bUs9B25nM*ZqnE=JDR-Lb|fEWiO*O+dK3e`wpMf1YyR3b^ANgicsus2|-y|F^qxXt5$$mS~mD*YKom5C-Z>^P-JG>wm z#cFnT{Mt{YtbzJVx%NLnO-fG0m;hR&{?mxuJM54Pi;Mp6HF!om8K`}K8ArR0J*G)5 zmg#(HpdtbXHY%T;tgPo5Cq!sDWbeksaR5$DEr^wIBTQAWnahxolgIneO6Y7^Bw>uv zYlBA@9wE{i?39gA$Ti_q6fI!vgmMGpTnecVG-~{&7oXm=?M>&LX6(UueB5)g6nV{!mox)+oHBSWRIBmkI=Xgq1?_vkMFGx zz3b?B01?eA_php;0*rW4 zxdMqdoLajWm=nCYVOHUL4|#sXLydh+$iiR-!h2dEy)3vSyMWKz#m)0n(sQfY0zc_^lH|7IS-L zWFDinlSHmRge2T~k0qkW@hkffMh0#Xy1}F9<*<&8B0O#cbczG+o32kSrhx_fLNxV5 z%}|qUBC8ED2?_t7jM;G+QE+zqpL2GgXIT_%ypr5XVG?*sv9$AJ- zHk)bj{4>B8be~LP8n)qzvRmBT+z9_ALAIbX%U--rkC=sPfPNN#!d=QwXCHS0SI5BT zgFyqrynK|BxJ@m)COm!Y0pb{0=;1U>OxRGy@W{!P>T-&SiJ24!StOJTp4pG*aFGQC zBT$B?gs&BArC=c5DZ!i}q(}MKyj|P}AWMV>AF^y7LBTN4eLHvWKI2plV8srmfH-+C z%?+k~XN0gXoZ;;hlEyk(H#B6ad1gNYZk8OdDwD|BJ*jSA3c_$l2qj6s`SoQpc}-W> zLm=OJr5^S|1~0Eae$0IM@ZpK&HN8Gp;TWR@`UZ~=a*H8UC z7|A(wjiNC(uSZ4EA!WlM!B1VY!>O>OBsxg@sJPFAruWg5|2FFAr(oq6j?>W5QEALQ z+rxK;`oA{L|K7=EBO!wWIjnABF-TOm>SMti*hSMi0V;(d8-k+4VTLO@|N3M;GiF(*K5=(_mY1|{{5BiMhVSs(@h{kN5lZXA8&fqRjJ5CLch zLCyg)LWSzwR~CxLx{Hmi5fh1HrV!P_CND86cLKR147NzQjP~7#qrbY7Pu+dAk&zIN zA*qcxs;JLyQ`I5SXo^=V< zg!U8-I>0^f$B;Ca$M@CVcfCh69D;&a7g^JRP7pz9uvZXbTWC8tLJ$Zkl@Z zgnH=H8PrVm7M;eWCT5*N=`5`+l5QD}p6zejv&=+=Qg@}c|KR&oAW!a}^5(0zsNLuo zVA?p3xk!QC_>E4dajE|NONANwnm;KoMV!i%6x;SN##ZUw9-|AVs(t0|ynlb>%H0;D_(uQD5nol=WBc`}^k)apu&A$k>yPXY@+rqp!1% z85npJ$lu;kmcaDAEX_UPd0CoNhPl3wS=Yz&!>pZS?5)PXM5`1=C>eYbSjSg$>J75J zzigjKmXK}niEq4Rk{vyhsXu00Qmk0m_=kj+`_T2QtdqR{>SsS(mvSVjSi z)kdV&=T_y5bIQt}%FN`Z402AcE*>e#(9hrqp!G06Bdeoleuk~nHOHynEPfiw|Z-)s+JYqIhHEaZ}d>pZ)g-L zGzW3)1c3{H)Q{;Y79&?cNieC+D&~`6#QeYCC9xNr8pjB9fORuYIXD5El$oZ_rAs4( z1aq#x6xqYwni?r>uvJh(<8(krB>3cUJOC`t`neYIz`*Y#1I**cH?=eoi<4Hxt}IWb zt{ifCc8h}2&Mqz#H9FT;R6TZR)QAJZh-94*D6a_)oKR+CIqXH4Fv;|1Cn8vwRnCJS zSPKZoU)E&@&d?TJ?FXsdgxNyK_?lI(TzS&?m`b_&=5$u`rpTh>$r!47aEEnR>sa7c z0-K%!qTjHCscL;rfrvbz8w!pv%(Ta*N~qKjl?=@;QNQn{i?gQ?2@NoO!?>Ga6p z9vSR~NG{n!B^T2`_HR*T2;=x}m$2nu8*pYnq^fwx<~uwjqLltMK!VZGY#fA7K<@nq z#b5t=i!|Lq-;wQjH5+*Yckc;GFDm}0ohr3Q*Rb?H-+R^sd&3qLD0+!d<%noz)deWf z?>%;~a4$m*0C~spRdhcmW6xD`Wk7=fxeA0)Y!HZLeR_D=Q=SvF0f7r*Z~ZzuOEgnN zu>*NV?R-KQf~U@BnU&thD9FqD*N!aM{n#?;GF>??m?h=)^0zkvX@YB(@q# zG5P1T4^G^AUeNrFg2oyw!+jk*o!yX?3vCXT2?z+(Pst)?+-7euHXPL`R(dcOj1X=` ze;4&?0WNe5n>|ferdjE0ahAEm-NAo;+{BWQrQJI`Yy~Cs3o@@3!~z`n^Y^dI6slfr z!q!`}DyX?nw&dk4C9g)!LC6QO)oleZ6Dtqd8S{8+qS&u8Dd*1PB*{?Jsb+cwmO&UR3MEEu39%=Ym&{kR<^qXQ6%Pb_ID+@4*=&hBL|h7s3fz0Jh_ZlQ8o| z=(Bi`5d|f5?_Ns|Ou(Z_y6y;MLZQTIBdH8?<2lUn9mVV72*xFdiF5cFwMOHd7ulyfO|k*titgq9f%^X z+>>y*;XRl7OW*0fvAr(ZZ9N5!14$fL1daIb?+FfCz;|$#)5NbYmdpk{DrT z{r$=|qss}Z*B*2eug2GszRf%`S2w)#X?odv^Q6GKsnbX2-o5>0qGj-nEXJm|~B< z&_gg7MYB3pj|LE!B1mYmQ8&*nW*sroWiO50mIYz>%44PDbwpGY zGa>Cniu-=UUl*Nv(M;)(*Z@#9TwOKwR!=#N<9XCTgG4MWEZCTOW8ND25mSKRA^lXJ zOh1KpRWthudY1A(ckbL_!rDa9zI*rX(Dd|llDBQP`?5^DA>5{iv4_GF_0it`fdPs= zrv^Iogir3{;W^SS=Q9^)Fo^g077jz|3A5@T^M7J>v>>lO*{%cUrPy;L%J(SAN*B4U zSd>B2T$5NDf;DYc+#qBB=>6dUCQI}983Ce$NPon4-3rQf!bb?l?hoj_2m=kCC5w{3 z6mDK^D)yv66};C_t)<$RauK&f2m^1e4~Z-zFXb89CumrAM5>=5l*UQOje{|P5Whdq z`d|kY93*aH#931x6O}EW2cRB>F7%&03{Ts;PzoTSln0`hfPPHUUuHtHp$cs-httyO zJyKE(0NF8GQ|B+PEMh}-pZy|@7WCSw^zwNL+#+HAAi=2#D5=Mq&yzqu483h}(`UGI zh$tnaJ(VCkrAqP>M_!zHW#~t+9KhBoJkf6xXR47b@lO4k0C9dg)7vFh9v_>GcTeGo zO8;0qK%c3z|5QH(TT}Hu+~f{CX=SyCa4*Wq^``wv3F){NpP0xE0Jm_>_;3Kz!k z=jpGeKV}A2f6+-UxAGSos`GJh1O;wG3CKZ@!f7JkW@lT1H`gt(9vV0rsiNb-a{nD& zlIUr1zi22g2_Y~ovby2reAea|u2>Ifs>r#wHiVQMvUx6t$x0h(W@rcAQ_ zfF?OD^st@gJ*W@!GxdsEhsxEii1GDxc2tFh_S)+CDi`V_P%l* zkSFb#@pzR7qYvpVR6Ym&7VW-HP6~Q>PLGGw&nb25LL$a(NU#~15ePNGGk5K83Ivib+p@y z3!d`v1UZO4l7aHt5p3l%Rpkdm#py&yk6{R1hgm~FRMebpiv-|b>x$O7sXwT1XbMe4 z8K^@`cKLMWym7~FteR~|ld30(>1=N#hsH|58906cpaqgS|-rYDTy^~L+!U$URn7#6GJoSr~i`WRMIORR} z!2gE2W_Wbez;0SArA-%c0#l!}wZNHyNmbjy4_cRqmxZVbXcaPINuVz&5R8S41>GT- zL6ua!-mdlns#Lub$R!}wE$ zT%I_z4xh0>PDxKsZ}s?N@WvANb$X2TFP#|2cEDD#qhPL4)DTku%N&7Dp=;02jHCLP z^5u0?Q`2rN#|-G2cZR_H!^_YA_T@ucou)Ymag!}7{cTVl@CplS0hdx|qn??b79RAU z7L9f5YBI`VfVYDYWmvK15RSx!N+f|`jgNVFy!QRO+@1QhoG4O=1|6gRT|8TP8dm`9 zJ!JI%tm+knNx&@=Pcpz=RN=u1yK`f=`ui1mZmXyN`85~Cq)CsBJ34%}BRvZ9{?+7t z4C{@Jjc;IID@Kw}KvfdaZE-m}#5u=s-wtl210-;U;<)71KY|R@2QU9}j{QZ{C?pr5 zl;^p=zasU#L|Am^N$XG3@g5eQrGC7k(nsOLOY+_qxEQ8;_2;p=RH* z2jO-6*Go*h3H3VJUCK~w4+TAs%mV@{(6j8urI`U&F=AvfhoB(84T<;-`r5Lb7|`9F z(`1BuC;|`_P5j3yCggxMIug2X6YE;26yelCk@2eR02xz!urP!<8iqlcIs(H~F1s#DQ% z*`c!XMMS%yfRbpuq{FE*FYYg!8sVHkh&pUgIS@&Kafbt+!bdthKCa>EDFxyih3p1w z`ZS@ZSPGe&`l?24F9xCK5&1it@n+XM8>!l)qZzqF$6n}#e32{GnQ^P$`St6O`NtOb z+cArLAxtRsoZJPGbHT$uSh=;KP=b9Rq*R*VyfIyYzfk&pw`B;d%oe%25ql6`t< zAS}hU9U+kFq`~+H$daO~MhH=Y(|Jwq-+l18RH?j4ILB}DL&7zGX}#^KKkAy!8@-5ES{VkN4mP(0U~ppE^#Z7Mt{ z2(`^5OsVqmckn&qfrF*>*kU`{zQ6;I9yZZ zO4wiA+8;=biO)f2k*t5%g!85oPmQrHyNEYUzUA3oU^!#hW(lS}V3itm0ZEDbS2AmB z^WW&lC;Llj#JpzAG9oVrf*@8#56yq*SG2ydMY(q*wgIBX#@*IIB^YefdL!~rFw&}H zFo3ZO7=N$;q$X-2HI8~1gJ2$~3o59s8AQ*cphFG_S4du-d-o=9$lhv7ZONFBrwFeK%- z%#EOqfw`y=QY03{x$vo1Sj;Rv#rcX*kvtZ&90P85ZsL>myM#K3y>V13@^x%cW*pnW zW;Rm_bqD2*i4S>mm#RHhCC~$@Wg9O2jgo9* zqxhmAL3)HSMq*j(M#V0o>U(clZeklP#h^|?!oqgq*b#k+7+oQn+_Ms|FFmhrh}_&# zU9+eclW;gF;LivC8+4q_1j%vtYp#~edGDN6_P9Z-g2jW9`;68uH+G6;&-ksbEqQ$3 zleRfdg=O1hxF_pxBNXLzYA%ER@4fM`Go-MpCsROJod#~@=%SV&);n=w% zrX!OYw?{P|CY<;fx#j)=bI*){%iBDBG&os~gin) z?kdVlTUJ~LVicE!g{xhXTAFNCst4@S0&Rr24NWIFy6p@DLV0 zd~crEX4ktCMWoG%{#$!AY#I}RK>WBuftQVsI;75neEx=S$firwCx$@k0W zy%wLlGLZB3aHYTAuY^Tjs-L!R^LY6A``>1?@a*5emBfv1iD4_)%SH`uu_gM8g(lk7 zgF7iGUv*|KiI9YClcC`-4eAtPgvQ~JTMeN4#l-n)up5bT`N4wiJ5*fLR@YD#kOfp8 ze0s7Zqx1KAbPjtV$!O!Zb$xq@*uysui7}@($_+4-mYPgSx!m?KR(|g@^LOo+HK>s% zo~1;47Xq0YtnAVjb3?f`ep78JR2U+|1%`4TXX#=o@;_}tjByBBSpA&+W4}Ff5u-Za z^VFo4=C{$0HRN=3XoG@$w@}Kz{ev|F%K`Zw>Ae&rv+;^PGLLQMl`qrR_UD`Gna=qL zDY!-bmx?>tIRB~F)2@pCYGs8YmsBj11pzr>&(o_eMfqPYGkqxf(}ybh;qs5#T$XBh z4T1&}LO)BJ%NM4j(j&+u)9w+!s&@mWUcgI^TvN}pT|b14pyB01;jFZ?=e;OB31)gA z+tY&U;O&8O?{=8w40Fy}%oK=R!wIWNxr?oYQ2xbkfX#J~r~b_9SDk+PM;;BK_2m6` zC1qbMG|p^&(>SU{dKTyFiBUoqbkFOPVkP+lOR$fv9T`MBxL~G-_z9Fi) z)j5JaAp%KI7ZAt$SLBC-`#51340?O}6fMzx4XqAnKW(Ax&BP3}W` zf=UJ1sxW!@jy^P^_S9NkU;h^{-R9W*Cdc~dF9Ck{{nC*>l-pHDC*~PnykHSCSI_Mp znVY97bh>h_QC7;0>jI8EU)$ukJ4WC1aj%F7Emi<5^B)i^5)Swo@IGA&cT>j6rU;fN zw<)4s4D65v~55c6LsM( zj)fw7nm`k#-;4nrL4e|kWAE4^>xFtkLqmJBXd$A-YkX{Ih-31|1lt6mKD6O@8INMy0vI4!O}K1>AvDU zv*l-mg3s%LBqnZJJOF^S5rq9$EbVD^JX0~d>UqMHfcB?IZovlOJE(Hm9%`zg=1u*H zQ#@}Ywxtc{0UmPgKMw3YT+{)f4TMJ#F7nqGN7OT~TnZ(QD2K0Jc=Bw$Tu(~J^Ao#a z_O)~tZk7;#w`+o$IKB)Q5FzJQt%XDL0MDPW3k}$=%^Y(deDfB0zk2c zz!v5gAb754s(|w7=a{kL-J{tvM)o&`LnV%p=&@-kTS?In`iq_FagZV zA`+^hRPxI1fFPQP_I2A-rZeLD937-mfAgD-7TH9 zVmQcq*5nbHN&ok_cZIr1;k!OiEnT7W;AMEqlN?P}cGe?AmEP8!PQ>-b2jYK@?&vnvop9z5cFw6%s`=MwqkKXxY9vc%A zhMwR?nI7q|rN7zgiEj@dJ>r#?X2fmOUqx?r*a#3C8#{a5;4j$8ucP?4!ig10_DX$u zz!MdnW~|(fE|j0yBYwjon(f=S?-vxDytLO` zr&V!@mj6U8ahf@R!r`G!zQx%+D$isM_4NrCdPZ(;118G4`6lXTb)2aSl>Kge{3V4= zre#~u+fQ&hNZQGm_6wyVO&KprbONg5{ z+eRsUzOafHvdZv-Zi9;CA;jvN^5VsZf1@_h(RSYHm0qUw_| zJMg%zmo-p%W3fq$lK#o9B_x}bWD5pSPFW;W6e`E@4D{L5$SV8| zyZ~M66&xMHf~H6v!x&?H0j3EX$dHN|E@6B?#!v&1!)aaJkmO`u$eMOSt-oJLXxHI~ zIJqNKqGl3+57kJuYvySXdU?gwc2Syy#^?mQo(zs1|Y6YBqTVVxsO>FXf zaOVDL-36;e(%WeAPvdaqi&i7wzkkn&TKVoS>u+Als9lh$>~e^mnuaFSj7OdYOYrs` zH^~72f?e>eo^Hjg#!@OzoJh-VF5X6UngACtzt;Y`l5I81(8tqRTk}_3a~yJ&;$`Fi z))wiPsc*1dA-+D_(>5(NbvcZ6dpQ$tgpW*`HA_8m10ta>+aY_-*f<7Cl0gf}hZ^l) zzBn;3GvA~noohvJ=B9|?mK!=d~rTh@$1V4P%FNQX9TFN@oi0cPwaw3 zDx|DT-np-6`||Q~QUh&WNS9M+>-aVP6NJpJx;o{^6H_^CTpXg~eZF z4b4;0mpG0B=OyHI_^}dOT(K{CKQ++Q>`y~5{Ui-t3)Sh6@_m{4<42iS-XSTHV)r4g z(<0l$zSiDyTp%JjaU8`;UK9mQB1KIiX~3&FfYPfx7Dn=ZAUym) z?vsnWdW$)8hcu(Cz&j2Fi!Lhdj!+Isl|4SjkFImb9Bzqn&1GA%txsR&JDj%q*V>XM zw>ttg1rHX;`1tsVnHk#hN{SlC2nwCs>^F-xH<&g{KHpHhNwYE2e>Rma#6?@m`2v~m zIStP!)Ah&IHnWx+;dc&*Ilrd8S-E-cNVgYJsitT+)W*~MeN>LHewLR?@gb*1{$4om zJ;&Eg);IgkGq3R{f4gkJ-G8e5u8yhUiK{P9)OxI(4=RcGwoWXP(q?{Cp2L3Ee%qFf)O~&{!(>$S+#Qg$#Y@PNmQUp zuiJS`Y$Djow1c_tDT&21)nmuf8Ze6f@BivEgR3t1DuQ*`gM@w(D^QIWKZZ}xiFhCL zY;d}8;ULm!u`~<=n}sHfDt$TWNT{vtt~O2_)0@}pjDQ6o=MseL)YH@Rt%7JWeM0Zf zHcnym0A7{?{QUp2jssT)n8&kjZPZ9A@Rr#^YEil2Ez9AH<9Er)kL=z~rkIaXxdVgi zfB5T2Hq|lyPDWFOQ{!f8H_OSpH41Iwp4YO@Kc7gb!*zG5U%5)@BN?RKanWY+c$|*j zLU0bvk5dtIM;5z&9k6~=&d5kIY!ImW&C+H5Djqq%RR7Iq*v{0tw*gomcPIXRbJMTtkH3TiLIbYzlHQjxCukrQ3@E!Gxtd{y4ytWZ{+^ii@rCKucQQ1ytv_|3*UZ4T3&m3LVV8{3By|1n)Jlo>oYzix)ftMPHcaIIA02+^-!#o zXV~mpe4_ZbIz8|EXmF&9E^|l#i7jc*mHN!c440P_L3d_50>DYhdMDp;(PqI?^!?al z^1A+yEYG`)7Pil0jd+Wt1f4@DM~Jta`$E_knKIX!*#a85x`knsknl7dq>QJxcm08k`TEv*5)l%8 z67yFy+)@G;Xm<^qDw4L&%iea^_AcGd7!^<+cQ}==Jn$+!7&)E4_Gzl(>Z#nTUw1I8 zdq-V=cgwu(u05+7o${`HU%OLSMi`KH6%n=)73F7EN0MVUUvumE+b0c_AqR4W_z%QT z<<|56pJsh>i9BML4Rg%G2!S+q$QEA{QTsEgoWEaeG0)={0SWf6)NG`!rK|jSg(>x9335Nu;mj`N7o-I zfh#9yM8e6Aj>QVnI;^Tm4bs|2Q6J{I+@idS9roKd_zg)yAdR~5h$2`Sni@OfdTg(! z&CNOe|3G2p=;EU0=eIhb{pf}wW>a{?=g)>DH7M|oHX$q&ef|mT{%i2j=S`jlny{o{NIy*aS#A$sM zxxB60U5MhK1gs!AAtBgXkd=}`9#qW@

FxgFmqKcPM4tzrEcSr{J>#?0S?|l~lni z)JkTM4053Yt(?myLbPyO)YR0fO^r=W7l&&|@e$85GPc^*-wx9#lY0B_7i8m45vf^c zi(NUN#>TdN`4Wzjh6sE+e&WQoY$H8Avf{qT@NjD4u80Doq_^ZEAM|&j5Q+1Nif+Hp zpM?IXTmDe&c323HYH87wi%%92m+mhaaK-*bMYN6Z48o87#=^nt2oa=r`LY5|@))_h zH-=P0mnXS|#%S38b>D1W->i=yy96ILsh#&G|0)MF_A}fGp|&T%pA%}Qj^(IYUHxD6 CQz#Aq literal 0 HcmV?d00001 diff --git a/ir_rx/test.py b/ir_rx/test.py index c3c7160..112db2e 100644 --- a/ir_rx/test.py +++ b/ir_rx/test.py @@ -32,7 +32,7 @@ def cb(data, addr, ctrl): else: print('Data {:02x} Addr {:04x} Ctrl {:02x}'.format(data, addr, ctrl)) -def run(proto=0): +def test(proto=0): classes = (NEC_8, NEC_16, SONY_12, SONY_15, SONY_20, RC5_IR, RC6_M0) ir = classes[proto](p, cb) # Instantiate receiver ir.error_function(print_error) # Show debug information diff --git a/ir_tx/__init__.py b/ir_tx/__init__.py index ef52e84..6fe7710 100644 --- a/ir_tx/__init__.py +++ b/ir_tx/__init__.py @@ -5,9 +5,10 @@ # Copyright (c) 2020 Peter Hinch from sys import platform -ESP32 = platform == 'esp32' or platform == 'esp32_LoBo' +ESP32 = platform == 'esp32' # Loboris not supported owing to RMT if ESP32: - from machine import Pin, Timer, PWM, freq + from machine import Pin, PWM + from esp32 import RMT else: from pyb import Pin, Timer # Pyboard does not support machine.PWM @@ -17,10 +18,12 @@ import micropython # micropython.alloc_emergency_exception_buf(100) -# ABC only +# ABC and Pyboard only: ESP32 ignores this value. +# Duty ratio in carrier off state: if driver is such that 3.3V turns the LED +# off, set _SPACE = 100 _SPACE = const(0) -# If the wiring is such that 3.3V turns the LED off, set _SPACE as follows -# On Pyboard 100, on ESP32 1023 +# On ESP32 gate hardware design is led_on = rmt and carrier + # Shared by NEC STOP = const(0) # End of data @@ -31,15 +34,11 @@ class IR: def __init__(self, pin, cfreq, asize, duty, verbose): if ESP32: - freq(240000000) - self._pwm = PWM(pin) # Produces 36/38/40KHz carrier + self._pwm = PWM(pin[0]) # Continuous 36/38/40KHz carrier self._pwm.deinit() - self._pwm.init(freq=cfreq, duty=_SPACE) # ESP32: 0 <= duty <= 1023 - self._duty = round((duty if not _SPACE else (100 - duty)) * 10.23) - self._tim = Timer(-1) # Controls carrier on/off times - self._off = self.esp_off # Turn IR LED off - self._onoff = self.esp_onoff # Set IR LED state and refresh timer + self._pwm.init(freq=cfreq, duty=round(duty * 10.23)) + self._rmt = RMT(0, pin=pin[1], clock_div=80) # 1μs resolution else: # Pyboard tim = Timer(2, freq=cfreq) # Timer 2/pin produces 36/38/40KHz carrier self._ch = tim.channel(1, Timer.PWM, pin=pin) @@ -47,67 +46,51 @@ class IR: # Pyboard: 0 <= pulse_width_percent <= 100 self._duty = duty if not _SPACE else (100 - duty) self._tim = Timer(5) # Timer 5 controls carrier on/off times - self._off = self.pb_off - self._onoff = self.pb_onoff - self._tcb = self.cb # Pre-allocate + self._tcb = self._cb # Pre-allocate + self._arr = array('H', 0 for _ in range(asize)) # on/off times (μs) + self._mva = memoryview(self._arr) + # Subclass interface self.verbose = verbose - self.arr = array('H', 0 for _ in range(asize)) # on/off times (μs) self.carrier = False # Notional carrier state while encoding biphase self.aptr = 0 # Index into array - # Before populating array, zero pointer, set notional carrier state (off). - def transmit(self, addr, data, toggle=0): # NEC: toggle is unused - self.aptr = 0 # Inital conditions for tx: index into array - self.carrier = False - self.tx(addr, data, toggle) - self.append(STOP) - self.aptr = 0 # Reset pointer - self.cb(self._tim) # Initiate physical transmission. - - # Turn IR LED off (pyboard and ESP32 variants) - def pb_off(self): - self._ch.pulse_width_percent(_SPACE) - - def esp_off(self): - self._pwm.duty(_SPACE) - - # Turn IR LED on or off and re-initialise timer (pyboard and ESP32 variants) - @micropython.native - def pb_onoff(self, p, v): - self._ch.pulse_width_percent(_SPACE if p & 1 else self._duty) - self._tim.init(prescaler=84, period=v, callback=self._tcb) - - @micropython.native - def esp_onoff(self, p, v): - self._pwm.duty(_SPACE if p & 1 else self._duty) - self._tim.init(mode=Timer.ONE_SHOT, freq=v, callback=self.cb) - - def cb(self, t): # T5 callback, generate a carrier mark or space + def _cb(self, t): # T5 callback, generate a carrier mark or space t.deinit() p = self.aptr - v = self.arr[p] + v = self._arr[p] if v == STOP: - self._off() # Turn off IR LED. + self._ch.pulse_width_percent(_SPACE) # Turn off IR LED. return - self._onoff(p, v) + self._ch.pulse_width_percent(_SPACE if p & 1 else self._duty) + self._tim.init(prescaler=84, period=v, callback=self._tcb) self.aptr += 1 - def append(self, *times): # Append one or more time peiods to .arr + # Public interface + # Before populating array, zero pointer, set notional carrier state (off). + def transmit(self, addr, data, toggle=0): # NEC: toggle is unused + self.aptr = 0 # Inital conditions for tx: index into array + self.carrier = False + self.tx(addr, data, toggle) # Subclass populates ._arr + self.trigger() # Initiate transmission + + # Subclass interface + def trigger(self): # Used by NEC to initiate a repeat frame + if ESP32: + self._rmt.write_pulses(tuple(self._mva[0 : self.aptr]), start = 1) + else: + self.append(STOP) + self.aptr = 0 # Reset pointer + self._cb(self._tim) # Initiate physical transmission. + + def append(self, *times): # Append one or more time peiods to ._arr for t in times: - if ESP32 and t: - t -= 350 # ESP32 sluggishness - t = round(1_000_000 / t) # Store in Hz - self.arr[self.aptr] = t + self._arr[self.aptr] = t self.aptr += 1 self.carrier = not self.carrier # Keep track of carrier state self.verbose and print('append', t, 'carrier', self.carrier) - def add(self, t): # Increase last time value + def add(self, t): # Increase last time value (for biphase) assert t > 0 self.verbose and print('add', t) # .carrier unaffected - if ESP32: - t -= 350 - self.arr[self.aptr - 1] = round((self.arr[self.aptr - 1] / 1_000_000 + t) / 1_000_000) - else: - self.arr[self.aptr - 1] += t + self._arr[self.aptr - 1] += t diff --git a/ir_tx/nec.py b/ir_tx/nec.py index 8cf39e7..1e02c9f 100644 --- a/ir_tx/nec.py +++ b/ir_tx/nec.py @@ -33,6 +33,5 @@ class NEC(IR): def repeat(self): self.aptr = 0 - self.append(9000, 2250, _TBURST, STOP) - self.aptr = 0 # Reset pointer - self.cb(self._tim) # Initiate physical transmission. + self.append(9000, 2250, _TBURST) + self.trigger() # Initiate physical transmission. diff --git a/ir_tx/philips.py b/ir_tx/philips.py index b069361..a37dd5b 100644 --- a/ir_tx/philips.py +++ b/ir_tx/philips.py @@ -6,7 +6,6 @@ from micropython import const from sys import platform -ESP32 = platform == 'esp32' or platform == 'esp32_LoBo' from ir_tx import IR # Philips RC5 protocol @@ -15,8 +14,6 @@ ermsg = 'ESP32 does not support Philips protocols' class RC5(IR): def __init__(self, pin, freq=36000, verbose=False): - if ESP32: - raise RuntimeError(ermsg) super().__init__(pin, freq, 28, 30, verbose) def tx(self, addr, data, toggle): @@ -42,8 +39,6 @@ _T2_RC6 = const(889) class RC6_M0(IR): def __init__(self, pin, freq=36000, verbose=False): - if ESP32: - raise RuntimeError(ermsg) super().__init__(pin, freq, 44, 30, verbose) def tx(self, addr, data, toggle): diff --git a/ir_tx/test.py b/ir_tx/test.py index bb54329..b175dcd 100644 --- a/ir_tx/test.py +++ b/ir_tx/test.py @@ -6,7 +6,7 @@ # Implements a 2-button remote control on a Pyboard with auto repeat. from sys import platform -ESP32 = platform == 'esp32' or platform == 'esp32_LoBo' +ESP32 = platform == 'esp32' if ESP32: from machine import Pin else: @@ -56,7 +56,10 @@ async def main(proto): # If button is held down normal behaviour is to retransmit # but most NEC models send a REPEAT code rep_code = proto == 0 # Rbutton constructor requires False for RC-X. NEC protocol only. - pin = Pin(23, Pin.OUT) if ESP32 else Pin('X1') + if ESP32: # Pins for IR LED gate + pin = (Pin(23, Pin.OUT, value = 0), Pin(21, Pin.OUT, value = 0)) + else: + pin = Pin('X1') classes = (NEC, SONY_12, SONY_15, SONY_20, RC5, RC6_M0) irb = classes[proto](pin, 38000) # My decoder chip is 38KHz @@ -75,30 +78,30 @@ async def main(proto): await asyncio.sleep_ms(500) # Obligatory flashing LED. led.toggle() +# Greeting strings. Common: s = '''Test for IR transmitter. Run: from ir_tx_test import test test() for NEC protocol test(1) for Sony SIRC 12 bit test(2) for Sony SIRC 15 bit -test(3) for Sony SIRC 20 bit''' -spb = ''' -test(5) for Philips RC-5 protocol -test(6) for Philips RC-6 mode 0. +test(3) for Sony SIRC 20 bit +test(4) for Philips RC-5 protocol +test(5) for Philips RC-6 mode 0. +''' +# Pyboard: +spb = ''' IR LED on pin X1 Ground pin X3 to send addr 1 data 7 Ground pin X4 to send addr 0x10 data 0x0b.''' -sesp = ''' -IR LED on pin 23 +# ESP32 +sesp = ''' +IR LED gate on pins 23, 21 Ground pin 18 to send addr 1 data 7 Ground pin 19 to send addr 0x10 data 0x0b.''' -if ESP32: - s = ''.join((s, sesp)) -else: - s = ''.join((s, spb)) -print(s) +print(''.join((s, sesp)) if ESP32 else ''.join((s, spb))) def test(proto=0): loop.run_until_complete(main(proto)) -- 2.47.3