ÿØÿà JFIF    ÿÛ „ !.%+&8&+/1555$;@;4?.451 4,$,44444444444414444444444444444444444444444444444444ÿÀ  á á" ÿÄ     ÿÄ ?    !1AQaq"2‘¡±ÁðBRbrÑá#‚’¢²3S CñÿÄ   ÿÄ !    !1QAa‘2ÿÚ   ? 5˜Z¯V¦cø)›t/? z¨±>Õ5€¶‹Á¤·¼z¼Ü¬+ñ®v¤¨_ˆR­BFn©—˜ý®ç̝P8gýt·ÉSTŦˆìät?þé¼íìN/Þa)ì–í6ô… Ï¿øÃj´¿KÇü]ÿ ªô¹-eKànëÕHTx}ýSÜ›ÿ ”7Ø×&µ<¦  ¥ÑO¶[Ù¯ä¨ÞÃÿ PZ-¬;#õ|•oaÿ ©CìÞz3˜öː/¤­ñTûIØ}š^ mÓ%ªxˆ¥ÉŸu=Z+ISe¿45™¼u;ú&WØ÷€æßQ™®{|íx*TC“#ZŠìZ§²‹ 6pv…³¿¡äª*áZÐ%ÒOáˆo"x«OHk w±æ+¬V(kMúŸ5Vö«$ ÁrÏbàb57/luR ¸ÑÛj Òµì`Мq­û žICÀÊ•©4€Âcà¨Ï€O´<èÐ:›ù(Ë^L8þ‘ÍÌ#¸Ð_Ì©ÙK(Öz 4¬û+¸;ü’V’84‘¬ÃŽ:[â‡ÔÌáõp¢~§ªlæ£ö{®G>J¼"°‡7¯ÆÉèßû ‹É‹§ÁòÃýâßî ^ƾÙõ‹×óH#«LP½ïX=xÑÍ$|W?•~• îëÔ©ª‹ {ÝT…Kÿ ”hûâá)J*ö˜–ÔU;iÇ€/ ÆþjóZ\ýwØ=Ìm ºèËL9 ýèÆð/¨’¥öo=nË.%Îì ŽÕ¯È|{Oj²ƒE6e/ßdÄõ²Ìâ1O®ò×TsəԸhOMýíMˆ¿¼H˜l²,7Â¥#MF/Úf°Ö½± ¸–dr‹NýÊ íjqx{œÉ ä-È ¦ øÄër¨q°ð †nцýÑÄÆ’mä…n<0È™;ÁÝá¯ÁZƒ7FÀmì­ É&9ˆîéi¶ùN§Y• ÃZãAâ?•‡©‰ , ó¾IŸŠc1 4â&y­&pŠ­6;M À 0¹qç»p.á …ŸÅáK@%6·y6ƒ‰3?”úºŽ‰éX5ªPT §µ!=Mž«Ú½‹ÅgÂSâÉaþÓoö–¯ÁÔìR>5éÿ üs¶ÆUcÌ kÇR ]ÿ ù¬¼«VŽ;Â|‡~¢¦”ÏŰæ {L™Õ°Óv¹ò¸írޡעCÃ!íVÕ {¶»sŒNPg/ "uÕbkm²“$ďå¿é¹§°½æz¯6 †s¿!s–wÚÝ“™Œ °.ûj>·+™Òa…©Œ&rÝÎtÛë긪Ît’LAVp%c Úý[ÄzJ¾ÇàXXç@˜ó<êL]·T˜¾¥1Ó©V‡g´æ½¦Ý@¹óø!_@´ÞâSÁ —S3™•& ]@JHÚý©ZŽ €×æÔr»Áf!‡yÞ4Mv*èÓã_{‘åóUuљØ«Oïé*®EvÑ Œ÷‡U \"㪒ÍK+À 4“M¡ï:0¥5í!'<@î´”>Ç»&Z–ïCCV˜Ì5Šo&îhè.žû |ÓK©h$s6KìŒëã)¹hI¦GïOåóI;ììü#É$Š0…Ææ¥TØ.5­¾gn´ “ÂÖ\:hœ89G)J@„}œ:’Ò{/Š"¦_Æ×7Æ3VÇŠÊa]ÚŒÙ€Ä–=®uÁßâACZƒ§§£ Qnâ:«,×{tyø¬iÛcœÜÄ€H½ÄÍCk´÷šß .W'b¤Íåh]÷€=,Žv×cÚEÚHXJX¶îo¨FÒtèöŸ>ªª6[J®Fµ£sGÁeqõfe\íjÒÐïÄÐGˆe1Ø‹.Ø”‘Ëuø Y­ˆÜ ŽG|zùªüMpDnQWÄ”%JŠ™)â*p@Örš«ÕT2Ð%ˆG#ª„ ·¤!°ŸOTÂT¸aÚ%4&h™LµšØüÐ.F¿²ÐÞ_Ç‚¾ÅÃaÜ÷09Æ q€öy˜v‡85õN÷]¬äѼóS{°_MެúÔ#°Ç¸0åÞè2ëôPcvÆw9®ií1Ä8F™˜à‰´+‰Ik1òÝ7“Ñ×ÒsÝ\x‚h`ÞÑ`ó"|µEcý£n˜h`}GÞ !±ù²Ápü²ß6 0ïi󜵩SÈÇ7˜-ÕURO˜¦´f$ªž-Í6(œ}<„ éc øs]ŽŽ„*—¾ ìdŽ„)méª\¿êÎIg¾ØÞ~I#C/¼¼´EÁÈŽi8“©õådô·>euä ƒ'Ê×लR1ÉJE1ÐAát`t;ÇР%Ý<‡¥„ÍÆ`×Oyó)õiI€ñQaŸ4Ûù\áàaÃÔ¹HÃu¹*k€¦<„e S‡&õÏ B!ŽhüÞ`yj}mªf×\¿ Ç~æ­9‡û\՞Ǖg²1Žû5V7 !àöšm° c`ܬøÇìµÒ'P"?…´Ö,"§^•õލsÔ)6˜sæéÍR¼ ò|Sl”‹7 nPW Gòú÷½§O¯‡„l¡kSÞŒr½PÊ@æ¢pŽ-mÿ #Ÿ˜Àº¶Áä¦;ïÔæ$1££`“Õ>„—·ž)ßð³ñ#Ï Ô$¶œ‰ÊE‹À;÷º ¯«P:Ñ”8–IÊtpÞ3ª“>ê“þës4ò2OÏÕ­±zô†Õ§‰.÷ä¸;¿˜“'œ›žª}«Œ{ª±Ì 9ÔóÞÕ‡0 $íWV3Üì¬ —@kÝ4@¿r¼±½¬™›?øØæ´'Áé®CË3-g$˜ö‡×auÚi´Žp/êÛ æF›Ú2v‹ã¿¿,nB1̨ƃqÞa5͝@&Æû“él÷ \C²½UÍc ¯k×¢U ÖéQå™—-r wô ÞÏ<Ò=&=ÿ Ôê Òêˈt,i—;LîÜ á¸*ÚÃ1$êL•LÍ <É)ýÐà’ ;F™{ƒ™˜€&'}‚ãÄK`¡ÞT@I;®žZóè‚s’7®°›+§O­Åq©é»²9<Ô J ¼9O’HL»Ùïì¸rk¼Ž_ý‘TŸu[²ßÚŒ·ü÷B%¯E ŸÔX5êO´ Ç•€’I0 ÉJX` ñ¹õ%;µŸD‘«´€àwÒ™U ûئžÖö\×®×´8 ½‡ºÐÆÓ§?Àkmœ=;d5*@-ì0F Rªýš[Ü6âö̃ڸr*KA9· u*µæ£?U¸Âêí†8@¦X4 e-ò„0s{ HâUpU?¼mñRa°®a%Ð'tÉ×’\¾ÊÉ]t›h>·(Ë@R¼¡Ãt h}’O÷au<+nT…Ö…MӐ??Óe95 q>í/;&JSû °¯ÊéÞ øƒ*Ã2½Ài&:nôUl=¾¿5eˆ3”ñc|Ú2V”>„»&eE;«ÚäC p¢Û úy 9š[ŒÌx¼擼A&DåÒ¯ˆ¤ÀÌ;"˜ ÏQä¸åhÊ}Ûq«Û0WžÒ|»€ø®öCm5•\ÇÀ§Pe3£]0ÃàLDÉ‰1øªxjgwT‚÷¿LΨK‹›ùs—xˆÜ±µ kæ¸f‰‰ÜGk/LÛØ6d9ò¶ùA{ƒA3š/¬D¬khÓk‰`˜"㯒r¿±Óã jx‡°e}<Ñø\3y:'À•/h½Í€Ç4~g ?Û(¼]v‘ªlKÎâ~?O‚W%{Ì:“'©úNq¾›úo(X’¥¯ˆ nFê{Ç€ü?º'ë ø‹ì Þ09ŒÌç9Æ —ËC`j@ÓÄ(+a‹un¸#ÂꟋ{K`‘ÑÍÍ'à´»/Û,KW;Þ4²þð ï Nm|~fGÏ(…³Ã)«1ö­Õ ¥‡¨©ƒÃ™ü-s=à=U66Ï«Ýc蓦W¹íž®›nÔ%êÇìŒ<#Ü×84ån®Ð ÒåOC` ñânÑs‡¢ç 1õ%Îhì½Ã½® e:ݼUZo™`  ÅZŸŒÊ«ê1ÏÄo$q¹Þ€©ˆhÐÉä¯ñ[!…Ú˜àJ:x2$Íß&PåT£6ç— ‡Í*4Ýšçjÿ ‰É nófÐ ó(L5C•åÆ\rMÒ@ò }y-W}™üýVù—ú¢=Ù”c®‘< M ž ´Phr ¦©TD ‘ù.$´÷O‡‘V2Æò.=IUŒ=ž‡â¬i™aþÓåÙ?òUø'ØÖ•.~* šTŒ!•-×áºTâ®ä#õü'´ eýlYÅÓeÕKÂrT"CÚ@u!Óxƒ{š3€}1¿(r}%«nËamjÑ%ÑNEò v ˜à  σöK³,*º.àzù¨™Ó ÚçâU¦*¿ 9{%Ö¹ njûdaXöb) kÛÆ±ûÓ\°M7ˆÂ=û›ç¿Ã‚­V»Cg–8ÙêE- j)k$º`Ã-ùEýeBÆÇ]c¡°ñty&Òd0nõ'¡W+ƒ*|–øµFa\GQªEAÔp5\Ǽ·¼Ç8·õ -â§Ú[ ‡ uZeÖ 3}×d'+¹:ð+K†Û®s!Ï$úe€<Û”x)1»a­¡LC]¸µík…ÚàA»AYº{†ªS[¦5HÒ7ù --,ísòDØ€èk ÞÀîÜ ò@â( ËNˆë›4ô½•/¦o‡€Û7 ê•ÆêòðÜy'Án½µ á˜ݦ ndeo…[ì¶Ê,¥R³Ä=À±—–ß;£™´ñSâ*g§”ïaið‘Jå~™ÓÞ ß³Õ¢»8x埒²52>AÊb&-÷\7´éÄù€T˜,w;3{ï˜k…à¹ÄqÀ«œ{€\ ˆ¾[´¨јr &Úé„Ívˆ±8†¿]|¬ņ4I×pÞS1ÈÖz‰#Ìv‡G!YNògñ:màTz¢Ý1ô©^O=~ë|5Bã™ç•¼µõ•bÆ@úÕS¬ÈŒ#¬zünrŸ û” Z²•èðV"ÁHÚý©wÝ €7¼Ìu1hÑa3Éä û f$o¿É ™Ú›ÝçnpÒ3äÌ3†Í§,Äï]$‰/pê †«À¼¸e9­Æê_C]žƒ·ý·frÁN«, E=›Çq -‰öŒ:aÏ¿±í&£Í:-} 84‘ÿ eƒQÑeëSsuiA ³g㟥ú£?ÿ ʼn*”“÷aühe:ÊWa@ÒÞk±eØ] F Ô—r.åä˜ @ö¥ªZoÐýYL·¥S²G/‡ñ <~*ZÆ´è>JlòàÛÆ½ÿ 窘ìGN¢:I®KšJp/`íIÁÀõ#Ä-€ö­šµŒoF4|ÆQØÆ@Ì|£Ô…¢À{9˜è½Üó›€ôYÒÎYsið;ís¤€à²ˆ‚4qÉVŒI$ ‰"° æµ8cXGjœˏ¡Aâý•ËÜ¢ûï e·çLx']á"oÅÎê3¯Ç—¹”ó0nå‚âg{Œñ> S´˜îè°g238‚ãköÝfÚd´6Ò€;ò÷±¢™¼›º ¢Æ'¥Ðx'e¬ç ]bÈÆV¢ó‹kýBO ðÊâ$Ÿ!×T 3Mýמ žìٍàÌü‘8÷€àæØ8æ©6‰©L´«…oãpð„~Çk‰!ñ;‹”ÛžÍ àž±z Ÿôû øŸÝužÏ;ÿ #|u6™Þ¬ÚˆÐõA4¶â|ôl|Ê2ŽÇ¤ÝÅÇY.<#Aí.k§hóF‚”Y; M½Ö4hŸ4&›­¿tès´%FìL¥£Ãk‰ÇT¤haÁ¤ÚxfÉ`ÑìË›>i 3t‚:,–+^÷´–{Û–Nxi"x‘Ûg î¨>¥Õ܁ùZH,2Û“:8xÊ¢Çí9.É-Ìâã-=çjwµS˜dütžçwýGòú®®ûº_ˆýx$–¡ãøO EÚÛÏ÷R„×w+3£Á£öUMyR²¹âŒ°š›¸Ñãò9§Ó_Dl+Ùßc›úšGÅÌc†Ž!Ko=¶.‘Îÿ c²(2®V mª.ÿ ¹B›¹å ù„öŸSV>™ü¯$y:G¢Z×àøúdî¹û­·ýÇ´:•c LÍõi_‹ö+ÎæGÊè>OŠ•äž´§Þ{X}¨1ÚTc›»Qþ•êô°t¿OP?eæ~É{5]•ÙR£r5†nZ\ã@ &îJõ ¾àC°þV>fé¥/ü5ñÊIº_é5 ;e­h<@ Ä&æÃëE%;X,ÒãÆÞ`Oò¦kŸm#˜!ÀyÄ¢| óLšò¥Ä` ¶R=|ÈCâh5ò3DˆïF†ðÒ#ÅìÛœ?¸yhBãœí ZxßÎÄhºRK„`Þödvײ™ÀÈÑÒgŒuY w³%†ƒÓzõ ÖÏp‚dH®¦A´ù§»ÓÇMæ~)ˆð‡û:ù&Ä •vGD´À n ݇¼Ö8Fö óáà£~Ë¥x`oK|Ä?fxiØü%pìR>éò+Û±éÎ>núlFŤ'tq8LZÏvÃ?„¡ß±È⽆¯³íü@x|PöUäèØã¡ð‚ŒAìÏ"vÍwóŸÍ{ ý0.z È•Ö{,N¡£¡ŸKÕÙž>Ýœþ ÍÀ°<×EA!Å‚D™IúOÍ¡>ôG}Â` ÍßkÜL™Ž Þð™ {IøF²¹òQ3&!ÃÂÞz.d&Ï-sH¸,Ôõ˜ŽP€ 77ˆÝ¼ÊëÜw =cÕ Ú,ØÐ5ÎYÐ)ì´öœgŒ[¤ßv㙑8心>h]§µháYš£²ºÑ.{Ï7Sð•?´~×SÃKýJÛ˜ ™Íäiúu<µX¶1õ^kâçIÑ£sZ4h>j*ÔšD:4­¿_ ÷¸ Õxæÿ ¸?Mù _•­ÊÐ ä ÷ý ÑwL œ­ïnTkÛUÍN©ë:¦fV ¶ÜÔÜMªÅâA½–¿R×TXš-%iTÊT•‡Ù‚JôϐZxWÑè‰f‰òG º ×Õû2aZ7OU3[“×AT–ÞŒ…-‘¤”Ì ì&(ˆ¿­•ƒkï’:ðY¦W‘ Å)“†‘˜³Åtcø˜ñTÂwÚÇ4|üLÇªí–v- qˆèU qPE.†â‘˜µ Æ,ÐÅs]8¾„oúÑ i>ÜxxÈó)ƒ ´æÁâØ$À‰vžŸf$Ž |ãw;ÀÁIJ»b` {¦Ó¤Ú$©YÀ‘n@Óïž«9J¼êG m¤ ܯ¹ÌW4€ÐÒÅÛ‡#褕Ÿn-?í|с¥÷Ú¹¬'´ÞÜ9ÓK `hê£SÄSà?7—Wí_´…óB›»:=Ãïq`<8ñÓŒÑlú2d¬ê³£hÖ[l|$vÝro~'R®‰§°ñmY ͧäP |PUª¹·:3Œ[Û{Xÿ ºâ@‚W–Äé u‚ ¯´*=íή.pûÒdt @G‰¬ s¸ ëÉücr ÞæÑ¨Ê@>¤¢Ö±. Þ'¯°ÌME[YéïĵÂCå½ Ué©Áû'Ê9%eÔðNU”ë‘ÌsD3/®+UI˜9h.WC”빓$#:pz:YÓ ¿xž* ³$Í +$kñAŠ‹†¢ Uê>¸)_š¬÷©ßAÂÔb9ÇU ¯¾á•9¯ÏÏ÷O÷¼¼Fähal1‰3Ì[Ïr•´UCksNÐ] R‘¸¥H+§Šé†c©vÖÞ0iÓ76s†î!§=ß ¼~Ô'°Ãmäoäš³ªøi1úÉ)³yV8 CLÄØÁ‘WYïi€H6ÖÑiámø^ÈY´°Ñ7¥Û*—Ñ©L«Qƒï—Ùrÿ ›£Ð*š¸ˆL©ˆ$ˆ ÷¾D§9È®«qbqC)–ˆïv´çñsÑVT­Ø, <àïºÀO«Jý·õ àfPìð .wFšir´þ’2_Y *Æ€x\« ì€9š@ Ž|F⇥ˆkZ@hÖÄ0t¿-<“‹qµ¾*ZL¤Ú)&BJpÓF5=$„at*Zš$’ÑtdûÝRI1 2މ$€$I$#‰SÞ’Hë¬ï;Á$¡t$’`<(ñÇt)$‡Ð.Êf¢X’Kt=Éé$‚ˆªè¢oÝëòI%Rgcª÷ŠyI%¡‰ÿ !ñ)´õ $¤ Ô’IIGÿÙ#!/usr/bin/env python # # A simple terminal application with wxPython. # # (C) 2001-2020 Chris Liechti # # SPDX-License-Identifier: BSD-3-Clause import codecs from serial.tools.miniterm import unichr import serial import threading import wx import wx.lib.newevent import wxSerialConfigDialog try: unichr except NameError: unichr = chr # ---------------------------------------------------------------------- # Create an own event type, so that GUI updates can be delegated # this is required as on some platforms only the main thread can # access the GUI without crashing. wxMutexGuiEnter/wxMutexGuiLeave # could be used too, but an event is more elegant. SerialRxEvent, EVT_SERIALRX = wx.lib.newevent.NewEvent() SERIALRX = wx.NewEventType() # ---------------------------------------------------------------------- ID_CLEAR = wx.NewId() ID_SAVEAS = wx.NewId() ID_SETTINGS = wx.NewId() ID_TERM = wx.NewId() ID_EXIT = wx.NewId() ID_RTS = wx.NewId() ID_DTR = wx.NewId() NEWLINE_CR = 0 NEWLINE_LF = 1 NEWLINE_CRLF = 2 class TerminalSetup: """ Placeholder for various terminal settings. Used to pass the options to the TerminalSettingsDialog. """ def __init__(self): self.echo = False self.unprintable = False self.newline = NEWLINE_CRLF class TerminalSettingsDialog(wx.Dialog): """Simple dialog with common terminal settings like echo, newline mode.""" def __init__(self, *args, **kwds): self.settings = kwds['settings'] del kwds['settings'] # begin wxGlade: TerminalSettingsDialog.__init__ kwds["style"] = wx.DEFAULT_DIALOG_STYLE wx.Dialog.__init__(self, *args, **kwds) self.checkbox_echo = wx.CheckBox(self, -1, "Local Echo") self.checkbox_unprintable = wx.CheckBox(self, -1, "Show unprintable characters") self.radio_box_newline = wx.RadioBox(self, -1, "Newline Handling", choices=["CR only", "LF only", "CR+LF"], majorDimension=0, style=wx.RA_SPECIFY_ROWS) self.sizer_4_staticbox = wx.StaticBox(self, -1, "Input/Output") self.button_ok = wx.Button(self, wx.ID_OK, "") self.button_cancel = wx.Button(self, wx.ID_CANCEL, "") self.__set_properties() self.__do_layout() # end wxGlade self.__attach_events() self.checkbox_echo.SetValue(self.settings.echo) self.checkbox_unprintable.SetValue(self.settings.unprintable) self.radio_box_newline.SetSelection(self.settings.newline) def __set_properties(self): # begin wxGlade: TerminalSettingsDialog.__set_properties self.SetTitle("Terminal Settings") self.radio_box_newline.SetSelection(0) self.button_ok.SetDefault() # end wxGlade def __do_layout(self): # begin wxGlade: TerminalSettingsDialog.__do_layout sizer_2 = wx.BoxSizer(wx.VERTICAL) sizer_3 = wx.BoxSizer(wx.HORIZONTAL) self.sizer_4_staticbox.Lower() sizer_4 = wx.StaticBoxSizer(self.sizer_4_staticbox, wx.VERTICAL) sizer_4.Add(self.checkbox_echo, 0, wx.ALL, 4) sizer_4.Add(self.checkbox_unprintable, 0, wx.ALL, 4) sizer_4.Add(self.radio_box_newline, 0, 0, 0) sizer_2.Add(sizer_4, 0, wx.EXPAND, 0) sizer_3.Add(self.button_ok, 0, 0, 0) sizer_3.Add(self.button_cancel, 0, 0, 0) sizer_2.Add(sizer_3, 0, wx.ALL | wx.ALIGN_RIGHT, 4) self.SetSizer(sizer_2) sizer_2.Fit(self) self.Layout() # end wxGlade def __attach_events(self): self.Bind(wx.EVT_BUTTON, self.OnOK, id=self.button_ok.GetId()) self.Bind(wx.EVT_BUTTON, self.OnCancel, id=self.button_cancel.GetId()) def OnOK(self, events): """Update data wil new values and close dialog.""" self.settings.echo = self.checkbox_echo.GetValue() self.settings.unprintable = self.checkbox_unprintable.GetValue() self.settings.newline = self.radio_box_newline.GetSelection() self.EndModal(wx.ID_OK) def OnCancel(self, events): """Do not update data but close dialog.""" self.EndModal(wx.ID_CANCEL) # end of class TerminalSettingsDialog class TerminalFrame(wx.Frame): """Simple terminal program for wxPython""" def __init__(self, *args, **kwds): self.serial = serial.Serial() self.serial.timeout = 0.5 # make sure that the alive event can be checked from time to time self.settings = TerminalSetup() # placeholder for the settings self.thread = None self.alive = threading.Event() # begin wxGlade: TerminalFrame.__init__ kwds["style"] = wx.DEFAULT_FRAME_STYLE wx.Frame.__init__(self, *args, **kwds) # Menu Bar self.frame_terminal_menubar = wx.MenuBar() wxglade_tmp_menu = wx.Menu() wxglade_tmp_menu.Append(ID_CLEAR, "&Clear", "", wx.ITEM_NORMAL) wxglade_tmp_menu.Append(ID_SAVEAS, "&Save Text As...", "", wx.ITEM_NORMAL) wxglade_tmp_menu.AppendSeparator() wxglade_tmp_menu.Append(ID_TERM, "&Terminal Settings...", "", wx.ITEM_NORMAL) wxglade_tmp_menu.AppendSeparator() wxglade_tmp_menu.Append(ID_EXIT, "&Exit", "", wx.ITEM_NORMAL) self.frame_terminal_menubar.Append(wxglade_tmp_menu, "&File") wxglade_tmp_menu = wx.Menu() wxglade_tmp_menu.Append(ID_RTS, "RTS", "", wx.ITEM_CHECK) wxglade_tmp_menu.Append(ID_DTR, "&DTR", "", wx.ITEM_CHECK) wxglade_tmp_menu.Append(ID_SETTINGS, "&Port Settings...", "", wx.ITEM_NORMAL) self.frame_terminal_menubar.Append(wxglade_tmp_menu, "Serial Port") self.SetMenuBar(self.frame_terminal_menubar) # Menu Bar end self.text_ctrl_output = wx.TextCtrl(self, -1, "", style=wx.TE_MULTILINE | wx.TE_READONLY) self.__set_properties() self.__do_layout() self.Bind(wx.EVT_MENU, self.OnClear, id=ID_CLEAR) self.Bind(wx.EVT_MENU, self.OnSaveAs, id=ID_SAVEAS) self.Bind(wx.EVT_MENU, self.OnTermSettings, id=ID_TERM) self.Bind(wx.EVT_MENU, self.OnExit, id=ID_EXIT) self.Bind(wx.EVT_MENU, self.OnRTS, id=ID_RTS) self.Bind(wx.EVT_MENU, self.OnDTR, id=ID_DTR) self.Bind(wx.EVT_MENU, self.OnPortSettings, id=ID_SETTINGS) # end wxGlade self.__attach_events() # register events self.OnPortSettings(None) # call setup dialog on startup, opens port if not self.alive.isSet(): self.Close() def StartThread(self): """Start the receiver thread""" self.thread = threading.Thread(target=self.ComPortThread) self.thread.setDaemon(1) self.alive.set() self.thread.start() self.serial.rts = True self.serial.dtr = True self.frame_terminal_menubar.Check(ID_RTS, self.serial.rts) self.frame_terminal_menubar.Check(ID_DTR, self.serial.dtr) def StopThread(self): """Stop the receiver thread, wait until it's finished.""" if self.thread is not None: self.alive.clear() # clear alive event for thread self.thread.join() # wait until thread has finished self.thread = None def __set_properties(self): # begin wxGlade: TerminalFrame.__set_properties self.SetTitle("Serial Terminal") self.SetSize((546, 383)) self.text_ctrl_output.SetFont(wx.Font(9, wx.MODERN, wx.NORMAL, wx.NORMAL, 0, "")) # end wxGlade def __do_layout(self): # begin wxGlade: TerminalFrame.__do_layout sizer_1 = wx.BoxSizer(wx.VERTICAL) sizer_1.Add(self.text_ctrl_output, 1, wx.EXPAND, 0) self.SetSizer(sizer_1) self.Layout() # end wxGlade def __attach_events(self): # register events at the controls self.Bind(wx.EVT_MENU, self.OnClear, id=ID_CLEAR) self.Bind(wx.EVT_MENU, self.OnSaveAs, id=ID_SAVEAS) self.Bind(wx.EVT_MENU, self.OnExit, id=ID_EXIT) self.Bind(wx.EVT_MENU, self.OnPortSettings, id=ID_SETTINGS) self.Bind(wx.EVT_MENU, self.OnTermSettings, id=ID_TERM) self.text_ctrl_output.Bind(wx.EVT_CHAR, self.OnKey) self.Bind(wx.EVT_CHAR_HOOK, self.OnKey) self.Bind(EVT_SERIALRX, self.OnSerialRead) self.Bind(wx.EVT_CLOSE, self.OnClose) def OnExit(self, event): # wxGlade: TerminalFrame. """Menu point Exit""" self.Close() def OnClose(self, event): """Called on application shutdown.""" self.StopThread() # stop reader thread self.serial.close() # cleanup self.Destroy() # close windows, exit app def OnSaveAs(self, event): # wxGlade: TerminalFrame. """Save contents of output window.""" with wx.FileDialog( None, "Save Text As...", ".", "", "Text File|*.txt|All Files|*", wx.SAVE) as dlg: if dlg.ShowModal() == wx.ID_OK: filename = dlg.GetPath() with codecs.open(filename, 'w', encoding='utf-8') as f: text = self.text_ctrl_output.GetValue().encode("utf-8") f.write(text) def OnClear(self, event): # wxGlade: TerminalFrame. """Clear contents of output window.""" self.text_ctrl_output.Clear() def OnPortSettings(self, event): # wxGlade: TerminalFrame. """ Show the port settings dialog. The reader thread is stopped for the settings change. """ if event is not None: # will be none when called on startup self.StopThread() self.serial.close() ok = False while not ok: with wxSerialConfigDialog.SerialConfigDialog( self, -1, "", show=wxSerialConfigDialog.SHOW_BAUDRATE | wxSerialConfigDialog.SHOW_FORMAT | wxSerialConfigDialog.SHOW_FLOW, serial=self.serial) as dialog_serial_cfg: dialog_serial_cfg.CenterOnParent() result = dialog_serial_cfg.ShowModal() # open port if not called on startup, open it on startup and OK too if result == wx.ID_OK or event is not None: try: self.serial.open() except serial.SerialException as e: with wx.MessageDialog(self, str(e), "Serial Port Error", wx.OK | wx.ICON_ERROR)as dlg: dlg.ShowModal() else: self.StartThread() self.SetTitle("Serial Terminal on {} [{},{},{},{}{}{}]".format( self.serial.portstr, self.serial.baudrate, self.serial.bytesize, self.serial.parity, self.serial.stopbits, ' RTS/CTS' if self.serial.rtscts else '', ' Xon/Xoff' if self.serial.xonxoff else '', )) ok = True else: # on startup, dialog aborted self.alive.clear() ok = True def OnTermSettings(self, event): # wxGlade: TerminalFrame. """\ Menu point Terminal Settings. Show the settings dialog with the current terminal settings. """ with TerminalSettingsDialog(self, -1, "", settings=self.settings) as dialog: dialog.CenterOnParent() dialog.ShowModal() def OnKey(self, event): """\ Key event handler. If the key is in the ASCII range, write it to the serial port. Newline handling and local echo is also done here. """ code = event.GetUnicodeKey() # if code < 256: # XXX bug in some versions of wx returning only capital letters # code = event.GetKeyCode() if code == 13: # is it a newline? (check for CR which is the RETURN key) if self.settings.echo: # do echo if needed self.text_ctrl_output.AppendText('\n') if self.settings.newline == NEWLINE_CR: self.serial.write(b'\r') # send CR elif self.settings.newline == NEWLINE_LF: self.serial.write(b'\n') # send LF elif self.settings.newline == NEWLINE_CRLF: self.serial.write(b'\r\n') # send CR+LF else: char = unichr(code) if self.settings.echo: # do echo if needed self.WriteText(char) self.serial.write(char.encode('UTF-8', 'replace')) # send the character event.StopPropagation() def WriteText(self, text): if self.settings.unprintable: text = ''.join([c if (c >= ' ' and c != '\x7f') else unichr(0x2400 + ord(c)) for c in text]) self.text_ctrl_output.AppendText(text) def OnSerialRead(self, event): """Handle input from the serial port.""" self.WriteText(event.data.decode('UTF-8', 'replace')) def ComPortThread(self): """\ Thread that handles the incoming traffic. Does the basic input transformation (newlines) and generates an SerialRxEvent """ while self.alive.isSet(): b = self.serial.read(self.serial.in_waiting or 1) if b: # newline transformation if self.settings.newline == NEWLINE_CR: b = b.replace(b'\r', b'\n') elif self.settings.newline == NEWLINE_LF: pass elif self.settings.newline == NEWLINE_CRLF: b = b.replace(b'\r\n', b'\n') wx.PostEvent(self, SerialRxEvent(data=b)) def OnRTS(self, event): # wxGlade: TerminalFrame. self.serial.rts = event.IsChecked() def OnDTR(self, event): # wxGlade: TerminalFrame. self.serial.dtr = event.IsChecked() # end of class TerminalFrame class MyApp(wx.App): def OnInit(self): frame_terminal = TerminalFrame(None, -1, "") self.SetTopWindow(frame_terminal) frame_terminal.Show(True) return 1 # end of class MyApp if __name__ == "__main__": app = MyApp(0) app.MainLoop()