From 16847ef5e97bae81c257759a86d191d326fd3039 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Thu, 23 May 2024 14:33:54 -0500 Subject: [PATCH 1/4] Add timers --- CHANGELOG.md | 4 +++ requirements.txt | 2 +- sounds/timer_finished.wav | Bin 0 -> 35874 bytes wyoming_satellite/VERSION | 2 +- wyoming_satellite/__main__.py | 51 ++++++++++++++++++++++++++++++-- wyoming_satellite/satellite.py | 35 ++++++++++++++++++++++ wyoming_satellite/settings.py | 28 ++++++++++++++++++ wyoming_satellite/utils/misc.py | 12 ++++++-- 8 files changed, 127 insertions(+), 7 deletions(-) create mode 100644 sounds/timer_finished.wav diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d811a3..798da0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.3.0 + +- Add support for voice timers + ## 1.2.0 - Add `--tts-played-command` diff --git a/requirements.txt b/requirements.txt index 1eb7bdf..a1bbfef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -wyoming==1.5.3 +wyoming==1.5.4 zeroconf==0.88.0 pyring-buffer==1.0.0 diff --git a/sounds/timer_finished.wav b/sounds/timer_finished.wav new file mode 100644 index 0000000000000000000000000000000000000000..40a97bca6c5eafa4e113ebc076d48ded0d9c95a0 GIT binary patch literal 35874 zcmai-2b>ed`~PRtd+sjR8wW`59h9chQ2~*r(gaaaL8M5NZUJecR0RZ4iZrDOBE3mb z1Qn$o*L!m5b@%@~JDJ<$;QRY;Udiq}^Lfh5Zg6<<9`j=F9z9Z*5;ElJ!QIEbJ0&ZI z5W>N$)?h-q?IuJ!@V!-shFX_) z+8iZK3&-SF=7)+VSNL2q;gn`v+`!ZmHD=XnSgS14lYBd-t6CXSI_j4+$vt$x=Dl_I zy*Uqxf3oii7OLjOe3QH^^K?yj%{n!frR-;9*(&`?*7n+?t-%wCa*^JyM9sp8)Ar7ur>(-cx<1a?(U zFYc1N?7>g>FJ!OF|D|lXtB+vO>f^?ze46RWT9DN`V@T3_<|e99!Cd>&lK4Dx_O1IL zXM6K5mcHV2gbu4O#k`VyF=Iv6)U25q`AN-V4C?NoUmVYtZp<5={qOze+0W)3DdFv- zf<{$Svpexvy0^v~S$u{g`EYEBW{~iQt7&@NlaiWa6iiBF{O zs4*ey^~~cbcjI2sdB{A^#fr}hKgdJ zEwAe{WLv#|Sl=j$&+U`lDZ4x;qv#c@(mR}6rTaH-TFRY_Ej7N)bf=cbn~i$Ke*cxq z1|?JS8a`Z~eKluQ;hYM$yFHnzy&JnP`K64pH4bIIoVFpsW2~Y4D^P5IqO@)P=7)!} z7w0$&R+azms^IHtzKz+H^i{gN#{SHmY0VPPnAECs!QUMDrRVcK4=-o;%NbL!r)-OJ zd1$Yyy?H|7-n508`!buPw@qpnb6tI$zvwzv-ltHTJLKWe9C!Z1($5_)2d63%O&=t< zQe!ihWR6Y0oKz=PrL9FedoryviXP^keAqQ-SpNAElYNPQzPz5{XUp=Gni+L6=cNxz zHpMm3z0S?^_OQKIye_YC&WN0h{3az?m9O~f%g*X1#LY>5Cp{F!ww#>2Ma8vj^}Kh;huYq;%acx}&CU2ey?@F@%R)m##neDON1*icg3-B$ zayH}@7CmKEdA=4pXwqW7NHnB9k+Cyrcd)lb=a5rH@V> zn>Zmxt-VE7dqcLPB|{3H%KIX3P+`Zi`}S@ATv?9J8aqDezf^15>eRx-4zUIukzMj- z*$$>mbFRp z)F)D1NxLokjf+$l_}ZRcwyPzL3J>KkE!bGRwW5*h``}xOi~5wf8;PH!@F@+FTgFc_ zO;%qK>U!H&epz~^&{D9s;M3xv6+4~Iz#4g}u77OX#8xTt)Gv~i2|LUV&1|m3S70}m z=N6qSxL?q;cwzZ8$3nkO_L?@&9E$IhJS3$}vNisOd9YS5`@;XFV^{gGVp-wsg1n;2 zvhDUyd`{9wlVhqM|9z4nrAzYj3A19_>U8q21D`t=R~Sp?6=oIMimsHsXqWl=lBMeU zrlppyNv`CD$#4Zr}7&^lemn;lw^C- z-h{cam-KN;Ysl;V%~n`?s%Tx|XGIH2pRzsgZXSA0LG(vr?C~!qZA*HP&@3+BFhez4 zSnlm@?^u4T_^qPY;*+KAY;o@2f*0iP>YB!U6mLn|n3S5hGj5&n74G{n=X7tF8(E%BSd>s;SsrsiA5`uY-u0bxw6La^{Z=xh z^ho)^N~c>0_LIM;U1)kI?qGbqgcFuEF-)eh z?mu;Ha1#QjT_M|w^7EzVN>`SLY$sjg1GBle)dvkZG3zW} z$Ny~^6g$}1OfyLKTyTT?RAqyT`ej9>@0PEzrMcGnKO<9BSM-T7p14NwYb||arx_<} zev};wsy(&slPaDqJ720S&$HHczV2%&7?khmvQ6^1<(5&Fxv`0+Nm{?WDZk9S*72)# zaQT;I%gXE98aX@o^7!u+ymp3Zf9y!h2+O|M875A*SK;Ab@eOm1vRx_vv+UFI9P19p zBCnP|CQs0AGx9P2#%V2|#V#<)f@Q_C=oC!AY_!>h}$T zIV)~=+^SfsiPt5oeju#_jonInruATXS^2M4;u!B)6O54!SIZ5X%y(m##chdgYJOY4 zTlFee8OV3ERrA ztmzen^ z4gSNzZN=x>Nya)clVaP)&NELkyr7vXAIJ~&d7WQWPO)C8*lu-Je&}-eY`j`=QgguY zi8&Z!k9o`dk>O*_MfpMgq;H5TyK<}bkBVv5#g+d$(|q=jL$+IeSbx~`PRxOrd1jZP zkM_EvLa+pOx)U8cZ5ynD^^eLNXN7lTXo0Mmx`V!#DKTbV%ye_Iajmwiayr===Z=In{Vl`-$=7 z+w*#`4tG&mq^)P{YUW~wnTHvh>T0T%aDN5Id3QKVD&MvBx2>(L<2>Ps3!Wmwm2UR3w#&Xkr(|n^_H%;ANp3N`wA9f#b zT&&EjJYTujvCloA7Fu8^-61_Y*f20OdhU#UD%Ioy%t z-r+kRGRx|!_G*V2ijAF3{fw>jDVko2B$6M9@!Fi1?7vm&>isLqBn+lzp`d z!+v9Z(^%sq{anqTilby@@MrI*u5ON6_73(X4u`9=uXd<6*I#*G^PPUI(QWK+oTuNb zNmnLwfnaT4f$K9zFHnwfeC;~xT^#(HY*e(-nDth}4r3GJ$ND>(HL2fU=p5uIweNJyb3N-l z5?Cy}E_bT(v`)R#aKrGqzOS~m>Py+ryx+gulj54;u-lhAy0{*C+6J8bMcE+LAnjcJ zw}u^tW%_ru(^WQEU14G%)4Ra+r=zvwkfWFDnnx4Z&wn6Os*Kvc`o)GH4ZrBmXvxU~4U-HT^%t~>>MQa_i?bd#vx9f-N&uL#!w}KgM9xCwF_q24ibary~bG_(E@{bM` zlA(&X)PHHm>OJ~7dWCL}dbgq*cQ!N~#(DmBJqWStdTiUVCR|hnORwh2_S4zF?sJxU62>?bHubx|@oE zLrM=kXmEc~;SKKxhEUqDTI(}bm>Y(!l;z|$&KIn2k`*z#Q_X2_#Fml{*7tLgAIvOx z+TBhYld>$Ucg80%m-xmNtlU4HAl%a=T3(HFT-7jdpBO5UAvt!E~}s4yzpk_NN#e>e&{mwwQ@;CC2 z6&$fyg(l|qDH~G%i`$~O>3mR>m)Er<-J@228t+ccNcJ1Xh0ayfD40_4t#uBcW1N$` zEp?sctkULMRqV=JQ+(DnM%mmlJN0_g<)q*w^ErPT4zbAG{-5mcw{knH) z>FIoBaR=u-`Ks8pDZeF8HeV(AmDz<>X5-MBc(VyLe^6#4?L7T{AR(c}iu%3Ekg;S1a5FZHv}bUK8R>UnezA=^WQg zInA9`8Z4M!YVrJ}92B=Hc}(JXLw)`e+fzk#iu%}Yhw2-iP28KjDlSGf#q)b7u`iSJ>|h4UDS98A-a>LPcxOS7k?w?vyR_WGWxT zoJ~9zze+!XKT_#0{-^k*%Ir{W{o?o=iS1&)Q>J-)z`A&P*#&nyMdO&=iOUjN8>f-o zj%KB~#Z7H_0gomx?&-w**um;{{&?%@;*_$!t_HGaP2&^lC9E`d=N35+mgW@iwk8JZ zXkL$dFR?N%Mf*(fs%?GAh0=$Pbkf<79Pf!=VM>)Rb$2WuUE;2I)0e8+74v?=)wnI% zjL_$mzLIZByE~o}7VEEA`Xqc}o~5vQT2!nqd8wkdccWs8ximh@azMA7&$0hp`bp^+ zduiwi-9K?$LcY11@~Zbr#etH_^0A)Z>gv8mz5(F800n z4`Z&Y&iLorR+j!yZgJ;u3gb7H36=$hr?~m9(d9czD{QX?-csL78dA| z;-*@fn#_uo-fyh~%g$L(c$+9PO?51v#@^B%<8{ud<Rr*Ie7=1|@OAa?=AYs+%)M1T1NoJs%X?TW+=saP z`jXgFv6J-mxv6foby9g3`^CU%)kyQfxI^aG)tiEH$G_!wDt5cx9V`!&jY#{Af0=5)=zkj6Q$g0C3jdPVp`J1gcu?0v&Fc_W{;QdLpE@@3xz z`M-wuV@ZrcTf#SUwXpIPyPTc*@tQs61+jgMA1IRiCo2b3_-r@4HRZ1uo{xPY=DL=U zmhL0g(-j*X8-sgQFPjvxmyDN{*8;sAjjY+W>z*&UYq~x$bz#IUWe@q?IGS$hT&NX*ulzCh)9Ycq!~uu-wW&?%;eX_UGm z)W+4uw#)Xmd$#b4#%I#Stk<`dAN9Rv-)~)EC;rLuo%%n`^GwY&2H_d^ZQGBwzg=_r zAJrXA56l{aQL)Qk+hMfLwy*Y;$U5r}n2(!&(flDe+_ftE+s-(xL4zu27;Bzp=%VZt zT;*=%EYgf*3{u|h#$kxlu0mCfSnb4oEw<-;=#x4^^Y3#XVS!HGDw!6~)xnqIXOg5=MH?%hP)3%en?qA@XXIHyg2W+x6+5*FQ{YKT3LIdx8 z#~%*4S0;>7eWAZ**rKg3-x%2F>fq?+(g#+^+G&p%{?nJKHVd=7m5#-ZQJ%W|Ql(tK z-yqYK$rlBOyH`0fUGMvkauYRO40rY8)NhbJzK@*89IZXShT@g`b>A2|>RwVfg4^7m zIyO6(_|}p!>aX>8^k&Uc?hF57R}-hr{UBH@-=tL<`sh?jHGkaG&uMjz^EDv3s%P|P z^zAj-T)zJ&m)<$c-8k4lZqnY;f37>AEaX4)UU52|F7MkyifX^^EB(isk7YXo{oR9{ zW8GH+yJTB6-|D~9@yb5JZSOE2VdCI!C3t|V7iZ!*70u|nHkuh1TmKM`8xDRr9N>-|BZQSZ|2&?(h&?vTHSyR*yT znG(7!FV~dm>S;SD{tdPD_HdcqJ^ZuDr>Y^k)w<^D{+!@{+g-=C+EWzlDgQ)sUpHS{ zsMyUrya!!+_kX@i!Zqb}?R&Z@>H=1%G^p^}Tw^@90>?Rx`l0p(?Of#rA;Uk*o#k%gTg}f@{H>{}v#E(( z6H4&jch&a18|cOTq`IN~N}HuxL-PG{&$I4)Z!mOT-dS@`Yl6R#eHYs5t>-p-^nqVU z1JxAmOl?QiO;R(E;c4Lh()(eki@a1_q1~i;UvZm1>nn73@@)0z3XPQ8H5uABRFgSx z;I3zhd%8Ci{8ZLbJwn?@Gfa`k|L(i(HhTK`=LvTd%QS=F53p9U)4>nCf4B#G7Y1{= zbt+EVTeD9wQ5fNW(v##-`8lD#A`@cTRU>4s;9Bolcc!;qa0b^$MYIPr^OYZyX#sIFVWZ*LSOgUWBOEXZpkSqvjy?s5O`S?(}yuSLB zW~6$yd=l^Vt@qUSjt#UYPK90FShG~=C;H%I@B5w+z7?S_Wk0H>Yx=3bmT%*0_&0k( zp7cPDuv?L%o}_7^D&mZxp1ud3KCr?*%iU2Hs2i$FZGOyBw7Lhi8AuAZTu0)I!H9QfSZ$-CNri60?9r@Ep}RrQc<30?3Ryn5f; zL5_P;`HgyndYR%0_&e$@Z&U9`{~SI;{++5oJw^4W>?yv7|2ePMYYfgHKPsM4_fcO^ ztS0q?dS5?pJAW#_S@xuAr}{mWSM~uv!#~`cgw`YXOs?N`lFTqd`IUSB=m_x_U54Nj$eN3~3OSXRt`5ctwp>>Cy|k`wZ} zs)?%k3I*3V^t^wZ@2uYn<1{OmtNu{N%byd@1b*u~-tN!17 zJp%ptzqzf7v&vhFR96>{af@?B(aXi=cl zpBmi3ALN=UIw=<^y2?5TbAkbXhd|Acp15T96zR&ZkFoG9lg zCdw8IH-hZ~eFC!3Wnq`>JB42Pv%D79gufdQ{0{;x`MTUMaDJ~Y>@@!9{F0?P@?4D3R;3SLXCxwxR>QR#W49NTpOW9s9|tsP|0^D zV`K;Ar{$l@CX=rGu3)>ME_910+$Gr>c?0<_ZmV!1bUb(`n8GWFgXJH#eGlpNte1f^91Q ztk9o4&7I|TadJ*e%))#82tJ#?B%CEPxxHLJZWfs%?BxgWpYmOVwj_yL!+pY8iH2kf z^ZB>JXSq~vDfwNfDV*RhgR)ZCPO`X` z+#S*eYO?cZK)FKbMzTo@Q0^iMGFA|HKR-|?78Vm9v2a&OC$dgR5VCo_@Tt&%{7e)u zbIXWR7%qf(H{VP6TbM{55+(Ns8AyH=x(X`c31PXANw$(Ql1Jd5mC0lwNl*)Yg)72P zat=myoIFQP3cUogP*?a^FhaDu>$_49`XXYF1#kx7utd{ zAWR~^kke!#NhF)WznSo=a8hVP){wJgHyK8Xgn2>_p|`M1aKY%elLKTf$sqec)kPR1 z924r21!OPTL|!5#!eZeCp}+8%P%gYkHb9<_NDK0ZFbA|#K$%S5A#2HU(vP@=ufTUC zj3`&=3YpiEsiXn9Cd`MbrV2-dSTdTdfKd*Cx!W&%AWRUx6dnkz$ou3|GJ!ONCugNF zQTR|eEU00A=0QaRh>H9Sl};7b3U`IZWIS0!-XrZusjx#>2y=WG#@Pj|6Gln(vp-5`=IibP-j5sNX9{)ek7jU6uuEw2wUNPB*6T>PF^9+h+X(u zSOa-}5!^xx@+w&Sk|c6l*a@~B!Yv_=^dPT+y&VyRi^4Wx6T~bPvPf?-h&&B39|#8_ z;$Gn&K~J87F}_4v0Xy&qSau4h1uHzGy~$wG9cH;qI3?^7eiClN)7Ts;?*rN8`J6`o}yX#(Gk z4kQc4`2c)>6YdCpAp7al_XE1>kk^UxGxHX;^y z?r($jrceU+6O}C?rV^gKn_#^y*aQPq(}+Al%<$yg1M5GKT>;UWK!hx!hI@AhYRwir zLJX+`b5xHc5I;Oyw}k&-?1GR;>O$@5ptOSjzfgk#pTwS;yyif$s%zemC;5&fI z3>c?V$bqOif?d#%RH&*Z)XBsAJOE{>zzY`mX4Qcj^u!G#d?4ftPEe+a%6Pb+R`AV+ zI(<-mDnv{oTH=9rE>z@z8A&2Fz&in+a0leigS#1mn$p0U2KOT%ltDyP%3&r`q4qeq z^G>L-5R?IUa#FyS3`!p6rwHOYK^X(eRH#!4nM%Q04);R?BTRv8Mp0P`aUJk9nZc3* zF;&D1Ph1JSeK7YHh?odU0yR;k0GSfN77KIdgG}Y{_Cxit;2#f44n|)AFDJ~h4*V0K z1~pV)DJngX$plsl)G3FWD#6V{cXLfZ`4Q0WDo zQ&g(KVg$VcV!FWQgjrU=b6^ye1V--$JH}Lj%?Lem5(JwQ-T|0+7%+@L4mr-PRoW;qBd57-2#Ndpn6 z(l2@kM5P*R$|@yhl0h}-t$^$Si0Xq`!kFlM$k`DpZC94wPtBfHDLYKV%nR^vWtFW;4MH+ay*#BMM?osHHJOVjdi)hZu{`j&bkt=KDvKQ*5dNy#3H=|D<4bc@2IWMLI4X)kF~I2K4r9!N>QOAe&u8*wiJ zDLJ!Nqgv%aO5O>Rk_{qA$@|4W0x4M_l9Ed^Pt|nQe4@tkl>HH;q&7@S?g3J=R3s%+ zfRs$FnGK}mx2at%4Xa4Wr?USPNy&_&5w>8Ml*~(in1!U|a*>pD0V%mpBqhh*e*;KK zPr(@=CDpQPkC2i+S-`svNXhFD*WZ5{NJ(|U2j#OxQZhd-9!SaLnlC*F!ZMbHkdpI&lzcJUk!u7}a$p20`EC^{=>}5rQZ-U?!edCuar!jb zHXtS6EJ^@UvSSq~`6~B??p72jX)+oVKl-m$HY}M8q~r!5CBFbt(gURAv?@~aaOTjo z^$A`e0RIk@0x8)BNXdiQOL81QN?sF5$!$PNu1{Bpq-68N(?Cj|4PF*WNw-K!jxE?5 zCM7$V-vv@~QRd!iq~vozN)CQF1W3v3Fe#Z}nkJHxpNXX86R}DlB|C|vWKQl0k(9hp zVzhq-q-0&gQOhzQC7;NgFOrgt!=&Uyk(3;slb+wSq-L0uoERo0e@#yWQt~vAl2iN* zfRxOSA|>xekdoWe2c(=ANy%wIN(RHEA||#g3BQC%$;SRWj@2S5nH@n&UQwSk&WKwsl9EkC zQgWW`LSRA_DfxWqJ&~0BStKP@A}Of`QgT3;lziQtXFFcjtK^hON@$smxDM}d@lUL++OTDm2C z6ecADA}M*s(bAe$b~#K+I@~1m0+5mmfRsECUtc68eIhCOz}FT?$suLeO5%Z(yk+~t zwLS1J_pka1W4G8lmO~;b`KMuy`XiB)}K$ASD|EDcPqQDfvtUDcM5MKSD|dfRtP+l9Gd}NXaLCxj;%19VI14 zTK30IH_3pM^zyF)tu)$px%_(BlJZ=Ul+^G?!=&V`Fe%wmBqdM!j#ZJ8qpf-Ng<(>1 zvVk|(3X_r{k(6u|CM6GrNy)c7tAnvXN-7MSt4PW5VN$XHNXgArq~xD&_%{_GC7b9U z0x4M+NXa@PDS04FO5U}mRgsc6ft2h6q~t@9lC{_U}SrNXc{{CG8?9c?3wwpJL{l z-G<&GDH#W(WCDqS!Xq0^gX6m*Y zSDWhtDVb(G1*GI(KuT^6lafXtB|88qIWG7FcS%{Otq-Im5lP8rx-5~Dd;>_yQXnM< z*w$4(5hf*H4U>|2=C{mWi=^a0*^eSAxz_&BHs3a*@<VN!B(1Sy#aq~y7f zNhBqQ0x8+a^n$UaK3UUSBqhyWt4K=fMN)Dukdi+FDcJ`|$sdgkOs@ecIZtz4Bqfgm zDcK!J$tUfL9d=h2ASHVNDfvLNO+O|~O77ESD3iFLNJ=ho^tQLR4+m2643Lsv0V&xU zNXZHyB^w(*0a9|Da;8X14yYm}r@AJ4lY%ioN^VlG5J|}{#yeG{z+B~_&4 z5@DQ3N;-g){L`>f-$&afOiKRXNft@TWg;or21v>CvLUL$+Ic`qZZ|9kQt|^JCF=+i z0yVtzMN;yxqqpm-M_olq_6AaNzu~z4l(s@OA4tiB;AxSRoFkHwn?1)xQgWq8O127< zl1U;d`7w}^6GT$-h@;qP08;W*ASGuiJ_k~Ax=2c%*Y;Dlu0~4sk02$-11b5sNJ=Vo zyVXA^x{IXb9?v&GO1@S_O3oxXa=YqHts;t)`~?06GFX@z+Tk1Oj1@`AQ$R|N)D4X< z0aEg5ASL%XACw=f$n>a0QnE3Sk}r7U9z#n045Xw%Bqe{9)sMTAI!7cW2aBZS!ot72 z(+xo&CD&!FG&c&9l01--D`S2DQu58%G9V=<11UKJNXhnLQgShnl1-{e$x9+B`8JS} zODi+;wmjGYq~s2flzbjY$$g~Q+CINkn3UX>vLk&4kdnDTN@fEo8Rt+aVebf#k~QL< z6-mkGa$hN(0i@*69z!Pr5CAyk(+%VVIOm22%11ASLxc zO0E$}$?;)Qa&($C{Z}9*|8T|tDLDX0$%Q~lb^uaRBa)KkA}Kj5OiK1mSP!J+S|BB# zi6SLafs`Btq-3ZJ{)OJ~WpYm-CHIP?WPczfcRK!){|uyLZ6GBl04Yfd`T!}}-M<(} z$smxDKLRQFepx*rC2s&JIVI*ckdoVDAIcIOhYAnmrIa25Qqq<%Pb4LG1-h4y3zL#f z%^iT0ye*QF*+5D@U6SU}M3ItNKuT_{A|=;a&M7N_l(biok{^ai$NsUBO@&h0xLn0}8AcBGQnDeCk~5RE z)kw+gm~)9|fs`Bxq+}pWO4ibU8h;Z=$!#JjnOF2d1S#1nOiJbyHv>}AtH}jYvLJR4 zkdhYbnc`F+B^y?elHID2l9NPI@>w7yH&YlmQY(^@iyUSk zCEJHd$@im3Nwr8ywj%EWDcQ1o0g#e!sILMknGU36uRwuFN?JuyvREV~8*o#Alzb1? zzL$WM9A!QbcMwR)jX^~fDOp!{5lG4I)kw+vKuZ2%&Ipr|p^Cw-mw}X=DUy=k${YH0 zA}Kk{cV2!=Bqf#FQXnOpTLsvO)=4BK7gmvy{VM!bq+~yllxzj0q~vcv zO8zdAk`KeAWbZI3`5KUtZLG_Ilxz?tC6}vC1hqg)zGz!gMM^dclad#Jl+*($`IVt_ zOn1{rwG~Lo)*>nSp>SN|6-mj~@?*Zy5v1e|^L$fNjWL3h+z+H=Hjt7A#Si{kA}P5B zNXbq>O8#OxE|QY9!=$8M6#!E510W?k11XskCMEyTo)Sq(KY7yI*j{M6S4B$xYB&j` zWWZTy^8qROHu+oA2uMjTOiI=QQu03_B_D{SXucc(Z_4_1~qUIJ3Gg{~2hlGme1Nup|O$TGDPNy(lfDY-j>lw3*H zdRsU;*-h@jKuSIclahyn--@K<5|Na=CX$j@sz}Mv!5)gA!=$7w^k0~ij3JxVtALbz zp^B7Lx!P1CCGREwSEmG0^4BO*@~pmt=01>;2V6SmY#=4;SCNuM{1PA~T~4=m zyhuuJ08(<1NJ_pWl9D@tl-#D@rW1ga{7)n$`vWQ20!Yb^L{hRCNJ)qDWp`F!9gva_ zB1p+ZS7&bukdiC4?ZTwwm!1+JB{ztqDHln}e*(XXq~!D3Im+`uO3thzCI8aY5=luFkdhC8 zl$-#hl4g;V{I-getPiB*EbWu38>Chst%{T^Q(Hw+@=lnP+~Usx zQu2GC8po-o04aG7NXeO1q-0;sP(?2Phwl!MlKrblNt>!Qkdo8Azq<#8NlBTur)E!> zluQnjk~Kt9a=6SHCM6pHDcQG*l>8u|_r43HWPg#A+@!Iq{t`*a&Yrs76M^5z66FX@ zZ_P`}MPzjWGbycLKL*1l`lzdMlC0EP#t7ZTx`IY=zK1(DeGelDI zq56GIOI0Cf2$Pb^&>0{l^MRBsmG=_v04dqnd)tqsWP3F)l9E}WK_V%6F1Ua@q;vo& zIayH-q~yv7Qu3-gMb#Zh$%{Zr>Z(Y|kw8k;A-e)Qfs`B-CMEOLQ&rbx9r^A+O8P`n zazBugeSwtxGE7Rg^{4QWq~vswl)M!tC6@pxIe;q%Dsi!w^Un?aE|QW1RlR_etQW`w zQgS1Zk~gbJ$(mtOaztnsHws9}4PjC;OC%-h2|Xf6$*27PhDk}c!lhakCMA0TDcKN6 z$rO15RhoKZ6)AbbwrBxX;gpMu$nsT=`zoRU{?n04Z4p zq+}(Kk{<&p=?OgPpA7ut?;)aZlJEJg5v1fS^-e0qdlvDyKxs&V(Efh(~ogyjO8A!=4KuUfHq-6U* ztte9RUTAHYlzfW22>*~hM=?RRMEEnkk_S?<1(1@*RJoJi4O1i_O zQnCn0$vUzMASKsu<-!XhDVfH9C&1rcfRyYY`-?OYt^+CgW#~LV zlH3APa)WF;*MMvXQgUXfl}JiHCENT6DftaIQ8qq;l&lX_<5eIf-vUxH7D&m(d>)XJ zcSKS$L)I8b$v*JUopB;5`HJv8kdlpnlyr%tq)j9x3x#+tpNkPm$+1jDANPVtO6~zt za&efHTn(h84JeQdk(B%oNXfONKai4xft0)`D1m=G!S@f7lB2nNAc~Nb{EROE{&5Pa z!Hoq{atS#Gq~sPLB|E~u-Ch7D@d)lAWVS$)mtTQc`k+zym4S6Id`x zO8yQc;PEgixeQ3jZ$(ma3H;mg6d_S0B`*uZ!ldN$lKrlW}{sdC7zR*Va2ay8r6MWW4|u~eVW3D#E&@94cVRA&l9PcJOd)TRbwF_T2jc5% zU?N8VDVYcS;zFP@r--EF0;m#6$(SlqQW+*CR|}MsTuj~tQu5y_Qc?q);XHV~2vp4x zVL_ObYy?c?LNciuDcKqFGg9(%&~5~dt_F;7N);)&6sSl_O1=q7MoKOZlahUbiF~z+ zlw1R(q)TWCmT^EzCITtB3;0P&O7@H(CBGFmi=<>tk(BHTEE$rLTYz=^TO=h1!_2h? zx`L9DXR1ia9zfTX2q%Rf9z#kV5=qH2ASJtsq+}+Lk{5*oKtTQjluHxR3z*85KuO&b zPKcyrQ8iNXs7Oi*LVb~xYzd4Pl9Io`6H*dIO8x_+<_X{(oj^54lakj!X(Ww-sB90! zpaAsXWuPMO04bCPq~udjLoBHPrtzZiJFtDoKemDh8KBGo`td3#87bKqI65cXiQgel z32=S2K#8QJLL?=B2W2H>ss*HEZQ$FGl)M2mR}A0J4B#Z20>P-QA|>4-DcMvcB?BTU z`7dM_ge2e}>w%t;k~uIUBqbXH>zDxK2$GU_fel1bvJQ}vnJ{-w;3@CI+X2<50;O3K zxI!c)?*so>0x{!7QZfUWHzXyq;YoG^0Z2*71Ykt0;QIg~Q&O@9Fo;?p0E=M!l$1<_ zF=hfUr~vY|0I0|^_$E?PG8OJe5J*ExO3Gj+Qz32~D4j5FMoK0FDVYMq9uH4U5mbcy zBa)J7FrP}OsSLD8N~$48DvS%6%1Y3cK%E@$gDLQ`z_%uvluQzp0%#a1X%ItlL;!1gY3vZR>I2-b4N+Z82HmV1fDjqQvNZL zl*E{flr%wh9=>gqf8^lqASsEgA|)lAFn7o=#y~boN+MI~h5L!5Br=thltii#Nl61d zNtCJdKo+V*Qqlx^g}|43Oyrld7dR*^E9lz*iBq!g3#dPo&grV>d-MoO|w z$OJM{l9853>LGuKdC;5DmsE)~n`FhBDAkD7GbPQ0%o|!M*+(-``VuKNMqg6?5xF&{ z#Qd~Qv?AL_=}oGPBqb>oiFqU?dSfm|O0w+KhU_Sd32vf})=4WudJcW@h2x|zd>&X# zmWkR>M`=MuAW~lzlh!WPNk>ktv{I>djEps*uVkh5Gb^hd>ty~^OUXj?q&4B(vpm!r zTp$Bl@ss+~Ow=yfDCbF4)QUPfPV}b-v(OnqHCkBpID3&=>Vqnbh}kh7t)1qlRyLog zqnS{N2lmt2X#}dI5vhenz)#6a{m@2_>TPDnGmZe?ctsDUW^J^hjlKW3iuTY?8aGla z`D0|3CDJGQ8tXz`WF3;9Wc~j|qe?cL(m12DMcW^ZiT=?!(L=ICk4I`rSJm@H_DGRf zM0TcGBDK}yJiZ<2kKVK`StOQgkIq%iy0XwIzH=Rb6>re#Xvjldz=DQ zcu0Mc9_^8%z&1TtPHaWqIoccj#4`^m7G|JvrL0tipVY?gnKUohC*_i=q*syg=rz_z zkH=-fJg8&Y*cE2MgZa={tX}Miw9wpWWe4_B1&hFX@ccL%>qjrF36IBVnJ+z4&HrD= zqT`uGW;UrUMPt=dOXMedVeUvRw%8qtw6nX%GO>|MwX@Mk3iOYR!Ftd}b1(%}MMj`i zNKXg)Vt=HD+3-vc>}46FM3qNY~kPM-#5y+3K0D{c$}`cMqXIuE^;wC%V=||HySV zt^y-t^59K(3Q;AlM9`nDo~bvjlUngvL@O$B^+>bh-$&R=8Lg4onG$Vu{VFMOO-xrj zxKd?GTodC{8@VFKsC1pplsI>o2@hOVv2|hON*80|dJD(IVoLtFrleNNgXiBXsp&s0fcqc_^9hDAjS^QXCSPUwO8sg0`8iZ(o? zDw#JO5mnNaJIzZop$e->q+^lPEE?;lZK{%@O8RIW zRnT5Z!(!rD(zBS%@_*}b1Z)&64$H%OqzE+TqccvUNL8Q?z1YlPWGRYt-@)Tjh>}C z=*+Mv(JGuFJg{bZX4$3thrKKlJF{A%qe-p*l}%DaUa_cXqu0z5nMbmrinZvqR3ppG z6p;~FPNtzFX5*&Wqn{(GBIXmVlKSyVI#?8DVQr~u*2B)MUfPzjNms0=x+OYh_Gmw=Ns1e(VZAgO8z;7;%4i?`jE+L{OE&3> zMR>GEoJ~AX@%V#Q!4&L_k*epzw$zW&qza^~$Sl#ZnRm1nBeG`zd+<%OMCOY0p>}C5 zX&<(kKYco|lE^uxZ5Exi@Em`EqcX1nVz2MQ^KN94UHUG74}DJXjL=<`p|n&Jp;Dsw}wV!dTOKNq&}=B z76H#p9ciO^X=QlLs-pFwk|~&%6j{2Wp4et@$&dN7=qyg;=$TDAV;trwS=kuSSDGQ3 zlc^&sU_IE!-WUVzkw;{k>DZj2j@s~(Dd`z4^k8jfVLIAN{h3m_gBY2OBicjqqnhZN zSr$|=KX#^`?2}sQTt`Zq>&zuL6A$5+n$*!-|Io*y@U zYI}Svrmr50+1dHg9FO*}8Kqj*PtU9s83(JSGlrg0eUH~NPu7ciHp4U$>xrBNrl8-7 z=rO7Un7>>EYxbWW<*K-+AdsFIF{jYEpc z;-C-B&AgeGwpmN^L`!7lsAIXJeQ1k$(k#@Q_Ao!z!n0HtR!-*uN6Vr}T4s@E4f|LN zdnE-MMdW>_k*J0#nNRc>S#NX{mWf(fA8OgXq8aeB`h2lGOe>9#j+td-=f}rjIq6x_ zvdUp;jmDv$Y>X^+bQG4C%{Nn{onF&= zt9wQ2Ffw~b+M|6U^G9Ak-h*1HE>cZnO5eap9m~g_P1cUiAk7=7*WZC5?{xV-2)2sZwb)tP<82eMUbVFN<0|4xI}c zgXLq|>XB(q8lPpUu4O)KH0V?PIlw;2Mzhjqi+a%-sfJ}^DmLy%^U|m^0;`U;Xidz6 z^|7avwWZw5!sbnyy~yaOVoJ12BVsC+k+x|Sc+KLmxtHQR&Lc7hRiY}=N@ti^Xbk$v zGDX)$l{7n3U_`V_ZFU`LVIz6mndPVY$SP3HdLQ+qS*e|>@RRyStLZ4PhxO7k%_zm8 zQBcLMFpl(up`Ln1#$neiCmT1rOEeaGR*%a(=s8*+-B-P)$od{tvO6I4u<@eh@xG6a z0<~ymJ?KY$@e{|wdQgc6Ytw$JMjQ1KB~>b_VIHh6`Yic9s*BD}=Z>k^OtOk-9NH`O zNx7n9pJ?xPD={(XW8vWR_T|Fc7#541j@2+}V4GHt*2g_3R6)Jy+@i923>IISo9deC`*X9ycc17hN5Vj-M<~ zWUlDUtUf6RUP*eqqW3q_DrsJ?<=xf%Zm5q}4=Pq>9*> z@Eonc7PBx_^~}u9&P>nZGrJUt&VXdEUM==ik3u!{xvB1neay!ESUcK}_TuN`bySU? z*iY+VN+}1uVox}#SvKiACEYXT&ookQs;RD|SurXe=uH*OAJ0-A)KCjv;X(7Utn4h+ zC~5GDMa64&NIq!6>{3itEn1i_J5&GY7F9_R@rt&Xj-8o?wb2u^vFtQD`;1nzZ!=oa zUy4DaL|ZUMv_Ff=bo4o+EouBxROw3k*0YFo#-tonOFyZGDe=rAu`~6<7Mp3wLao?F zzeo>i!%tc*(!r0X&+)e_@K-L}Kyc&UOS;Qb0ZcgV5_JJx%yxOAl5*-EAW!j^Hw-|4 zBYloLGLg!b0nttAbGjpw?gz!apU98nj!JLUE>B9IBU4U)4+D33qEZK1{N;?uU7qNT zOgY`F;4g(&tza7vANG`$0Jv6LRJ>CjM?mG<}ZzHT^vd{FMr-q@*|7 z<%#q;lH&B2FEBfj;+VD>U{ANb)ouq$8Fm@fkqnMj1Dc6l{iPfh5Fysg;krBTrX?f6XKRiu{bsTDs_g<5Gws6SOwf2u>v{{y-hc0~XH literal 0 HcmV?d00001 diff --git a/wyoming_satellite/VERSION b/wyoming_satellite/VERSION index 26aaba0..f0bb29e 100644 --- a/wyoming_satellite/VERSION +++ b/wyoming_satellite/VERSION @@ -1 +1 @@ -1.2.0 +1.3.0 diff --git a/wyoming_satellite/__main__.py b/wyoming_satellite/__main__.py index ed29a6f..f878081 100644 --- a/wyoming_satellite/__main__.py +++ b/wyoming_satellite/__main__.py @@ -1,4 +1,5 @@ """Main entry point for Wyoming satellite.""" + import argparse import asyncio import logging @@ -22,6 +23,7 @@ MicSettings, SatelliteSettings, SndSettings, + TimerSettings, VadSettings, WakeSettings, WakeWordAndPipeline, @@ -225,6 +227,23 @@ async def main() -> None: "--disconnected-command", help="Command to run when disconnected from the server", ) + parser.add_argument( + "--timer-started-command", + help="Command to run when a timer starts", + ) + parser.add_argument( + "--timer-updated-command", + help="Command to run when a timer is paused, resumed, or has time added or removed", + ) + parser.add_argument( + "--timer-cancelled-command", + "--timer-canceled-command", + help="Command to run when a timer is cancelled", + ) + parser.add_argument( + "--timer-finished-command", + help="Command to run when a timer finishes", + ) # Sounds parser.add_argument( @@ -233,6 +252,17 @@ async def main() -> None: parser.add_argument( "--done-wav", help="WAV file to play when voice command is done" ) + parser.add_argument( + "--timer-finished-wav", help="WAV file to play when a timer finishes" + ) + parser.add_argument( + "--timer-finished-wav-repeat", + nargs=2, + metavar=("repeat", "delay"), + type=float, + default=(1, 0), + help="Number of times to play timer finished WAV and delay between repeats in seconds", + ) # Satellite details parser.add_argument("--uri", required=True, help="unix:// or tcp://") @@ -296,6 +326,10 @@ async def main() -> None: _LOGGER.fatal("%s does not exist", args.done_wav) sys.exit(1) + if args.timer_finished_wav and (not Path(args.timer_finished_wav).is_file()): + _LOGGER.fatal("%s does not exist", args.timer_finished_wav) + sys.exit(1) + if args.vad and (args.wake_uri or args.wake_command): _LOGGER.warning("VAD is not used with local wake word detection") @@ -347,9 +381,11 @@ async def main() -> None: names=[ WakeWordAndPipeline(*wake_name) for wake_name in args.wake_word_name ], - refractory_seconds=args.wake_refractory_seconds - if args.wake_refractory_seconds > 0 - else None, + refractory_seconds=( + args.wake_refractory_seconds + if args.wake_refractory_seconds > 0 + else None + ), ), snd=SndSettings( uri=args.snd_uri, @@ -379,6 +415,15 @@ async def main() -> None: connected=split_command(args.connected_command), disconnected=split_command(args.disconnected_command), ), + timer=TimerSettings( + started=split_command(args.timer_started_command), + updated=split_command(args.timer_updated_command), + cancelled=split_command(args.timer_cancelled_command), + finished=split_command(args.timer_finished_command), + finished_wav=args.timer_finished_wav, + finished_wav_plays=int(args.timer_finished_wav_repeat[0]), + finished_wav_delay=args.timer_finished_wav_repeat[1], + ), debug_recording_dir=args.debug_recording_dir, ) diff --git a/wyoming_satellite/satellite.py b/wyoming_satellite/satellite.py index 6102254..6d5fa47 100644 --- a/wyoming_satellite/satellite.py +++ b/wyoming_satellite/satellite.py @@ -28,6 +28,7 @@ StreamingStopped, ) from wyoming.snd import Played, SndProcessAsyncClient +from wyoming.timer import TimerCancelled, TimerFinished, TimerStarted, TimerUpdated from wyoming.tts import Synthesize from wyoming.vad import VoiceStarted, VoiceStopped from wyoming.wake import Detect, Detection, WakeProcessAsyncClient @@ -289,6 +290,18 @@ async def event_from_server(self, event: Event) -> None: elif Error.is_type(event.type): _LOGGER.warning(event) await self.trigger_error(Error.from_event(event)) + elif TimerStarted.is_type(event.type): + _LOGGER.debug(event) + await self.trigger_timer_started(TimerStarted.from_event(event)) + elif TimerUpdated.is_type(event.type): + _LOGGER.debug(event) + await self.trigger_timer_updated(TimerUpdated.from_event(event)) + elif TimerCancelled.is_type(event.type): + _LOGGER.debug(event) + await self.trigger_timer_cancelled(TimerCancelled.from_event(event)) + elif TimerFinished.is_type(event.type): + _LOGGER.debug(event) + await self.trigger_timer_finished(TimerFinished.from_event(event)) # Forward everything except audio to event service if not AudioChunk.is_type(event.type): @@ -852,6 +865,28 @@ async def trigger_error(self, error: Error) -> None: """Called when an error occurs on the server.""" await run_event_command(self.settings.event.error, error.text) + async def trigger_timer_started(self, timer_started: TimerStarted) -> None: + """Called when timer-started event is received.""" + await run_event_command(self.settings.timer.started, timer_started) + + async def trigger_timer_updated(self, timer_updated: TimerUpdated) -> None: + """Called when timer-updated event is received.""" + await run_event_command(self.settings.timer.updated, timer_updated) + + async def trigger_timer_cancelled(self, timer_cancelled: TimerCancelled) -> None: + """Called when timer-cancelled event is received.""" + await run_event_command(self.settings.timer.cancelled, timer_cancelled.id) + + async def trigger_timer_finished(self, timer_finished: TimerFinished) -> None: + """Called when timer-finished event is received.""" + await run_event_command(self.settings.timer.finished, timer_finished.id) + for _ in range(self.settings.timer.finished_wav_plays): + await self._play_wav( + self.settings.timer.finished_wav, + mute_microphone=self.settings.mic.mute_during_awake_wav, + ) + await asyncio.sleep(self.settings.timer.finished_wav_delay) + async def forward_event(self, event: Event) -> None: """Forward an event to the event service.""" if self._event_queue is not None: diff --git a/wyoming_satellite/settings.py b/wyoming_satellite/settings.py index 903ff27..fd9c959 100644 --- a/wyoming_satellite/settings.py +++ b/wyoming_satellite/settings.py @@ -1,4 +1,5 @@ """Satellite settings.""" + from abc import ABC from dataclasses import dataclass, field from pathlib import Path @@ -172,6 +173,32 @@ class EventSettings(ServiceSettings): disconnected: Optional[List[str]] = None +@dataclass(frozen=True) +class TimerSettings: + """Voice timer settings.""" + + started: Optional[List[str]] = None + """Command to run when a timer starts.""" + + updated: Optional[List[str]] = None + """Command to run when a timer is paused, resumed, or has time added or removed.""" + + cancelled: Optional[List[str]] = None + """Command to run when a timer is cancelled.""" + + finished: Optional[List[str]] = None + """Command to run when a timer finishes.""" + + finished_wav: Optional[str] = None + """WAV file to play when a timer finishes.""" + + finished_wav_plays: int = 1 + """Number of times to play finished WAV.""" + + finished_wav_delay: float = 0 + """Delay in seconds between repeats of finished WAV.""" + + @dataclass(frozen=True) class SatelliteSettings: """Wyoming satellite settings.""" @@ -181,6 +208,7 @@ class SatelliteSettings: wake: WakeSettings = field(default_factory=WakeSettings) snd: SndSettings = field(default_factory=SndSettings) event: EventSettings = field(default_factory=EventSettings) + timer: TimerSettings = field(default_factory=TimerSettings) restart_timeout: float = 5.0 diff --git a/wyoming_satellite/utils/misc.py b/wyoming_satellite/utils/misc.py index 5d39d14..0c8a7c5 100644 --- a/wyoming_satellite/utils/misc.py +++ b/wyoming_satellite/utils/misc.py @@ -1,24 +1,32 @@ """Miscellaneous utilities.""" import argparse import asyncio +import json import logging import re import shlex import unicodedata import uuid from functools import lru_cache -from typing import List, Optional +from typing import List, Optional, Union + +from wyoming.event import Eventable _LOGGER = logging.getLogger() async def run_event_command( - command: Optional[List[str]], command_input: Optional[str] = None + command: Optional[List[str]], command_input: Optional[Union[str, Eventable]] = None ) -> None: """Run a custom event command with optional input.""" if not command: return + if isinstance(command_input, Eventable): + # Convert event to JSON + event_dict = command_input.event().to_dict() + command_input = json.dumps(event_dict, ensure_ascii=False) + _LOGGER.debug("Running %s", command) program, *program_args = command proc = await asyncio.create_subprocess_exec( From af18c7c0f46d8f058a29a3e08346ea45f86b7268 Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Fri, 24 May 2024 11:04:52 -0500 Subject: [PATCH 2/4] Add websocket service --- .gitignore | 2 +- examples/websocket-service/requirements.txt | 1 + examples/websocket-service/server.py | 100 +++++++ examples/websocket-service/timer_finished.wav | Bin 0 -> 35874 bytes examples/websocket-service/timers.html | 244 ++++++++++++++++++ wyoming_satellite/satellite.py | 10 +- 6 files changed, 354 insertions(+), 3 deletions(-) create mode 100644 examples/websocket-service/requirements.txt create mode 100644 examples/websocket-service/server.py create mode 100644 examples/websocket-service/timer_finished.wav create mode 100644 examples/websocket-service/timers.html diff --git a/.gitignore b/.gitignore index f0c0a12..8798bff 100644 --- a/.gitignore +++ b/.gitignore @@ -9,7 +9,7 @@ tmp/ build htmlcov -/.venv/ +.venv/ .mypy_cache/ __pycache__/ diff --git a/examples/websocket-service/requirements.txt b/examples/websocket-service/requirements.txt new file mode 100644 index 0000000..02e1832 --- /dev/null +++ b/examples/websocket-service/requirements.txt @@ -0,0 +1 @@ +websockets==12.0 diff --git a/examples/websocket-service/server.py b/examples/websocket-service/server.py new file mode 100644 index 0000000..6fbf84c --- /dev/null +++ b/examples/websocket-service/server.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +import argparse +import asyncio +import json +import logging +from functools import partial +from typing import Optional + +import websockets +from wyoming.event import Event +from wyoming.server import AsyncEventHandler, AsyncServer + +_LOGGER = logging.getLogger() + + +async def main() -> None: + """Main entry point.""" + parser = argparse.ArgumentParser() + parser.add_argument("--uri", required=True, help="unix:// or tcp://") + parser.add_argument("--websocket-host", default="localhost") + parser.add_argument("--websocket-port", type=int, default=8675) + # + parser.add_argument("--debug", action="store_true", help="Log DEBUG messages") + args = parser.parse_args() + + logging.basicConfig(level=logging.DEBUG if args.debug else logging.INFO) + _LOGGER.debug(args) + + _LOGGER.info("Ready") + + # Start server + server = AsyncServer.from_uri(args.uri) + queue: "asyncio.Queue[Optional[Event]]" = asyncio.Queue() + + try: + async with websockets.serve( + partial(websocket_connected, queue), + args.websocket_host, + args.websocket_port, + ): + await server.run(partial(WebsocketEventHandler, args, queue)) + finally: + queue.put_nowait(None) + + +# ----------------------------------------------------------------------------- + + +async def websocket_connected(queue: "asyncio.Queue[Optional[Event]]", websocket): + try: + while True: + event = await queue.get() + if event is None: + # Stop signal + break + + await websocket.send( + json.dumps( + {"type": event.type, "data": event.data or {}}, ensure_ascii=False + ) + ) + except websockets.ConnectionClosed: + pass + except Exception: + _LOGGER.exception("Error in websocket handler") + + +# ----------------------------------------------------------------------------- + + +class WebsocketEventHandler(AsyncEventHandler): + """Event handler for clients.""" + + def __init__( + self, + cli_args: argparse.Namespace, + queue: "asyncio.Queue[Optional[Event]]", + *args, + **kwargs, + ) -> None: + super().__init__(*args, **kwargs) + + self.cli_args = cli_args + self.queue = queue + + async def handle_event(self, event: Event) -> bool: + _LOGGER.debug(event) + self.queue.put_nowait(event) + + return True + + +# ----------------------------------------------------------------------------- + + +if __name__ == "__main__": + try: + asyncio.run(main()) + except KeyboardInterrupt: + pass diff --git a/examples/websocket-service/timer_finished.wav b/examples/websocket-service/timer_finished.wav new file mode 100644 index 0000000000000000000000000000000000000000..40a97bca6c5eafa4e113ebc076d48ded0d9c95a0 GIT binary patch literal 35874 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 literal 0 HcmV?d00001 diff --git a/examples/websocket-service/timers.html b/examples/websocket-service/timers.html new file mode 100644 index 0000000..0c6777b --- /dev/null +++ b/examples/websocket-service/timers.html @@ -0,0 +1,244 @@ + + + + + + + + + + + Wyoming Timer Example + + + + +
+
+ + + + + diff --git a/wyoming_satellite/satellite.py b/wyoming_satellite/satellite.py index 6d5fa47..c798167 100644 --- a/wyoming_satellite/satellite.py +++ b/wyoming_satellite/satellite.py @@ -243,6 +243,8 @@ async def stopped(self) -> None: async def event_from_server(self, event: Event) -> None: """Called when an event is received from the server.""" + forward_event = True + if Ping.is_type(event.type): # Respond with pong ping = Ping.from_event(event) @@ -252,12 +254,16 @@ async def event_from_server(self, event: Event) -> None: # Enable pinging self._enable_ping() _LOGGER.debug("Ping enabled") + + forward_event = False elif Pong.is_type(event.type): # Response from our ping self._pong_received_event.set() + forward_event = False elif AudioChunk.is_type(event.type): # TTS audio await self.event_to_snd(event) + forward_event = False elif AudioStart.is_type(event.type): # TTS started await self.event_to_snd(event) @@ -303,8 +309,8 @@ async def event_from_server(self, event: Event) -> None: _LOGGER.debug(event) await self.trigger_timer_finished(TimerFinished.from_event(event)) - # Forward everything except audio to event service - if not AudioChunk.is_type(event.type): + # Forward everything except audio/ping/pong to event service + if forward_event: await self.forward_event(event) async def _send_run_pipeline(self, pipeline_name: Optional[str] = None) -> None: From 14edd814bbbcf81ead9190d7764802cd8519e9ed Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Fri, 24 May 2024 11:28:24 -0500 Subject: [PATCH 3/4] Document timers --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 3c4ba64..e82c9dd 100644 --- a/README.md +++ b/README.md @@ -135,9 +135,12 @@ You can play a WAV file when the wake word is detected (locally or remotely), an * `--awake-wav ` - played when the wake word is detected * `--done-wav ` - played when the voice command is finished +* `--timer-finished-wav ` - played when a timer is finished If you want to play audio files other than WAV, use [event commands](#event-commands). Specifically, the `--detection-command` to replace `--awake-wav` and `--transcript-command` to replace `--done-wav`. +The timer finished sound can be repeated with `--timer-finished-wav-repeat ` where `` is the number of times to repeat the WAV, and `` is the number of seconds to wait between repeats. + ## Audio Enhancements Install the dependencies for webrtc: @@ -179,5 +182,9 @@ Satellites can respond to events from the server by running commands: * `--error-command` - an error was sent from the server (text on stdin) * `--connected-command` - satellite connected to server * `--disconnected-command` - satellite disconnected from server +* `--timer-started-command` - new timer has started (json on stdin) +* `--timer-updated-command` - timer has been paused/unpaused or has time added/removed (json on stdin) +* `--timer-cancelled-command` - timer has been cancelled (timer id on stdin) +* `--timer-finished-command` - timer has finished (timer id on stdin) For more advanced scenarios, use an event service (`--event-uri`). See `wyoming_satellite/example_event_client.py` for a basic client that just logs events. From 2824b653c5f7b2a396c13bae3bc5bac0daa56bcd Mon Sep 17 00:00:00 2001 From: Michael Hansen Date: Fri, 24 May 2024 11:29:33 -0500 Subject: [PATCH 4/4] Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 798da0f..0f0e976 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,13 @@ ## 1.3.0 +- Bump to wyoming 1.5.4 (timers) - Add support for voice timers +- Add `--timer-finished-wav` and `--timer-finished-wav-repeat` +- Add `--timer-started-command` +- Add `--timer-updated-command` +- Add `--timer-cancelled-command` +- Add `--timer-finished-command` ## 1.2.0