pax_global_header00006660000000000000000000000064140352063350014513gustar00rootroot0000000000000052 comment=c546442d1ba38e488f47ef8a190eb5e890260aa2 pynmea2-1.18.0/000077500000000000000000000000001403520633500131555ustar00rootroot00000000000000pynmea2-1.18.0/.gitignore000066400000000000000000000004571403520633500151530ustar00rootroot00000000000000*.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox nosetests.xml # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject pynmea2-1.18.0/.travis.yml000066400000000000000000000013261403520633500152700ustar00rootroot00000000000000language: python python: - "2.7" - "3.4" - "3.5" - "3.6" - "3.7" - "3.8" - "3.9" - "pypy" - "pypy3" # command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors install: - pip install pip --upgrade - pip install pytest --upgrade # - pip install pylint --upgrade # command to run tests, e.g. python setup.py test script: - python setup.py sdist --format=zip - pip install dist/pynmea2*.zip - py.test # - pylint -E pynmea2 ## pylint is not backwards compatible with itself after_success: - pip install coveralls coverage - PYTHONPATH=. coverage run --source=pynmea2 -m pytest - coverage report - coveralls pynmea2-1.18.0/LICENSE000066400000000000000000000020041403520633500141560ustar00rootroot00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. pynmea2-1.18.0/MANIFEST.in000066400000000000000000000002101403520633500147040ustar00rootroot00000000000000include README.md include LICENSE recursive-include test * recursive-include examples * global-exclude __pycache__ global-exclude *.pyc pynmea2-1.18.0/Makefile000066400000000000000000000002701403520633500146140ustar00rootroot00000000000000test: python2 -m pytest . python3 -m pytest . publish: test rm dist/ -r python3 setup.py sdist python3 setup.py bdist_wheel python3 -m twine upload dist/* .PHONY: test publish pynmea2-1.18.0/NMEA0183.pdf000066400000000000000000001653211403520633500147140ustar00rootroot00000000000000%PDF-1.2 % 10 0 obj << /Length 11 0 R /Filter /FlateDecode >> stream HWr8A8d3[xƞ$aC*ƍeybW%qU|ӧ]**9ݿ.0_f.3Fo/pWؠ5Z%zw`~Nyb+w)zp{ЯFѭ&">`/Z Na&DtH_YbZY]wXA5c[K鱡t'4IeK4G>tZث~%`EEI۪K˾d_P9UV<4րr,/}> GevX%"}mc?i_C)h/'47zj܍`mVdQ*Ws>y0F2dW E#Yfj[ (cT5K;KV|!`nϴWA.ɗV)η|V3Զ=>tyq;50 )REwݻ= gTk G ut!eZ-5y$Zցh7y0.Z@r361XF|bF[z}~r?>9=soHs;iƣ{݄$v?\V\)*b)QsKY<`C L-j3HJ8N-B>%^J}>j_Ó`!6ƍ@yhPooW$vIFx3"EnΉKm8KU@2Z[JTԭL~{"bsH?5e r IrjFC Gm-ؙCDh@ Wt}/BcVa'cOgw08SZ=ׄE743O|Οel,]loRۥc^ACʽO 9E-b)JWy~gm:~r`= X8qA/~wA'LD+/z 9]"Vغ7 DhH|?7d c(ĺ"1_iS9_bqʴER&›ҊbIj5,ۨ,P`In{^1IPX3z<ڞGifj>w}][Y(EyN7Qaφ>UT(hwUd0T'  {4iܰr A[P^Yn8;lZ""ÄہtNVi7qhǵ7zID5f*i3ʹ™# yxw0"[ endstream endobj 11 0 obj 2124 endobj 4 0 obj << /Type /Page /Parent 5 0 R /Resources << /Font << /F0 6 0 R /F1 8 0 R >> /ProcSet 2 0 R >> /Contents 10 0 R >> endobj 15 0 obj << /Length 16 0 R /Filter /FlateDecode >> stream HWnpI AO:@'bC:0{ύCYvs9/kۻ,_i {F 5%×%]4%<Ə䂯ȇFˢ![%MQO8gU%׌ _?/qW,c+$NBY&H%եHK<mdEZmeOq,y|B$ah~Я iۀ sEXa*Bl >zN)2Gȓ NZ˺} ڮ_/Ё,uMA#,6GݾfBm2Ⱦ֮D_ %iG|g_NbSGB"{aDm1Vv1UqpeD0XEsqw6*TAk5\ ϓO/\2暟2[ퟻ>O M]gѬ}/ZyJr񋛢mDYF,OOu@Mhi9Ўo',s,#`~=steĜ)˱V+rev@-4ˮmA,_biwgNl[OS_;yQ_ڵ́Jf4 r-(K̸CDTQ (矹."^`R(fd<#ԦፃD6rUcSĖWVGu-m.֢H8Ja@נ6˛۩@[+A?p'[l-DVAwXj< uq$q*(VK`K $ R$)R9G[VC9LD' jC i~TD3L ?N" Ů .#uUo6|p  #zd7u|IyaAίuIߞL Bx}Gߤ} h;~:KU:h&J5wտ]=؆,+>K-oG B`,5Xvsّnk6[W(̷QŃ"ȸ2]aAҪS 3p]8m"aү?"Iq*lX r3^vʠTbuP1_y %Y[{2>>z *.Oa 4h *Sa%U#’)z߁ț,K&fQ,Kva@+,`)@J5OXl[r,42 *@A-K7R*R7Ԗ8bȩ#%jVQM}#) g0;2u:KiհVlA$\|w}(;طW,nhFO6yɌ5dkmFo-+(a288ԃt.s<{*a)7.4˧춶>]:<zsWWx/⊝W^JX4&3kCY:*,0Zc" W\h.+.'6A⍍0/o"f.is|ǰ]p^b'Kl6J\5q8ѯ}EGN;DYQ>; 񝤝4srI;S۰˩́f'T0.-^D 4Z;6z&GPpZ6ӄ!H;3+aXTlQ jnڍ^wɩvદw4"_\! M݃PLPeF]NS'|H( eaizlx@AO!6r)P= RG"9u?p !Mҡ4e^ õ/(!3Ju )(ɮ]4B(k\|0}I .P(7~5A"l Ns#t?݆-ɨKJvuD<Ɯyս|x'Eځ`yoWfԊU#hڞZ#3K,*V{yp\WNsaIfZj7-4< rpG(x$Fen(Pz%r j~+x 얕K(~ۃ'tĦjXo(qj6]i='̜LP3Uj*Sq(TO1%Zynk)m fG7T܁LfX@Q(ѸӸ0zK~9鄈SQ֋zFǴ:Gn^RQ2|w)$n@nߛ=;Jgsl튠8qvٮ;Y c鍱`RϸsZ[H|Ʒ$ ?AJxDp7Xg+HM&%~V_ endstream endobj 16 0 obj 3528 endobj 12 0 obj << /Type /Page /Parent 5 0 R /Resources << /Font << /F0 6 0 R /F1 8 0 R /F2 13 0 R /F3 17 0 R >> /ProcSet 2 0 R >> /Contents 15 0 R >> endobj 20 0 obj << /Length 21 0 R /Filter /FlateDecode >> stream HWmoP(%roš"Ɏa[}MC{83 Sh#Rdk_*]q/~6D8oB6"w*J;EPO*|M*]gIY* $яQ[|pJNZH2kP /9JWC:$e>62SUen;<e$Vw+! O4_Z&(TSo*ͩ:шY7Xz/aH[yİ_'_ ]1;fMYè*6rcj1o(l⦅l$ fe9i6\vQ>lz!a{t .qAak='g᪫k;2'dBmAјe)[4nM~rG{&:6|e>R {7E4pIZ`ꩅ!CvX' Dr{ %:@'q:j[k6 H׶gb|<`@OR"kilѰY$Қg N/w/X%=s_Ga:2͚KekZ$ endstream endobj 21 0 obj 2270 endobj 19 0 obj << /Type /Page /Parent 5 0 R /Resources << /Font << /F0 6 0 R /F1 8 0 R /F3 17 0 R >> /ProcSet 2 0 R >> /Contents 20 0 R >> endobj 23 0 obj << /Length 24 0 R /Filter /FlateDecode >> stream HW[63> Pw:%,K&M#@Y_l3=GmelttnΧe/(˫QA!oS['_`_{.5ZzctadDEQ"~wr-+x3 tFc*u%,d$:bHwW'ɍ^V3BKw,j-G 3m2$Jo<$*7 wƒq c>j- cR?` cC na00}6.3 AQ5,j^\NR*U Rbu{NN(V2cR`ǫJ` 6v4g(#je(d]AR()e&S\5PVw>o߁^E׻ٳ9 k)\я Y@BB /qe#3"^ڣ01. ɍa}iF{ya΂heY#x}E(U.oT 7fzZ9"d-L Dިl>5Là.C[4+A ~D|ZP#42 -!+H|Vh+.[2G|ŞG`؁]oDZ Vhuz*/>cJ`+xVo*EC'3[*vj'NS^)Җ;⊈[X#Tuy"dkYLthܜf[CRP/˚|5D.9Zn+9A6'bIis "ˍb'e~捫P40O2YG^AY2+;qsQZx𖝕<&-F?%h$ƚ(OJi$6^/{C/[;e *E>; | d`ߵ̋7~lHo=NL p s^42:y9qt{s۵1UϘc8 #.2{j3`ck0G>hЉ#=z_N}вh};>d[qcZ، endstream endobj 24 0 obj 1504 endobj 22 0 obj << /Type /Page /Parent 5 0 R /Resources << /Font << /F0 6 0 R /F1 8 0 R /F3 17 0 R >> /ProcSet 2 0 R >> /Contents 23 0 R >> endobj 26 0 obj << /Length 27 0 R /Filter /FlateDecode >> stream HWr6Nӱ3C"ymZ[q,&~JĘ"}| x()9!\ vZ M ? !mj~ <4< k&/ApeBP ?jAg!|$Ծ P\NxHE,\(.ò]Knti3,3x7|אV 6MMeJJz,X*5|@L1Eڋ{ߺ/?PK:էכl߯V%E7Pp ۬[ocI7v_@ Q)/vQMͦwr/'Q3=xG %Ӹ\ 1] 4_1 }4]u/+cGt)Tn3F!G<29E .(.q\Z/OǸׂꮋ=ؙSpJy4G7D1Sm}܀".l3HJz?@e|ÒE4ƔÆfh09Z{I$w(Hy=,ˣD 01CuWȷR5 +ӸC Bj1tpa҃ME$"yāхйt6ɜ7/ˊ͟T*jT`6Jm- 4E֞eW)_&OIRYv !q7BdԖD/ 񝂡> /ProcSet 2 0 R >> /Contents 26 0 R >> endobj 29 0 obj << /Length 30 0 R /Filter /FlateDecode >> stream Hŗr6ǟﰇ E AjKδɭČüJш$_$Ad2FE.v|/1P!!I<.Q(@2aa( | g܏ O>'u0 0`unİHݯw%NW!*vH`., ĪaZ&E۴;2i_D9.u*ϫ~*Q:dYC2{7ikHǬga$]rE|":"U҈~6I^7yXU0ֽNRB/fq-҆rӄ4Iw0Ze[fnyE *Z8빯+:K 9v[Ҕ5(\·UJ.4q#A\wɭ!PURVR2-6ra|6{Q]q a JQ@bdN gϝ}vkTe*ŭmQEol6~?+xznvztAnff"dAÕY5I넭r'vUSY}XuX[Uc)0Nҗ~ jhFw5Zѣ/#{zo L֛~CG5忍z>hJC+R\T(xe8d䧡P( T5sTmGϏ)s{o\g/T5uw$\^{.O39N;G<:S;c/99Z͵DZZr>XPCΡ#V0p#ka76oW{ Fpp me"3DƨMlg1P,l þ/OuytF8qf‰]},U> /ProcSet 2 0 R >> /Contents 29 0 R >> endobj 33 0 obj << /Length 34 0 R /Filter /FlateDecode >> stream H]S@Aù:w7_^TAEˇ!:ُ$*3E9gxn{@j3Pk*`K#BxQ" _P_[f]^u>@h]Fw CSQ}4ѹlPfQ:[N1d K唝ya-AJK(`'/r "JEO&EǀMB+;SY>W*V@DR{~b1PxO{'/p%\m{ C@[Pdž\{U'm t%1ܭ6"骙} +wF4Yv+w;? G(O@Dqi%q2hhzrZޛO`CWM/|#X{3}̮ua` 6G0f_yE endstream endobj 34 0 obj 804 endobj 31 0 obj << /Type /Page /Parent 32 0 R /Resources << /Font << /F0 6 0 R /F1 8 0 R /F3 17 0 R >> /ProcSet 2 0 R >> /Contents 33 0 R >> endobj 36 0 obj << /Length 37 0 R /Filter /FlateDecode >> stream HVrFC?hg4iTyx+NJ<`asցR^ ρ2x1FŸPqD߈c0{Ä@bo/62 ׳0ϼ=$.LhGeXX>4<6"˧~\.[:0ƒZJ6C7XESZיl~:^*-aTZermP5lR E^a`tUɻ,al)պl3>>HwL陱fIwZ`ia*-`* giQhu\V)wrʟHļ꾪7U::D|~%a'^B\Xo͞[ C,n/@K@@ts& wub'Cc) s.+.RԔw5Zy7y${.'f~>ߖC\ʧ~21J8=2@l/0C@IBUz8 MpH-7{)n! · aIZ^LBI ]Z/s*H"[)dv) -DEw8;$J ]7xz_?kTABI \`[!e'i?&Ɲ bA endstream endobj 37 0 obj 907 endobj 35 0 obj << /Type /Page /Parent 32 0 R /Resources << /Font << /F0 6 0 R /F1 8 0 R /F3 17 0 R >> /ProcSet 2 0 R >> /Contents 36 0 R >> endobj 39 0 obj << /Length 40 0 R /Filter /FlateDecode >> stream HWYs6;>(Ql9$uh RCR>:](R6`boójSCs!bA@gB54Ģ-8 'L&0a\̌`# 53Gl5fȋفeQ5$qgi`R|32`oY@.`9O/`D ByIqC- .PeCOS1#%`ȨƄK½"jsQ**- ~TC.xD y(>uk`bj]Aufv̮] CzUf$fdzyg,Ӹ?aһ nuv~OS i2. ޳ao-;E,/#ۖ~qaGu'a(nMd@y7E)uWc8 |M5mNk@/c0>F"ɨGu-*.֦v1!jлrtsmT[G*K-o(TLꜨ.VTUPpXE?S%{G,~ Fͻ# d;}RwtL{%UcR7X $7PfG'.fS߈:W)|{۪ղ?) -E]~u!o~ U]=쓼U'xɪ^ty PKϡX2qd*.:Gvn[TJ7c^s6o>-T=>C-W3ؑU: dj7jkۯΉN뻁jaFEߟjF,m„oU(F2mO0> /ProcSet 2 0 R >> /Contents 39 0 R >> endobj 42 0 obj << /Length 43 0 R /Filter /FlateDecode >> stream HWr6>AD4@<:U8q: D&$e|7^DJV2MZggw| Pς'z$'qk.DyeDNG] j?Vigry:p(oi5ߐ%Ő BH57N@Wje@QC -4fG5|03YY*/1 MөˮכX3]8_o9Nn._rcUSF̫5~ hY5}ɗ XO5P6xu?%B&۶hFjs6eد+Ѵ #Hq@x t^v&HBGP>ȯ:mJ#5wZ1]Bl "Ї?ݞ[]U <`C [Tˮ7g|\Ey᫗w Ni^RLJD_<[oo *}h~JYu6)-=f _GBq)LHsWfK#O ?l1Pq7"}O8 #LBخO؎xzLHsBٴȫ|o>j:x8;:BI V._ƙ[>tͮѱYvPO[8/a@pj endstream endobj 43 0 obj 1149 endobj 41 0 obj << /Type /Page /Parent 32 0 R /Resources << /Font << /F0 6 0 R /F1 8 0 R /F3 17 0 R >> /ProcSet 2 0 R >> /Contents 42 0 R >> endobj 45 0 obj << /Length 46 0 R /Filter /FlateDecode >> stream HV]o0a R&C6&1mZ=D] jk'ω``l@v{=<$F"j\h[dZH$C//1-RRcPOpu  P:0n!ǁሰDZ8@Mn(̳|k&6X RU;&?b572kc2/psGnnpyl6K)q 9a6z>Wf.y=-^lvɼcw0Vk],^W!>oZq^w֛?eƒm+cL ,/r椇SAwHϻ]욭]J#tXBKO/;d43lgfށEնJ1`IEjQ3ŒzF)Nv1G`~;SFS:uv<eKo k!MhuQEOE3$bJޞJ7YNP;@:ڰ{5͝[+dd6bzZ>K,*l҂)q[%i]I/~SY]4{,+~4&V?7msbi8M]{.DX}S^c>AKa_Ki>L<oFpaur85M^dܺE1" endstream endobj 46 0 obj 705 endobj 44 0 obj << /Type /Page /Parent 32 0 R /Resources << /Font << /F0 6 0 R /F1 8 0 R /F3 17 0 R >> /ProcSet 2 0 R >> /Contents 45 0 R >> endobj 48 0 obj << /Length 49 0 R /Filter /FlateDecode >> stream HW]SFst}ƱlH,,UL㰉993X.mSWn!<@mx>=M-꯸@l/1{; D߬ g@1(P"Ɠ! E1&H)@5@TpBg qUA3U Bb |%:%) RN O`Ĉv#6w4cQGgCkQjޝ@hܻwPbzfiRLlQRRK(sڛe% U\>%zިAA(]K˥,dHdoSQK7UXզܯ-}AY(kF9Ը9_Q#,J jXT(3CQP1MnFQ0Z=1f;x*? j* 9_]`r)ي4C첐wH3x_L9W9p|f'6P J&UgQ '+3y)MUT~PyKliȩq%.%iCy`ȎQc4bƆqpϘ1T,|Mԯ'Xj"za-2# ˢғ h2a*>5 ( Q۝}zK 6ctUN$vv Ih%_'NJov2s2pt@tYaIlM!?_wi^sz8R2yUQ{֝>qeVd!1$mC0dgb>3Dԇ~w=^R1w}8~A6:=.lTOEQɾmc|> /ProcSet 2 0 R >> /Contents 48 0 R >> endobj 52 0 obj << /Length 53 0 R /Filter /FlateDecode >> stream HWvHsi,db3Yh|H`Mͣ^}v;X>(p8PF0 BbgS`$ 8.=`2>uY04~zj2ިڃ-j07o|aؠ$$%W+".\nV_Ei2uʫ(\\3N;hPc^m^g09%\5`SHnm AOY6* {-kÇrSNu82w _pPӨI=~k vi-j]ֲx8(Lyt_f1>m`2`1+ C4h =/iΛru\")LK(CS6e2E.Q, ޹KnƼ&m'fvp4d;ãt?\uɯCRfpLppx\D"B'Y6+%CeEYo[u_) C4#UB,Q Sܗf;w{ } `bJsN#><=8}ד<+`WlOV/0&+ɤPV0Y,,Պ$Y5>'l%4[q;dW=o53SʨGojm9lATp^"碬{-Q'w0Sel2I'u9VeVEⴛ׭idxnFB45G Jek*:]yѭm z} %9/fJ<-ݎnzJY,01UYIۆ^Zlt LIYK#X/9փl7ǃYL )!~ endstream endobj 53 0 obj 1154 endobj 50 0 obj << /Type /Page /Parent 51 0 R /Resources << /Font << /F0 6 0 R /F1 8 0 R /F3 17 0 R >> /ProcSet 2 0 R >> /Contents 52 0 R >> endobj 55 0 obj << /Length 56 0 R /Filter /FlateDecode >> stream HWnF~hJ { qjuts1fw'=3{Sf9ۀ[@ 2\9@7&6Xޥ5uk=oʕwP\eV]z/bi5&kiҔf! agqZpo/blx*wjSq߫t9>)`DWL]Li]  &Μ,i+~Bk?F#:U_/QP- ^${[q[*ժyD?A>ͥ#UYPD\I@BZrk; INY83uJ<9rib)dz?V'$}_ѶXm7,",T;` X2C c(@hxWpĥ_P䮥j0Mjb.{(@ooDzZt*PBI [1AzPLnA]BQL`od6gkugλ;H=Cr 5ŀOk/|s@x;?Un+I=aI)"|ӡw_ I>Z=9V`V _*G|Dq6|EPB2JF\:4v f#wi:. ?["_ģ; ~ϻhۇo:oۢHfӃUMBhm5*<2]ө`(nv҉̯fV! ۆmuGb49h[̰W X".<|z)wS[}3(_شdMHaPؗz}dl endstream endobj 56 0 obj 1196 endobj 54 0 obj << /Type /Page /Parent 51 0 R /Resources << /Font << /F0 6 0 R /F1 8 0 R /F3 17 0 R >> /ProcSet 2 0 R >> /Contents 55 0 R >> endobj 58 0 obj << /Length 59 0 R /Filter /FlateDecode >> stream HWmoHQUvwy}p䔓bHwm`Ή/lu>33ө?:tlZ~ L}2z" Oxe1u ׋דּ GrCW^x>վƶC]Z. .`S p,\v&j 7Nk-d21Zgװx.JQFuٓ0X9R6dJa/WfVBVu mMuNI3jl-+KmR=DE:af|ϓq Bl_J~1dE#&E4N-7#4Ħr8QVoP':t$\ST>×+Lt4Ft.mbX^`R2GTNh(nu֨[&V lSYRl-/|RGc=) b bb=d @)F<ɍTGx-m \aPg^]UUٵ *|xY\)+%Mx1˓ GG5}~hhJq2n-:S*S`gW\dQt>fnyʏݠ UB=AM[V0jItZ'=cW5;Jj_W؋W׋ԋ&񴇓d-ea$^u$F$ ^Vi`IA-_->ڽㇿ0J1!$}9dZ?SupN]Ak*(ܘb}{| %'DWg?(quۙ.m8.Ps p*o> /ProcSet 2 0 R >> /Contents 58 0 R >> endobj 61 0 obj << /Length 62 0 R /Filter /FlateDecode >> stream HVv6Ü,S ;sX:֋ $`?|Ibײ@ν30<|?@m:Ɯ=`DF~@ƾ M %VW 6Y`]@_Fj,BX%tX"ٞ~ޙ`YZ@9g=@txֶUKٮ vj!(r|s.î@}(wfK7M].hj;'%ۂ6H@59tyuτuGb<]F.&|M,.o^0Xiǂ4y \HR֓AU re x{墑F%KߠJat_l&ycMW'9+ Fjq֓cEm&|AЙU,J> /ProcSet 2 0 R >> /Contents 61 0 R >> endobj 64 0 obj << /Length 65 0 R /Filter /FlateDecode >> stream HWKs6;䌥 ࡇN#pCG%%C|H%Wja~X}O p>0eLy*,'"@{@h1e0z[\A{y0t`>e2(2aiBn:6" YPAE9l T.4,EuN $I*B MU'3D\IB$zA KdT]HQq@:^mIkpȪg|VmOŏS}ѝNXP" }G_:]/ m_m85OiDYZ4icrt0뻈ܗ/%-*fMW~D>{9| \E[N xn=yD endstream endobj 65 0 obj 1095 endobj 63 0 obj << /Type /Page /Parent 51 0 R /Resources << /Font << /F0 6 0 R /F1 8 0 R /F3 17 0 R >> /ProcSet 2 0 R >> /Contents 64 0 R >> endobj 67 0 obj << /Length 68 0 R /Filter /FlateDecode >> stream HVv6[t!X4NࢋCj+؋ %&PTQm} Hon{@j3k.2~YZaqo`a5]Qqe󌿅H"% wu*Z4Iyaϋv1&6@P`pcmT~Qi<ƪGzuɻͦRa,x".6ZgI_ H2Xu.Į⩸X$M >Y9۷h?$7h3Q|";̼|(9 1IʭekvZ26hˍʧkY`=\ޗAnBVQ7 U&YqE*8`PaDX:jPNoOїxo`{RRC@NaS*qޔ徺NUnF)U+,P&G|qI~72ߢ>\*?) Е [lOƏyϣUy^R'L̞r |:MϼgU 9Y|;BmZ=s0Uy<I0K!V5^+iVPFRU$Q7:7;܋x Wvz+Jj(';q#hhZxzQk;Dȷ;MFpP}8eHM1/uAH ̋"SFl}u+^9D]sEBܜ-hF[d7P}F 'z,'AuU^}h^_:P%gﻖ)qMʶ㓾mV8hRʡ9@P6eQNqsd(og~WgϿ+(؋L.ln )Xwؗr endstream endobj 68 0 obj 1036 endobj 66 0 obj << /Type /Page /Parent 51 0 R /Resources << /Font << /F0 6 0 R /F1 8 0 R /F3 17 0 R >> /ProcSet 2 0 R >> /Contents 67 0 R >> endobj 71 0 obj << /Length 72 0 R /Filter /FlateDecode >> stream HW[sFȩwײ;J*4I݇  Xy; ԡ6/u/2~ ~@l)`^5!61>FX;X Cck(s_lpO۲u"/K EQ24 '}ٲ><7s lsa(0;ueJ; u6Oۈ0`bwx'x(q R-/Pˎ'bZr*'Tˆޞdb'T_uB׉(EI J$Q mA:޻,J`WзQr&6w򶓮\%PK}uJvS?uZI5XH?T)G =*37iCj҃ԵtCOl Iٵc}|7\!@i I ?Kq"M%m9ЮflRdfN{̩73Mm Kwb?CIb]KZ,R@&KQycY%VIf6Kw"]w;mJZXrD`^u֎lL&޼qKw[S٘u/loMZG_0{HLiln!ӴHb*|RJ:8{g۞ `",0,ĶJcuNJ! " o%jPfeQbj4`.H%c]cǐ5=tء37K2u4zd4, ̏s >&@oLFg]zZlFT1QG*ѥzuon,;eBEfDlZL<u߫ X[3A[8ZMcQzħ*@d da;l+:PH(&]s:(qBXZ,L>i>% ̔3Ca,eCm _Osx`#@ǫfSvYw;W?3T`f*)aq~7V;'(%NRN^bh೮Bd't:θP~NP0͡EWߪ-oG،'  jR:$N[ ;cDz^jGolw5e^l>`%OG@\u0s۽CY@tzI!Ё*; פ2f=b$=⬫_(u;f&gS1/41 .0E^qt)osE#aK:>`<} sX封8`ݴo֝+ܛ endstream endobj 72 0 obj 1350 endobj 69 0 obj << /Type /Page /Parent 70 0 R /Resources << /Font << /F0 6 0 R /F1 8 0 R /F3 17 0 R >> /ProcSet 2 0 R >> /Contents 71 0 R >> endobj 74 0 obj << /Length 75 0 R /Filter /FlateDecode >> stream HU]o0ajR;cq+ڤkR $IZBUMꏟ?x؂dL||Ͻ'@!!ρPˎn!Q!Bb_p5Bgt1KaЋ^`p7z@c=?D~1#&BGC]{PirSdNdT}OW%QJ"lx, MSX%pN&]9<im Q/.@!P?A3f`ٙ͞6{]-]&wի4kG}pauvp"Bs[(.i>ߦEFCגOLkij_L8y,F9O۱zB%Ɲu& bB@_FUwLTIi+Ҵ4I"4eD0sG4iJZJ` mm(Fg2r*ޢҥ Žh= /P5&f܉eAc5ߦ߻AkzQ endstream endobj 75 0 obj 578 endobj 73 0 obj << /Type /Page /Parent 70 0 R /Resources << /Font << /F0 6 0 R /F1 8 0 R /F3 17 0 R >> /ProcSet 2 0 R >> /Contents 74 0 R >> endobj 77 0 obj << /Length 78 0 R /Filter /FlateDecode >> stream HWr6~z3=`G8Qc=T=$!?w$n;S .(ׇ0`9PWF.vG!a?,'u pmL|\-E)oK|k^ԼHy1dꡐؼh^lɿ,.?_S=T7Q(!QhF:nf8Wkžv$ 'ڔI-daPbO:ǰ=h}\e8 d= b*L 2)La"jI6zhO?6SORM @?Z@'x{8|~r>9f/wl:tu2㼐)<4T.I7?;v +t}VyluiRqQy^8$7򎿾Ṽ-O tM9gj 4"5/+!<) dIlA9a CST<kIϤ"{jHeQ'$7rB ^7C3Tz Ee;VL7Y]+&E䀧=mZ^EVJ ii,;Γד(:IkYN|J՘i| fuϐ1+RKv˶:/\g(\rњdK][M`Zf|M%0u?{jJܳɠT `Öӣ1cĶ.Wn2+B~O岡Pc--T~OԺ9ٶѥ3iGݶNܬ%GڲaW,d%t6/vj)ڥrſ*%t)gʢ}iҊaE=W}spW?=sYGUpۑCj5~w%K r"D%;`$Y0ڀ7&r-1zkM^eta>Ic ȨXapoIk x}Q.Q 2,P;gF;Bd eAϡXvB~%'Rb(yVH>zs|y,gsإ>ޣ=}:.Oϗ(lw9wK{C֣ endstream endobj 78 0 obj 1352 endobj 76 0 obj << /Type /Page /Parent 70 0 R /Resources << /Font << /F0 6 0 R /F1 8 0 R /F3 17 0 R >> /ProcSet 2 0 R >> /Contents 77 0 R >> endobj 80 0 obj << /Length 81 0 R /Filter /FlateDecode >> stream HWrF~ޡ9[ aeZR>9[hVJ@ 6$1Lw0<|[.0JRP wS`]$aa'?^]q0~;(Is|g(2Qwg1Z(eR%yW+EV,5V@ Acu\Ed Ʀ|S{M+ mtҀע0;Ɋ<|p׹W] ? |IɫqdLӲlX)^; irhu6?9&KpDNfߟ9<,.kz8~ֈc~_\@VX7SSUI:Nu[V|Uc*xH%j10o /`>%s=Ks#,e >Cfpu$Qϑ TyZGC *C'ڮ;:E1ˏyMg`AO5៯!ly,V9A]A=SSQ<[B#3wK1jc c!D9،xi{Z6FfQ!E"Gpe 6G6b4̷7kJsjJ 'K=AMS]_i{ Z)hpgPb+G S ri2 PJPOǭcC+HLQmmcC'4oL9G\}SOq8vc$uڞ?^gC$u5_9m{KCNRQ"ư89-ߡTU'ta %S3]ylO4,-bѕ:Iӛ%ò*" H}kahUޛYKQT;ֶ;(6G4#9,ܫ-Q< XEwUJfRh#+d/'ws;M ? |tb^G"\Y˩K&Agڎ'#@FJZN\fpoNFgW%v3K˜?.7 fvy~xs6nl ^PeE^QtӚGC%y-8aΞ/h*>+~Fu 68wz0簉 endstream endobj 81 0 obj 1223 endobj 79 0 obj << /Type /Page /Parent 70 0 R /Resources << /Font << /F0 6 0 R /F1 8 0 R /F3 17 0 R >> /ProcSet 2 0 R >> /Contents 80 0 R >> endobj 83 0 obj << /Length 84 0 R /Filter /FlateDecode >> stream HWnF ^x}Cb;Fqm}R+ E*$;@>RˋlEkA]Ι3s,+80\%@|8NaD:䧺=X^:G%w0.ϯ.>s6w~+8SF9.siR 7zד/vcyA ]ՀShu#$4OUV0\I>%u&CmfiGrT]p5&kOa?)53:ooX@PkIunb$՝ha!MVp{ ;wf¨$|6,Qj&a6(&uou YJ2nraBHe&fZzDj"b'y>rsԞچe3S66˘퇋\zʕOdT{~7mxFjG\˄D8bVq-t6^lU‰-0zf4wYk'eY)xM2/Zr kLʮ ֧[mRu,ϣ80!*ECReU-awI0-u:f'-ޔ=}Lz}<>׾0nUoM~q 0s|Mփt _.j}jR$ endstream endobj 84 0 obj 1308 endobj 82 0 obj << /Type /Page /Parent 70 0 R /Resources << /Font << /F0 6 0 R /F1 8 0 R /F3 17 0 R >> /ProcSet 2 0 R >> /Contents 83 0 R >> endobj 86 0 obj << /Length 87 0 R /Filter /FlateDecode >> stream HVn@r}; 7_az\NO<0~䫋&W J@J,T8[\g DeѠ*[4&|2K*|!xs&)Y}CSS/I< 4}3f/m rmj}ebQ4cazC²;'CMV"=腍pƘL]?RfUr:g+VTb'!0M);f~'KjGu=bEM>\B[qN 0n "2ƥҒ;]1?m&ql-2`I*G=f)9) j&Me!x#@ֺChh"=̋5)Vꩵvcz0 3(łZ)a֡v!ƇpȉjډZq-޼ތV\OD3ݸUCѨCsAJ$b]7|/󤦲|JoV]&ŽCÊUU27'LGNV(&1g넣(ϛ+ 4Jj>5.yQ%0Bc}I|> /ProcSet 2 0 R >> /Contents 86 0 R >> endobj 90 0 obj << /Length 91 0 R /Filter /FlateDecode >> stream HWvH&&4oǫ C&7F!Ѻq:JU`f֬<\9{=ykއ>!4F8`1IAu!SP#L̛ElQuQGm5Z8$ٍ탄]'uf~߾qS cw=&{N2!#ɞeQC^Ug](a[~4q3A"+e!'DTl"ϒMye#?h~z+ө4Ju M oqq52cb4Pl_l#.b4~s5EY;*x[K4*ûz/J?ęwjy21BI H;1lԙ!]gB'@ wKqag``k+{ı Vt;gK+Csk7 [Y(>7]aQӕ mDF>.:_}3p<{gqfBIDԻY{$q/ږoCJ,ӷW"j}j7,/grOcdrf99 R)5<?_8L)McK9^H7  ³tiů4Avt(F1W3Z!Vl'W]5B^-vh|_#:#ED;2[4z(梒eoʱp-WQ&F^;On"l Q=ѓ5N@ǬVHG0@ܪ4*qSThjktҗِC#ROf4Ŧf ZtȦJ^Z;9ÅŞxLi)$"~I:q@R'6Hڋ|[WeKW}O*ULƣ>7N (t[I)[>NC>oJjS)z%Up(6P8@mZ$\5pJed]f)&:͐}t!P=JFұav)Vuԫ"uл݃ }s+R/FXE[x)j90|+ٯgff꫊PMiU_v>Y4>{M?;6Y]a;"!KHYgĜ##+pmycs(MvͰ=1i߯- h,$%7K" G8pB$'ШgeT4&fӃ"(>r@&}|IX6c~~1b!G8?,w+v~>Nߊk0ZE[rb̚?Kr+}Sb`0J&Sp*f*=>}_O~]Vq}jP'S]mk4yS8Nr'.A#>,m3d^L-P}="ƅ08 kYzSNT luZi =qou֚Rs8`e:kr[WI1jGpso XE;J9 !bp\g@(AgsX׎b d79=(kK5S&#@n y9<5f*=Dp( aot󻣍fI?u\m`A@υ2XE灊d%Q K%?}d?lzN= M%ۺS ӗxFģH ܆8p._MP|#gS0ЎVv݅q  nwǰq\3"gKWKl4J Q:ȐPgǀd(#@vB*n)hűB#{^K PO=!O(&GP oa|uҨyo.oe-Kn]wX7зJu#`< TJe)MiQƍ1yGXԜ uis/.VX5iKoIX'䧩S/]Y6NY|v(HQͻ⭅N1㴛=Ύc )?yȡȝGUa~z[";ZK-n(5C.|tu+gNd'I;oL+8୧U߾ rewqz ~2]9LB+g<@~dB^tQ$_Mk+gBQ9Y93c`Q(g;DY}`>_F$U=yQ>WBvb&o }/NJCl$W֎#儩Ϥ^v\*lkч.Χ@ry} 䐴aXc-4Eی%R$gW<ܕ 9X=yø03԰C8P=4XH qz})NU,Xxܱш\x,?FC#jO:+Td4N-+A\<ժh lDYN}/-5W`eޙT6f Lֆ10?5<yCQ}_X4`fCbw}U؎@ℸYq/MS(Ux\7U>M@Qڪ:8$Xj_GKS Gzjb(^{!er; .~R-?U~~;]"uӫl>pDH;j`f73Dըpx,nׂK=cѺz]Ul[tɏ-Bf^]ϥ爾KCM.]@(vOAMK dԒ ˥z2{l湨7vQdy]'70BO?Ü@(ѤLj/?s@/:$5בx}ޘҙґ յBU=*SKrmzk[wϜ߀lK7ǭ{x)}B ?B@՛jHhWn0ƴܽP[_!ڌQ\`u@cA-V,ˡ:-&|_2?mF-NAx. ɪ5xegY?'vSn}zWKsǥ/l pmegz t—:6@pNE&҈q9ǰQ߀&7rAehhP!ڐ4b=|D-N@@| QwHU{ P*EC?$ot?N&Pk*6x=yG35,c}5D Q5gp'\$s W?X\BI|RNnz_y"Cn_u>;SB?D;'Wk]]^.۰S.[ɽO4xi endstream endobj 91 0 obj 4020 endobj 88 0 obj << /Type /Page /Parent 89 0 R /Resources << /Font << /F0 6 0 R /F1 8 0 R >> /ProcSet 2 0 R >> /Contents 90 0 R >> endobj 93 0 obj << /Length 94 0 R /Filter /FlateDecode >> stream HWv<}~i&n;sI0Őۏ!izd}gO" 3` s8??}8G3,"-8`tg¨?rvwe5|G8aJc0;!,Y<9&p">8! Gx*:Rܫ~\HL|o./|ǎbF]\ʢi\4Mi+'HB\|!47jڴNv ǀeUVuU9h[3 /f%Tq#H04qHCfZ AM!Z*}ʭFc"!jafF1dJP,iX LE4b2 b_,$fŭ-c=hf8cU!3Ů2tI~sw]yz}}{tԷnFwvL[ZreRVEuK; Xuq6qC6l;U!Y8/vxcjx@oӺx""Ko7og4^FZ`n H56?Wh(C˷BCLk/I)s8N;4>Y5UL&%i{JvWwʢͣ`{v^#h#zP x|G 79]ߩp'͇KO5b({+Y""VRJSZUCAڃ4痮!JyVm*gCSPgHTM]ggn XgؗUͲyY/<ϯ}laCO>&xjf▫76,W1&*5kv!Ā n&5glёyp}6_=hA=\p`%bI-ҽ xl H5ދP ]H[&oZ(s6/U*?e13-7N7'7{08 LYO(aX+HmBZɦwH+[qqxH܉wuO΃.0cN' eɻ0 E 9ȇapn8h TiT,4tCk2ٍKBOG%rӕ-F!ndI1P]L!G/J?Rkס jHHnC',K٢ziw9Uͩ_$bJ θAתǶ&`_}AiHF0x#]Z\] ym!Ua \VY.6R;U4LvIY=8&˜0uPTqcĦ5[_6V>iȏ:_^.q\]W] VouZ¾-S4j:i?1%TX8K.e"ɳ_岏j.@#ANTCB Bb%(T2vMw[ŴZ$1]&9;"iIų_+hj&W0GAK[(u/6"dQOmş[L&ߨY2]κiѲ(WPC]mvYW6zd@ëj\תgA__{PD'7>c8$@gYj;=ܬL06# ߌ"X6;Gy@Pk@r){&6){* A~8U) 'ٻԲ/eMx~ݖ!Cۗ4X?X}@;wBi[/Dۼs,eAIN=')V#D͋B ^~Hn‘$\iݼ'٧.~:s{!mC&Ƈʈ/0Xf`{ C~Bܔ|$7z)M>g]?P! Pvzq,ɽ5L$*9n]=ߞkʿ} ,FAͪRn*=XΒw N3//y6HˍH21LSfJ _< 4p<=nc)%2C9ӏx LpRUCն۶ @KX=R'ȚHy^hh̖K<)8A/`¯-~p-ۮ F"(POҭó.)J+qb[hYΕ4;"E4@%Tj$wu|T@տkD|{L_*O/IyK!L T̨a)4q5LD,|Ó4ImݮP8fȉ{)gx*p7X2ՕH,Yiu7}I(MߔP^fάWg5FB!i\<,U4P6 nuWL4:%=uwjV9A j)6!cB΁PVGȶ"T=j3- }ql{nfctl 3$ ^J |L'fU;ϤmrTn۠X%9O_^E̦U%Q!&lG>čVf\RڠjW5V7p~9`)q9,uKQ@7e1ߘs!fO&x~Wjd7\APŹzn-7U yAZrB*@;%|=ޚt̵CY}55@B!!ݩ|xd |NS! ;^Vo|FM X/*գ٘ɱP:|d=."njq(@NNXN>2c r5A,EM˴-I*gjqt1)X)#\אB~@p$obK ˮ7nˮ!xZ=i{0JxF3$S$U(,ڃ=a'F,4:#1Oq;A l>̈IGBC١&hEqk䮥O~t`ɓ|/s2v/\G&8(4(?D*|A(y8x<隠&z:ZKU(™DSTP#!K'1|f4w'D9>!j_XBzL:PMp"aAà`-EգQqjmC1:~i?A|s B⦝bhĤzFRC?kE endstream endobj 94 0 obj 3958 endobj 92 0 obj << /Type /Page /Parent 89 0 R /Resources << /Font << /F1 8 0 R >> /ProcSet 2 0 R >> /Contents 93 0 R >> endobj 96 0 obj << /Length 97 0 R /Filter /FlateDecode >> stream HW[o8zZxMGǹ4h*t,R-AZa[$]BB`w(/m6gC*e{Bd_IJ?68}R3Koy_/T~nQD?i&b|:#}( s8$uQ $-y+#&YuH(e)EDqlJbi`!|yiBx|]-! ]#8"0qW5o:'j!6.ÑOEJ;Kv[Ui?mn"e ^DQțK(ϋϔb+ xB,7a9xFz`RND}M ~qZKex`Xuߍv &&ERoI9p̜*DDL>a)6П;hS_S` !Eys%S1*ɟ%F$d/Ah{q&ɟ6bQ[(?B0&(C Iҍ@s^J>?uooȨ$%H~ Qı4gjɕJh SPYZf;?qK01B!sy7 @oC, ( Z&75O3u)U?p[p 輪m0SɖVyK"a4 #/#2 f^yzOUFѣ4q"KQ\Zl*>csvMW0@W#ڶЇtʛuFv[YؘZ|=-WnMW^SшI=<`H} zxGF6Yؖ$fhj QTHFf#w2Ur\`,k ؕ\X1y̐T; TLGHHRǟҊo|v9@:9Hb9GڷzxRJ.嶢b_2^h1@[N ~0|UhZn27|g@W^oZs/:Hu:^ >BuPLPΕ#D8f nRZ!$vN\ol0# 6jʼlvI:# d]Y*6ہ$0:*e' 3\E~GV(vT(h_n)3v @ުJA!(LP<¡/wɱSƾ#|tk4#K[k~̠{bbx0FDxUeo&TCPa;2(Qc6F!3 jX݊gNs fJj ADrSҹcA_0329U{OgI/l( 4˟D o?힬B|͕3!bybkpL #db$uXƝ4BА.\&Տ|ӊlR@Fm0b~ʔewb94a|Ab!Ry&C3ZY'K tUЧh}߮.7'@v֪ny(opaF1) #0ԦdUVEȐ^_cZG6k޴Xd>y!B( LX2 1#I6Ïr2 "21f4@$2t FdNfj<9bK)ȥɄKRdRttM3Qp2=h7.#<r#WU) @A )68sq@U鏼,H5՛ ]SJjGyYM6~e+fGYTY.3tLі|aQ GjiUat<^eGWB&⟼(xb p̍mLdR:#=|t^-m@ <D:*렀6vJEj4N井e|Z$e' 0$.3[3/l1¹PN~A{vxxZ֩QX]M)ycnۤ:z!+E ymo.0¡(b|lk`.2_A)qwطfJ6x4D\3WͨPs.%"0" uUjb2}:68Ϧ v*yE\ a/ f+Qustv[-AA7={me7T-nZDн^L܅魯QxU‹X+xN]O,%H:NRiJ>GTya Py,YRsȓ))+iPwrc2%LǍ"3>N.ڠÈOWXnwD[l:UdP[c endstream endobj 97 0 obj 3280 endobj 95 0 obj << /Type /Page /Parent 89 0 R /Resources << /Font << /F1 8 0 R >> /ProcSet 2 0 R >> /Contents 96 0 R >> endobj 99 0 obj << /Length 100 0 R /Filter /FlateDecode >> stream HVn6ü5b-E "]CL; $%dxmH乞9sF$/ Y Wo!">$(EdJ)8+R&7RKUI;,i"C$̅@w>'`Вx0DWם |VnYSue˜'pp^Z&E㬏}4"r3\DXR%.;mz9&Gd>+22.Rەh23"EAX)ib ok^ v;֝i6^ճ'D_Ui-hA:ԡ*>q^NJɾ 8gg.|C8/7"02BIK~gn,Nl'ҤUw|70ZH =`fEؽҼE/`2mm]!]U]M]͂MmegWvvn8E;ˊ<* k3L3+YeKܡ)V1ZWۑyl,.E݈U#Aa$ ,?16ݹI-9К{4[>]oa-M4?JEFazQNGzjU5u @Y-쨡4jh 8] $)6?cNк=t-{_XܩH=ǧ=<8[\^ r>pΖ7 "^4#W]Z= ,^;p#K3;Uyc6QlBaB<v%ܳD9w&zm ="J.hY/ݰXRw{s\<&$XxR(6w]_-No[y2 4|b> /ProcSet 2 0 R >> /Contents 99 0 R >> endobj 6 0 obj << /Type /Font /Subtype /TrueType /Name /F0 /BaseFont /Arial,Bold /FirstChar 31 /LastChar 255 /Widths [ 750 278 333 474 556 556 889 722 238 333 333 389 584 278 333 278 278 556 556 556 556 556 556 556 556 556 556 333 333 584 584 584 611 975 722 722 722 722 667 611 778 722 278 556 722 611 833 722 778 667 778 722 667 611 722 667 944 667 667 611 333 278 333 584 556 333 556 611 556 611 556 333 611 611 278 278 556 278 889 611 611 611 611 389 556 333 611 556 778 556 556 500 389 280 389 584 750 556 750 278 556 500 1000 556 556 333 1000 667 333 1000 750 750 750 750 278 278 500 500 350 556 1000 333 1000 556 333 944 750 750 667 278 333 556 556 556 556 280 556 333 737 370 556 584 333 737 552 400 549 333 333 333 576 556 278 333 333 365 556 834 834 834 611 722 722 722 722 722 722 1000 722 667 667 667 667 278 278 278 278 722 722 778 778 778 778 778 584 778 722 722 722 722 667 667 611 556 556 556 556 556 556 889 556 556 556 556 556 278 278 278 278 611 611 611 611 611 611 611 549 611 611 611 611 611 556 611 556 ] /Encoding /WinAnsiEncoding /FontDescriptor 7 0 R >> endobj 7 0 obj << /Type /FontDescriptor /FontName /Arial,Bold /Flags 16416 /FontBBox [ -250 -259 1244 926 ] /MissingWidth 741 /StemV 153 /StemH 153 /ItalicAngle 0 /CapHeight 926 /XHeight 648 /Ascent 926 /Descent -259 /Leading 222 /MaxWidth 1037 /AvgWidth 481 >> endobj 8 0 obj << /Type /Font /Subtype /TrueType /Name /F1 /BaseFont /Arial /FirstChar 31 /LastChar 255 /Widths [ 750 278 278 355 556 556 889 667 191 333 333 389 584 278 333 278 278 556 556 556 556 556 556 556 556 556 556 278 278 584 584 584 556 1015 667 667 722 722 667 611 778 722 278 500 667 556 833 722 778 667 778 722 667 611 722 667 944 667 667 611 278 278 278 469 556 333 556 556 500 556 556 278 556 556 222 222 500 222 833 556 556 556 556 333 500 278 556 500 722 500 500 500 334 260 334 584 750 556 750 222 556 333 1000 556 556 333 1000 667 333 1000 750 750 750 750 222 222 333 333 350 556 1000 333 1000 500 333 944 750 750 667 278 333 556 556 556 556 260 556 333 737 370 556 584 333 737 552 400 549 333 333 333 576 537 278 333 333 365 556 834 834 834 611 667 667 667 667 667 667 1000 722 667 667 667 667 278 278 278 278 722 722 778 778 778 778 778 584 778 722 722 722 722 667 667 611 556 556 556 556 556 556 889 500 556 556 556 556 278 278 278 278 556 556 556 556 556 556 556 549 611 556 556 556 556 500 556 500 ] /Encoding /WinAnsiEncoding /FontDescriptor 9 0 R >> endobj 9 0 obj << /Type /FontDescriptor /FontName /Arial /Flags 32 /FontBBox [ -250 -231 1292 1000 ] /MissingWidth 769 /StemV 84 /StemH 84 /ItalicAngle 0 /CapHeight 1000 /XHeight 700 /Ascent 1000 /Descent -231 /Leading 231 /MaxWidth 1077 /AvgWidth 462 >> endobj 13 0 obj << /Type /Font /Subtype /TrueType /Name /F2 /BaseFont /Arial,Italic /FirstChar 31 /LastChar 255 /Widths [ 750 278 278 355 556 556 889 667 191 333 333 389 584 278 333 278 278 556 556 556 556 556 556 556 556 556 556 278 278 584 584 584 556 1015 667 667 722 722 667 611 778 722 278 500 667 556 833 722 778 667 778 722 667 611 722 667 944 667 667 611 278 278 278 469 556 333 556 556 500 556 556 278 556 556 222 222 500 222 833 556 556 556 556 333 500 278 556 500 722 500 500 500 334 260 334 584 750 556 750 222 556 333 1000 556 556 333 1000 667 333 1000 750 750 750 750 222 222 333 333 350 556 1000 333 1000 500 333 944 750 750 667 278 333 556 556 556 556 260 556 333 737 370 556 584 333 737 552 400 549 333 333 333 576 537 278 333 333 365 556 834 834 834 611 667 667 667 667 667 667 1000 722 667 667 667 667 278 278 278 278 722 722 778 778 778 778 778 584 778 722 722 722 722 667 667 611 556 556 556 556 556 556 889 500 556 556 556 556 278 278 278 278 556 556 556 556 556 556 556 549 611 556 556 556 556 500 556 500 ] /Encoding /WinAnsiEncoding /FontDescriptor 14 0 R >> endobj 14 0 obj << /Type /FontDescriptor /FontName /Arial,Italic /Flags 96 /FontBBox [ -250 -231 1292 1000 ] /MissingWidth 769 /StemV 84 /StemH 84 /ItalicAngle -11 /CapHeight 1000 /XHeight 700 /Ascent 1000 /Descent -231 /Leading 231 /MaxWidth 1077 /AvgWidth 462 >> endobj 17 0 obj << /Type /Font /Subtype /TrueType /Name /F3 /BaseFont /CourierNew /FirstChar 31 /LastChar 255 /Widths [ 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 ] /Encoding /WinAnsiEncoding /FontDescriptor 18 0 R >> endobj 18 0 obj << /Type /FontDescriptor /FontName /CourierNew /Flags 35 /FontBBox [ -250 -250 700 1000 ] /MissingWidth 583 /StemV 106 /StemH 106 /ItalicAngle 0 /CapHeight 1000 /XHeight 700 /Ascent 1000 /Descent -250 /Leading 250 /MaxWidth 583 /AvgWidth 583 >> endobj 2 0 obj [ /PDF /Text ] endobj 5 0 obj << /Kids [4 0 R 12 0 R 19 0 R 22 0 R 25 0 R 28 0 R ] /Count 6 /Type /Pages /Parent 101 0 R >> endobj 32 0 obj << /Kids [31 0 R 35 0 R 38 0 R 41 0 R 44 0 R 47 0 R ] /Count 6 /Type /Pages /Parent 101 0 R >> endobj 51 0 obj << /Kids [50 0 R 54 0 R 57 0 R 60 0 R 63 0 R 66 0 R ] /Count 6 /Type /Pages /Parent 101 0 R >> endobj 70 0 obj << /Kids [69 0 R 73 0 R 76 0 R 79 0 R 82 0 R 85 0 R ] /Count 6 /Type /Pages /Parent 101 0 R >> endobj 89 0 obj << /Kids [88 0 R 92 0 R 95 0 R 98 0 R ] /Count 4 /Type /Pages /Parent 101 0 R >> endobj 101 0 obj << /Kids [5 0 R 32 0 R 51 0 R 70 0 R 89 0 R ] /Count 28 /Type /Pages /MediaBox [ 0 0 596 842 ] >> endobj 1 0 obj << /Creator /CreationDate (D:20010804080205) /Title /Author /Producer (Acrobat PDFWriter 4.0 fr Windows) >> endobj 3 0 obj << /Pages 101 0 R /Type /Catalog /DefaultGray 102 0 R /DefaultRGB 103 0 R >> endobj 102 0 obj [/CalGray << /WhitePoint [0.9505 1 1.0891 ] /Gamma 0.2468 >> ] endobj 103 0 obj [/CalRGB << /WhitePoint [0.9505 1 1.0891 ] /Gamma [0.2468 0.2468 0.2468 ] /Matrix [0.4361 0.2225 0.0139 0.3851 0.7169 0.0971 0.1431 0.0606 0.7141 ] >> ] endobj xref 0 104 0000000000 65535 f 0000057254 00000 n 0000056518 00000 n 0000057496 00000 n 0000002255 00000 n 0000056552 00000 n 0000050922 00000 n 0000052042 00000 n 0000052323 00000 n 0000053439 00000 n 0000000021 00000 n 0000002231 00000 n 0000006037 00000 n 0000053713 00000 n 0000054838 00000 n 0000002399 00000 n 0000006013 00000 n 0000055122 00000 n 0000056238 00000 n 0000008588 00000 n 0000006208 00000 n 0000008564 00000 n 0000010360 00000 n 0000008746 00000 n 0000010336 00000 n 0000011791 00000 n 0000010518 00000 n 0000011767 00000 n 0000013068 00000 n 0000011949 00000 n 0000013044 00000 n 0000014139 00000 n 0000056669 00000 n 0000013226 00000 n 0000014116 00000 n 0000015314 00000 n 0000014298 00000 n 0000015291 00000 n 0000017048 00000 n 0000015473 00000 n 0000017024 00000 n 0000018466 00000 n 0000017207 00000 n 0000018442 00000 n 0000019439 00000 n 0000018625 00000 n 0000019416 00000 n 0000020854 00000 n 0000019598 00000 n 0000020830 00000 n 0000022277 00000 n 0000056788 00000 n 0000021013 00000 n 0000022253 00000 n 0000023742 00000 n 0000022436 00000 n 0000023718 00000 n 0000025128 00000 n 0000023901 00000 n 0000025104 00000 n 0000026462 00000 n 0000025287 00000 n 0000026438 00000 n 0000027826 00000 n 0000026621 00000 n 0000027802 00000 n 0000029131 00000 n 0000027985 00000 n 0000029107 00000 n 0000030750 00000 n 0000056907 00000 n 0000029290 00000 n 0000030726 00000 n 0000031596 00000 n 0000030909 00000 n 0000031573 00000 n 0000033217 00000 n 0000031755 00000 n 0000033193 00000 n 0000034709 00000 n 0000033376 00000 n 0000034685 00000 n 0000036286 00000 n 0000034868 00000 n 0000036262 00000 n 0000037353 00000 n 0000036445 00000 n 0000037330 00000 n 0000041642 00000 n 0000057026 00000 n 0000037512 00000 n 0000041618 00000 n 0000045856 00000 n 0000041788 00000 n 0000045832 00000 n 0000049380 00000 n 0000045990 00000 n 0000049356 00000 n 0000050763 00000 n 0000049514 00000 n 0000050738 00000 n 0000057131 00000 n 0000057597 00000 n 0000057686 00000 n trailer << /Size 104 /Root 3 0 R /Info 1 0 R /ID [] >> startxref 57865 %%EOF pynmea2-1.18.0/README.md000066400000000000000000000111251403520633500144340ustar00rootroot00000000000000pynmea2 ======= `pynmea2` is a python library for the [NMEA 0183](http://en.wikipedia.org/wiki/NMEA_0183) protocol `pynmea2` is based on [`pynmea`](https://code.google.com/p/pynmea/) by Becky Lewis The `pynmea2` homepage is located at http://github.com/Knio/pynmea2 ### Compatibility `pynmea2` is compatable with Python 2.7 and Python 3.4+ ![Python version](https://img.shields.io/pypi/pyversions/pynmea2.svg?style=flat) [![Build Status](https://www.travis-ci.com/Knio/pynmea2.svg?branch=master)](https://www.travis-ci.com/Knio/pynmea2) [![Coverage status](https://img.shields.io/coveralls/github/Knio/pynmea2/master.svg?style=flat)](https://coveralls.io/r/Knio/pynmea2?branch=master) ### Installation The recommended way to install `pynmea2` is with [pip](http://pypi.python.org/pypi/pip/): pip install pynmea2 [![PyPI version](https://img.shields.io/pypi/v/pynmea2.svg?style=flat)](https://pypi.org/project/pynmea2/) [![PyPI downloads](https://img.shields.io/pypi/dm/pynmea2.svg?style=flat)](https://pypi.org/project/pynmea2/) Parsing ------- You can parse individual NMEA sentences using the `parse(data, check=False)` function, which takes a string containing a NMEA 0183 sentence and returns a `NMEASentence` object. Note that the leading '$' is optional and trailing whitespace is ignored when parsing a sentence. With `check=False`, `parse` will accept NMEA messages that do not have checksums, however it will still raise `pynmea2.ChecksumError` if they are present. `check=True` will also raise `ChecksumError` if the checksum is missing. Example: ```python >>> import pynmea2 >>> msg = pynmea2.parse("$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D") >>> msg ``` The `NMEASentence` object has different properties, depending on its sentence type. The `GGA` message has the following properties: ```python >>> msg.timestamp datetime.time(18, 43, 53) >>> msg.lat '1929.045' >>> msg.lat_dir 'S' >>> msg.lon '02410.506' >>> msg.lon_dir 'E' >>> msg.gps_qual '1' >>> msg.num_sats '04' >>> msg.horizontal_dil '2.6' >>> msg.altitude 100.0 >>> msg.altitude_units 'M' >>> msg.geo_sep '-33.9' >>> msg.geo_sep_units 'M' >>> msg.age_gps_data '' >>> msg.ref_station_id '0000' ``` Additional properties besides the ones explicitly in the message data may also exist. For example, `latitude` and `longitude` properties exist as helpers to access the geographic coordinates as python floats ([DD](http://en.wikipedia.org/wiki/Decimal_degrees), "decimal degrees") instead of the DDDMM.MMMM ("Degrees, minutes, seconds") format used in the NMEA protocol. `latitude_minutes`, `latitude_seconds`, `longitude_minutes`, and `longitude_seconds` are also supported and allow easy creation of differently formatted location strings. ```python >>> msg.latitude -19.4840833333 >>> msg.longitude 24.1751 >>> '%02d°%07.4f′' % (msg.latitude, msg.latitude_minutes) '-19°29.0450′' >>> '%02d°%02d′%07.4f″' % (msg.latitude, msg.latitude_minutes, msg.latitude_seconds) "-19°29′02.7000″" ``` Generating ---------- You can create a `NMEASentence` object by calling the constructor with talker, message type, and data fields: ```python >>> import pynmea2 >>> msg = pynmea2.GGA('GP', 'GGA', ('184353.07', '1929.045', 'S', '02410.506', 'E', '1', '04', '2.6', '100.00', 'M', '-33.9', 'M', '', '0000')) ``` and generate a NMEA string from a `NMEASentence` object: ```python >>> str(msg) '$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D' ``` File reading example -------- See [examples/read_file.py](/examples/read_file.py) ```python import pynmea2 file = open('examples/data.log', encoding='utf-8') for line in file.readlines(): try: msg = pynmea2.parse(line) print(repr(msg)) except pynmea2.ParseError as e: print('Parse error: {}'.format(e)) continue ``` pySerial device example --------- See [examples/read_serial.py](/examples/read_serial.py) ```python import io import pynmea2 import serial ser = serial.Serial('/dev/ttyS1', 9600, timeout=5.0) sio = io.TextIOWrapper(io.BufferedRWPair(ser, ser)) while 1: try: line = sio.readline() msg = pynmea2.parse(line) print(repr(msg)) except serial.SerialException as e: print('Device error: {}'.format(e)) break except pynmea2.ParseError as e: print('Parse error: {}'.format(e)) continue ``` pynmea2-1.18.0/examples/000077500000000000000000000000001403520633500147735ustar00rootroot00000000000000pynmea2-1.18.0/examples/data.log000066400000000000000000000003211403520633500164030ustar00rootroot00000000000000$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D $GPRTE,2,1,c,0,PBRCPK,PBRTO,PTELGR,PPLAND,PYAMBU,PPFAIR,PWARRN,PMORTL,PLISMR*73 $GPR00,A,B,C*29 foobar $IIMWV,271.0,R,000.2,N,A*3Bpynmea2-1.18.0/examples/read_file.py000066400000000000000000000004061403520633500172570ustar00rootroot00000000000000import pynmea2 file = open('examples/data.log', encoding='utf-8') for line in file.readlines(): try: msg = pynmea2.parse(line) print(repr(msg)) except pynmea2.ParseError as e: print('Parse error: {}'.format(e)) continue pynmea2-1.18.0/examples/read_serial.py000066400000000000000000000007001403520633500176140ustar00rootroot00000000000000import io import pynmea2 import serial ser = serial.Serial('/dev/ttyS1', 9600, timeout=5.0) sio = io.TextIOWrapper(io.BufferedRWPair(ser, ser)) while 1: try: line = sio.readline() msg = pynmea2.parse(line) print(repr(msg)) except serial.SerialException as e: print('Device error: {}'.format(e)) break except pynmea2.ParseError as e: print('Parse error: {}'.format(e)) continuepynmea2-1.18.0/pynmea2/000077500000000000000000000000001403520633500145305ustar00rootroot00000000000000pynmea2-1.18.0/pynmea2/__init__.py000066400000000000000000000006531403520633500166450ustar00rootroot00000000000000# pylint: disable=missing-docstring # pylint: disable=wildcard-import # pylint: disable=invalid-name from ._version import __version__ version = __version__ from .nmea import NMEASentence, ProprietarySentence, QuerySentence from .nmea import ChecksumError, ParseError, SentenceTypeError parse = NMEASentence.parse from .types import * from .stream import NMEAStreamReader from .nmea_file import NMEAFile pynmea2-1.18.0/pynmea2/_version.py000066400000000000000000000000301403520633500167170ustar00rootroot00000000000000__version__ = '1.18.0' pynmea2-1.18.0/pynmea2/nmea.py000066400000000000000000000164011403520633500160240ustar00rootroot00000000000000import re import operator from functools import reduce class ParseError(ValueError): def __init__(self, message, data): super(ParseError, self).__init__((message, data)) class SentenceTypeError(ParseError): pass class ChecksumError(ParseError): pass class NMEASentenceType(type): sentence_types = {} def __init__(cls, name, bases, dct): type.__init__(cls, name, bases, dct) base = bases[0] if base is object: return base.sentence_types[name] = cls cls.name_to_idx = dict((f[1], i) for i, f in enumerate(cls.fields)) # http://mikewatkins.ca/2008/11/29/python-2-and-3-metaclasses/ NMEASentenceBase = NMEASentenceType('NMEASentenceBase', (object,), {}) class NMEASentence(NMEASentenceBase): ''' Base NMEA Sentence Parses and generates NMEA strings Examples: >>> s = NMEASentence.parse("$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D") >>> print(s) ''' sentence_re = re.compile(r''' # start of string, optional whitespace, optional '$' ^\s*\$? # message (from '$' or start to checksum or end, non-inclusve) (?P # sentence type identifier (?P # proprietary sentence (P\w{3})| # query sentence, ie: 'CCGPQ,GGA' # NOTE: this should have no data (\w{2}\w{2}Q,\w{3})| # taker sentence, ie: 'GPGGA' (\w{2}\w{3},) ) # rest of message (?P[^*]*) ) # checksum: *HH (?:[*](?P[A-F0-9]{2}))? # optional trailing whitespace \s*[\r\n]*$ ''', re.X | re.IGNORECASE) talker_re = \ re.compile(r'^(?P\w{2})(?P\w{3}),$') query_re = \ re.compile(r'^(?P\w{2})(?P\w{2})Q,(?P\w{3})$') proprietary_re = \ re.compile(r'^P(?P\w{3})$') name_to_idx = {} fields = () @staticmethod def checksum(nmea_str): return reduce(operator.xor, map(ord, nmea_str), 0) @staticmethod def parse(line, check=False): ''' parse(line) Parses a string representing a NMEA 0183 sentence, and returns a NMEASentence object Raises ValueError if the string could not be parsed, or if the checksum did not match. ''' match = NMEASentence.sentence_re.match(line) if not match: raise ParseError('could not parse data', line) # pylint: disable=bad-whitespace nmea_str = match.group('nmea_str') data_str = match.group('data') checksum = match.group('checksum') sentence_type = match.group('sentence_type').upper() data = data_str.split(',') if checksum: cs1 = int(checksum, 16) cs2 = NMEASentence.checksum(nmea_str) if cs1 != cs2: raise ChecksumError( 'checksum does not match: %02X != %02X' % (cs1, cs2), data) elif check: raise ChecksumError( 'strict checking requested but checksum missing', data) talker_match = NMEASentence.talker_re.match(sentence_type) if talker_match: talker = talker_match.group('talker') sentence = talker_match.group('sentence') cls = TalkerSentence.sentence_types.get(sentence) if not cls: # TODO instantiate base type instead of fail raise SentenceTypeError( 'Unknown sentence type %s' % sentence_type, line) return cls(talker, sentence, data) query_match = NMEASentence.query_re.match(sentence_type) if query_match and not data_str: talker = query_match.group('talker') listener = query_match.group('listener') sentence = query_match.group('sentence') return QuerySentence(talker, listener, sentence) proprietary_match = NMEASentence.proprietary_re.match(sentence_type) if proprietary_match: manufacturer = proprietary_match.group('manufacturer') cls = ProprietarySentence.sentence_types.get(manufacturer, ProprietarySentence) return cls(manufacturer, data) raise ParseError( 'could not parse sentence type: %r' % sentence_type, line) def __getattr__(self, name): #pylint: disable=invalid-name t = type(self) try: i = t.name_to_idx[name] except KeyError: raise AttributeError(name) f = t.fields[i] if i < len(self.data): v = self.data[i] else: v = '' if len(f) >= 3: if v == '': return None try: return f[2](v) except: return v else: return v def __setattr__(self, name, value): #pylint: disable=invalid-name t = type(self) if name not in t.name_to_idx: return object.__setattr__(self, name, value) i = t.name_to_idx[name] self.data[i] = str(value) def __repr__(self): #pylint: disable=invalid-name r = [] d = [] t = type(self) for i, v in enumerate(self.data): if i >= len(t.fields): d.append(v) continue name = t.fields[i][1] r.append('%s=%r' % (name, getattr(self, name))) return '<%s(%s)%s>' % ( type(self).__name__, ', '.join(r), d and ' data=%r' % d or '' ) def identifier(self): raise NotImplementedError def render(self, checksum=True, dollar=True, newline=False): res = self.identifier() + ','.join(self.data) if checksum: res += '*%02X' % NMEASentence.checksum(res) if dollar: res = '$' + res if newline: res += (newline is True) and '\r\n' or newline return res def __str__(self): return self.render() class TalkerSentence(NMEASentence): sentence_types = {} def __init__(self, talker, sentence_type, data): self.talker = talker self.sentence_type = sentence_type self.data = list(data) def identifier(self): return '%s%s,' % (self.talker, self.sentence_type) class QuerySentence(NMEASentence): sentence_types = {} def __init__(self, talker, listener, sentence_type): self.talker = talker self.listener = listener self.sentence_type = sentence_type self.data = [] def identifier(self): return '%s%sQ,%s' % (self.talker, self.listener, self.sentence_type) class ProprietarySentence(NMEASentence): sentence_types = {} def __init__(self, manufacturer, data): self.manufacturer = manufacturer self.data = list(data) def identifier(self): return 'P%s' % (self.manufacturer) pynmea2-1.18.0/pynmea2/nmea_file.py000066400000000000000000000037361403520633500170320ustar00rootroot00000000000000try: # pylint: disable=used-before-assignment basestring = basestring except NameError: # py3 basestring = str from .nmea import NMEASentence class NMEAFile(object): """ Reads NMEA sentences from a file similar to a standard python file object. """ def __init__(self, f, *args, **kwargs): super(NMEAFile, self).__init__() if isinstance(f, basestring) or args or kwargs: self._file = self.open(f, *args, **kwargs) else: self._file = f self._context = None def open(self, fp, mode='r'): """ Open the NMEAFile. """ self._file = open(fp, mode=mode) return self._file def close(self): """ Close the NMEAFile. """ self._file.close() def __iter__(self): """ Iterate through the file yielding NMEASentences :return: """ for line in self._file: yield self.parse(line) def __enter__(self): if hasattr(self._file, '__enter__'): self._context = self._file.__enter__() return self def __exit__(self, exc_type, exc_val, exc_tb): if self._context: ctx = self._context self._context = None ctx.__exit__(exc_type, exc_val, exc_tb) def next(self): """ Iterate through the file object returning NMEASentence objects :return: NMEASentence """ data = self._file.readline() return self.parse(data) def parse(self, s): return NMEASentence.parse(s) def readline(self): """ Return the next NMEASentence in the file object :return: NMEASentence """ data = self._file.readline() s = self.parse(data) return s def read(self): """ Return a list of NMEASentence objects for each line in the file :return: list of NMEASentence objects """ return [s for s in self] pynmea2-1.18.0/pynmea2/nmea_utils.py000066400000000000000000000070301403520633500172420ustar00rootroot00000000000000#pylint: disable=invalid-name import datetime import re def valid(s): return s == 'A' def timestamp(s): ''' Converts a timestamp given in "hhmmss[.ss]" ASCII text format to a datetime.time object ''' ms_s = s[6:] ms = ms_s and int(float(ms_s) * 1000000) or 0 t = datetime.time( hour=int(s[0:2]), minute=int(s[2:4]), second=int(s[4:6]), microsecond=ms) return t def datestamp(s): ''' Converts a datestamp given in "DDMMYY" ASCII text format to a datetime.datetime object ''' return datetime.datetime.strptime(s, '%d%m%y').date() def dm_to_sd(dm): ''' Converts a geographic co-ordinate given in "degrees/minutes" dddmm.mmmm format (eg, "12319.943281" = 123 degrees, 19.943281 minutes) to a signed decimal (python float) format ''' # '12319.943281' if not dm or dm == '0': return 0. d, m = re.match(r'^(\d+)(\d\d\.\d+)$', dm).groups() return float(d) + float(m) / 60 class LatLonFix(object): '''Mixin to add `latitude` and `longitude` properties as signed decimals to NMEA sentences which have co-ordinates given as degrees/minutes (lat, lon) and cardinal directions (lat_dir, lon_dir)''' #pylint: disable=no-member @property def latitude(self): '''Latitude in signed degrees (python float)''' sd = dm_to_sd(self.lat) if self.lat_dir == 'N': return +sd elif self.lat_dir == 'S': return -sd else: return 0. @property def longitude(self): '''Longitude in signed degrees (python float)''' sd = dm_to_sd(self.lon) if self.lon_dir == 'E': return +sd elif self.lon_dir == 'W': return -sd else: return 0. @staticmethod def _minutes(x): return abs(x * 60.) % 60. @staticmethod def _seconds(x): return abs(x * 3600.) % 60. @property def latitude_minutes(self): return self._minutes(self.latitude) @property def longitude_minutes(self): return self._minutes(self.longitude) @property def latitude_seconds(self): return self._seconds(self.latitude) @property def longitude_seconds(self): return self._seconds(self.longitude) class DatetimeFix(object): #pylint: disable=no-member @property def datetime(self): return datetime.datetime.combine(self.datestamp, self.timestamp) class ValidStatusFix(object): #pylint: disable=no-member @property def is_valid(self): return self.status == 'A' class ValidGSAFix(object): #pylint: disable=no-member @property def is_valid(self): return int(self.mode_fix_type) in [2, 3] class ValidGGAFix(object): #pylint: disable=no-member @property def is_valid(self): return self.gps_qual in range(1,6) class ValidVBWFix(object): #pylint: disable=no-member @property def is_valid(self): return self.data_validity_water_spd == self.data_validity_grnd_spd == 'A' class TZInfo(datetime.tzinfo): def __init__(self, hh, mm): self.hh = hh self.mm = mm super(TZInfo, self).__init__() def tzname(self, dt): return '' def dst(self, dt): return datetime.timedelta(0) def utcoffset(self, dt): return datetime.timedelta(hours=self.hh, minutes=self.mm) pynmea2-1.18.0/pynmea2/seatalk_utils.py000066400000000000000000000016061403520633500177510ustar00rootroot00000000000000# pylint: disable=invalid-name class SeaTalk(object): '''Mixin to add Seatalk functionality. Based on Thomas knauf's work http://www.thomasknauf.de/seatalk.htm''' byte_to_command = { '00': 'Depth below transducer', '01': 'Equipment ID', '05': 'Engine RPM and PITCH', '10': 'Apparent Wind Angle', '11': 'Apparent Wind Speed', '20': 'Speed through water', '50': 'LAT position', '51': 'LON position', '52': 'Speed over Ground', '53': 'Course over Ground', '82': 'Target waypoint name', '84': 'Compass heading Autopilot course and Rudder position', '9C': 'Compass heading and Rudder position' } # pylint: disable=no-member @property def command_name(self): '''Get seatalk command's meaning''' return self.byte_to_command.get(self.cmd, 'Unknown Command') pynmea2-1.18.0/pynmea2/stream.py000066400000000000000000000046031403520633500164000ustar00rootroot00000000000000from __future__ import unicode_literals from . import nmea __all__ = ['NMEAStreamReader'] ERRORS = ('raise', 'yield', 'ignore') class NMEAStreamReader(object): ''' Reads NMEA sentences from a stream. ''' def __init__(self, stream=None, errors='raise'): ''' Create NMEAStreamReader object. `stream`: file-like object to read from, can be omitted to pass data to `next` manually. must support `.readline()` which returns a string `errors`: behaviour when a parse error is encountered. can be one of: `'raise'` (default) raise an exception immediately `'yield'` yield the ParseError as an element in the stream, and continue reading at the next line `'ignore'` completely ignore and suppress the error, and continue reading at the next line ''' if errors not in ERRORS: raise ValueError('errors must be one of {!r} (was: {!r})' .format(ERRORS, errors)) self.errors = errors self.stream = stream self.buffer = '' def next(self, data=None): ''' consume `data` (if given, or calls `stream.read()` if `stream` was given in the constructor) and yield a list of `NMEASentence` objects parsed from the stream (may be empty) ''' if data is None: if self.stream: data = self.stream.readline() else: return lines = (self.buffer + data).split('\n') self.buffer = lines.pop() for line in lines: try: msg = nmea.NMEASentence.parse(line) yield msg except nmea.ParseError as e: if self.errors == 'raise': raise e if self.errors == 'yield': yield e if self.errors == 'ignore': pass __next__ = next def __iter__(self): ''' Support the iterator protocol. This allows NMEAStreamReader object to be used in a for loop. for batch in NMEAStreamReader(stream): for msg in batch: print msg ''' return self pynmea2-1.18.0/pynmea2/types/000077500000000000000000000000001403520633500156745ustar00rootroot00000000000000pynmea2-1.18.0/pynmea2/types/__init__.py000066400000000000000000000001321403520633500200010ustar00rootroot00000000000000# pylint: disable=wildcard-import from .talker import * from .proprietary import * pynmea2-1.18.0/pynmea2/types/proprietary/000077500000000000000000000000001403520633500202545ustar00rootroot00000000000000pynmea2-1.18.0/pynmea2/types/proprietary/__init__.py000066400000000000000000000003231403520633500223630ustar00rootroot00000000000000from . import ash from . import grm from . import kwd from . import mgn from . import rdi from . import srf from . import sxn from . import tnl from . import ubx from . import vtx from . import nor pynmea2-1.18.0/pynmea2/types/proprietary/ash.py000066400000000000000000000075451403520633500214140ustar00rootroot00000000000000''' Support for proprietary messages from Ashtech receivers. ''' # pylint: disable=wildcard-import,unused-wildcard-import from decimal import Decimal import re from ... import nmea from ...nmea_utils import * class ASH(nmea.ProprietarySentence): ''' Generic Ashtech Response Message ''' sentence_types = {} def __new__(_cls, manufacturer, data): ''' Return the correct sentence type based on the first field ''' sentence_type = data[1] name = manufacturer + 'R' + sentence_type if name not in _cls.sentence_types: # ASHRATT does not have a sentence type if ASHRATT.match(data): return super(ASH, ASHRATT).__new__(ASHRATT) cls = _cls.sentence_types.get(name, ASH) return super(ASH, cls).__new__(cls) class ASHRATT(ASH): ''' RT300 proprietary attitude sentence ''' @staticmethod def match(data): return re.match(r'^\d{6}\.\d{2,3}$', data[1]) def __init__(self, *args, **kwargs): self.subtype = 'ATT' super(ASHRATT, self).__init__(*args, **kwargs) fields = ( ('R', '_r'), ('Timestamp', 'timestamp', timestamp), ('Heading Angle', 'true_heading', float), ('Is True Heading', 'is_true_heading'), ('Roll Angle', 'roll', float), ('Pitch Angle', 'pitch', float), ('Heave', 'heading', float), ('Roll Accuracy Estimate', 'roll_accuracy', float), ('Pitch Accuracy Estimate', 'pitch_accuracy', float), ('Heading Accuracy Estimate', 'heading_accuracy', float), ('Aiding Status', 'aiding_status', Decimal), ('IMU Status', 'imu_status', Decimal), ) class ASHRHPR(ASH): ''' Ashtech HPR Message ''' fields = ( ('R', '_r'), ('Subtype', 'subtype'), ('Timestamp', 'timestamp', timestamp), ('Heading Angle', 'heading', Decimal), ('Pitch Angle', 'pitch', Decimal), ('Roll Angle', 'roll', Decimal), ('Carrier measurement RMS', 'carrier_rms', Decimal), ('Baseline measurement RMS', 'baseline_rms', Decimal), ('Integer Ambiguity', 'integer_ambiguity'), ('Mode', 'mode'), ('Status', 'status'), ('PDOP', 'pdop', float), ) class ASHRLTN(ASH): ''' Ashtech LTN Message ''' fields = ( ('R', '_r'), ('Subtype', 'subtype'), ('Latency (ms)', 'latency', int), ) class ASHRPOS(ASH, LatLonFix): ''' Ashtech POS Message ''' fields = ( ('R', '_r'), ('Subtype', 'subtype'), ('Solution Type', 'mode', int), ('Satellites used in Solution', 'sat_count', int), ('Timestamp', 'timestamp', timestamp), ('Latitude', 'lat'), ('Latitude Direction', 'lat_dir'), ('Longitude', 'lon'), ('Longitude Direction', 'lon_dir'), ('Altitude above WGS84 ellipsoid, meters', 'altitude'), ('Empty', '__'), ("True Track/Course Over Ground", "course", float), ("Speed Over Ground", "spd_over_grnd", float), ('Vertical Velocity', 'vertical_velocity', Decimal), ('PDOP', 'pdop', float), ('HDOP', 'hdop', float), ('VDOP', 'vdop', float), ('TDOP', 'tdop', float), ('Base station ID', 'station_id', int) ) class ASHRVEL(ASH): ''' Ashtech VEL Message ''' fields = ( ('R', '_r'), ('Subtype', 'subtype'), ('ENU', 'enu', int), ('Timestamp', 'timestamp', timestamp), ('Easting', 'easting', Decimal), ('Northing', 'northing', Decimal), ('Vertical Velocity', 'vertical', Decimal), ('Easting RMS', 'easting_rms', Decimal), ('Northing RMS', 'northing_rms', Decimal), ('Vertical RMS', 'vertical_rms', Decimal), ('Applied effective velocity smoothing interval (ms)', 'smoothing', Decimal), ) pynmea2-1.18.0/pynmea2/types/proprietary/grm.py000066400000000000000000000042741403520633500214220ustar00rootroot00000000000000# Garmin from decimal import Decimal from ... import nmea class GRM(nmea.ProprietarySentence): sentence_types = {} def __new__(_cls, manufacturer, data): name = manufacturer + data[0] cls = _cls.sentence_types.get(name, _cls) return super(GRM, cls).__new__(cls) def __init__(self, manufacturer, data): self.sentence_type = manufacturer + data[0] super(GRM, self).__init__(manufacturer, data) class GRME(GRM): """ GARMIN Estimated position error """ fields = ( ("Subtype", "subtype"), ("Estimated Horiz. Position Error", "hpe", Decimal), ("Estimated Horiz. Position Error Unit (M)", "hpe_unit"), ("Estimated Vert. Position Error", "vpe", Decimal), ("Estimated Vert. Position Error Unit (M)", "vpe_unit"), ("Estimated Horiz. Position Error", "osepe", Decimal), ("Overall Spherical Equiv. Position Error", "osepe_unit"), ) class GRMM(GRM): """ GARMIN Map Datum """ fields = ( ("Subtype", "subtype"), ('Currently Active Datum', 'datum'), ) class GRMW(GRM): """ GARMIN Waypoint Information https://www8.garmin.com/support/pdf/NMEA_0183.pdf https://github.com/wb2osz/direwolf/blob/master/waypoint.c $PGRMW,wname,alt,symbol,comment*99 Where, wname is waypoint name. Must match existing waypoint. alt is altitude in meters. symbol is symbol code. Hexadecimal up to FFFF. See Garmin Device Interface Specification 001-0063-00 for values of "symbol_type." comment is comment for the waypoint. *99 is checksum """ fields = ( ("Subtype", "subtype"), ("Waypoint Name", "wname"), ("Altitude", "altitude", Decimal), ("Symbol", "symbol"), ("Comment", "comment"), ) class GRMZ(GRM): """ GARMIN Altitude Information """ fields = ( ("Subtype", "subtype"), ("Altitude", "altitude", Decimal), ("Altitude Units (Feet)", "altitude_unit"), ("Positional Fix Dimension (2=user, 3=GPS)", "pos_fix_dim"), ) pynmea2-1.18.0/pynmea2/types/proprietary/kwd.py000066400000000000000000000105371403520633500214210ustar00rootroot00000000000000# Kenwood from decimal import Decimal from datetime import date, time from ... import nmea from ... import nmea_utils class KWD(nmea.ProprietarySentence): sentence_types = {} def __new__(_cls, manufacturer, data): name = manufacturer + data[0] cls = _cls.sentence_types.get(name, _cls) return super(KWD, cls).__new__(cls) def __init__(self, manufacturer, data): self.sentence_type = manufacturer + data[0] super(KWD, self).__init__(manufacturer, data) class KWDWPL(KWD, nmea_utils.LatLonFix, nmea_utils.DatetimeFix, nmea_utils.ValidStatusFix): """ Kenwood Waypoint Location https://github.com/wb2osz/direwolf/blob/master/waypoint.c $PKWDWPL,hhmmss,v,ddmm.mm,ns,dddmm.mm,ew,speed,course,ddmmyy,alt,wname,ts*99 Where, hhmmss is time in UTC from the clock in the transceiver. This will be bogus if the clock was not set properly. It does not use the timestamp from a position report which could be useful. GPS Status A = active, V = void. It looks like this might be modeled after the GPS status values we see in $GPRMC. i.e. Does the transceiver know its location? I don't see how that information would be relevant in this context. I've observed this under various conditions (No GPS, GPS with/without fix) and it has always been "V." ddmm.mm,ns is latitude. N or S. dddmm.mm,ew is longitude. E or W. speed is speed over ground, knots. course is course over ground, degrees. ddmmyy is date. See comments for time. alt is altitude, meters above mean sea level. wname is the waypoint name. For an Object Report, the id is the object name. For a position report, it is the call of the sending station. An Object name can contain any printable characters. What if object name contains , or * characters? Those are field delimiter characters and it would be unfortunate if they appeared in a NMEA sentence data field. If there is a comma in the name, such as "test,5" the Kenwood TM-D710A displays it fine but we end up with an extra field. $PKWDWPL,150803,V,4237.14,N,07120.83,W,,,190316,,test,5,/'*30 If the name contains an asterisk, it doesn't show up on the display and no waypoint sentence is generated. Some other talkers substitute these two characters following the AvMap precedent. $PKWDWPL,204714,V,4237.1400,N,07120.8300,W,,,200316,,test|5,/'*61 $PKWDWPL,204719,V,4237.1400,N,07120.8300,W,,,200316,,test~6,/'*6D ts are the table and symbol. What happens if the symbol is comma or asterisk? , Boy Scouts / Girl Scouts * SnowMobile / Snow the D710A just pushes them thru without checking. These would not be parsed properly: $PKWDWPL,150753,V,4237.14,N,07120.83,W,,,190316,,test3,/,*1B $PKWDWPL,150758,V,4237.14,N,07120.83,W,,,190316,,test4,/ **3B Other talkers do the usual substitution and the other end would need to change them back after extracting from NMEA sentence. $PKWDWPL,204704,V,4237.1400,N,07120.8300,W,,,200316,,test3,/|*41 $PKWDWPL,204709,V,4237.1400,N,07120.8300,W,,,200316,,test4,/~*49 *99 is checksum Oddly, there is no place for comment. """ fields = ( ("Subtype", "subtype"), ("Time of Receipt", "timestamp", nmea_utils.timestamp), ("GPS Status (Void)","status"), ("Latitude", "lat"), ("Latitude Direction", "lat_dir"), ("Longitude", "lon"), ("Longitude Direction", "lon_dir"), ("Speed over Ground", "sog", float), ("Course over Ground", "cog", float), ("Date", "datestamp", nmea_utils.datestamp), ("Altitude", "altitude", Decimal), ("Waypoint Name", "wname"), ("Table and Symbol", "ts"), ) pynmea2-1.18.0/pynmea2/types/proprietary/mgn.py000066400000000000000000000031321403520633500214060ustar00rootroot00000000000000# Magellan from decimal import Decimal from ... import nmea from ... import nmea_utils class MGN(nmea.ProprietarySentence): sentence_types = {} def __new__(_cls, manufacturer, data): name = manufacturer + data[0] cls = _cls.sentence_types.get(name, _cls) return super(MGN, cls).__new__(cls) def __init__(self, manufacturer, data): self.sentence_type = manufacturer + data[0] super(MGN, self).__init__(manufacturer, data) class MGNWPL(MGN, nmea_utils.LatLonFix): """ Magellan Waypoint Location https://github.com/wb2osz/direwolf/blob/master/waypoint.c $PMGNWPL,ddmm.mmmm,ns,dddmm.mmmm,ew,alt,unit,wname,comment,icon,xx*99 Where, ddmm.mmmm,ns is latitude dddmm.mmmm,ew is longitude alt is altitude unit is M for meters or F for feet wname is the waypoint name comment is message or comment icon is one or two letters for icon code xx is waypoint type which is optional, not well defined, and not used in their example. *99 is checksum """ fields = ( ("Subtype", "subtype"), ("Latitude", "lat"), ("Latitude Direction", "lat_dir"), ("Longitude", "lon"), ("Longitude Direction", "lon_dir"), ("Altitude", "altitude", Decimal), ("Altitude Units (Feet/Meters)", "altitude_unit"), ("Waypoint Name", "wname"), ("Comment", "comment"), ("Icon", "icon"), ("Waypoint Type", "type") ) pynmea2-1.18.0/pynmea2/types/proprietary/nor.py000066400000000000000000000240111403520633500214220ustar00rootroot00000000000000''' Support for proprietary messages from Nortek Doppler Velocity Log (DVL). ''' from ... import ProprietarySentence, nmea_utils from datetime import datetime class NOR(ProprietarySentence): sentence_types = {} def __new__(_cls, manufacturer, data): name = manufacturer + data[0] cls = _cls.sentence_types.get(name, _cls) return super(NOR, cls).__new__(cls) def __init__(self, manufacturer, data): self.sentence_type = manufacturer + data[0] super(NOR, self).__init__(manufacturer, data[1:]) def identifier(self): return 'P%s,' % (self.sentence_type) ################################################################## ## ## ## DVL Bottom Track ASCII formats ## ## ## ## Invalid estimates of Velocity are set to set to -32.768. ## ## Invalid estimates of Range are set to 0.0. ## ## Invalid estimates of FOM are set to 10.0 ## ################################################################## class NORBT0(NOR, nmea_utils.DatetimeFix): # Bottom Track DF350/DF351 - NMEA $PNORBT1/$PNORBT0 # Example: $PNORBT0,1,040721,131335.3341,23.961,-48.122,-32.76800,10.00000,0.00,0x00000000*48 fields = ( ('Beam number', 'beam', int), ('Date', 'datestamp', nmea_utils.datestamp), ('Time', 'timestamp', nmea_utils.timestamp), ('Time (Trigger)', 'dt1', float), ('Time (NMEA)', 'dt2', float), ('Beam Velocity', 'bv', float), ('Figure of Merit', 'fom', float), ('Vertical Distance', 'dist', float), ('Status ', 'stat'), ) class NORBT4(NOR, nmea_utils.DatetimeFix): # Bottom Track DF354/DF355 - NMEA $PNORBT3/$PNORBT4 # Example: $PNORBT4,1.234,-1.234,1.234,23.4,12.34567,12.3*09 fields = ( ('Time (Trigger)', 'dt1', float), ('Time (NMEA)', 'dt2', float), ('Speed of Sound', 'sound_speed', float), ('Direction', 'dir', float), ('Figure of Merit', 'fom', float), ('Vertical Distance', 'dist', float), ) class NORBT7(NOR): # Bottom Track DF356/DF357 - NMEA $PNORBT6/$PNORBT7 # Example: $PNORBT7,1452244916.7508,1.234,-1.234,0.1234,0.1234,0.1234,12.34,23.45,23.45,23.45,23.45*39 fields = ( ('Ping Time', 'timestamp', lambda x: datetime.utcfromtimestamp(float(x))), ('Time (Trigger)', 'dt1', float), ('Time (NMEA)', 'dt2', float), ('Velocity X', 'vx', float), ('Velocity Y', 'vy', float), ('Velocity Z', 'vz', float), ('Figure of Merit', 'fom', float), ('Vertical Distance Beam 1', 'd1', float), ('Vertical Distance Beam 2', 'd2', float), ('Vertical Distance Beam 3', 'd3', float), ('Vertical Distance Beam 4', 'd4', float), ) class NORBT9(NOR): # Bottom Track DF358/DF359 - NMEA $PNORBT8/$PNORBT9 # Example: $PNORBT9,1452244916.7508,1.234,-1.234,0.1234,0.1234,0.1234,12.34,23.45,23.45,23.45,23.45,23.4,1567.8,1.2,12.3,0x000FFFFF*1E fields = ( ('Ping Time', 'timestamp', lambda x: datetime.utcfromtimestamp(float(x))), ('Time (Trigger)', 'dt1', float), ('Time (NMEA)', 'dt2', float), ('Velocity X', 'vx', float), ('Velocity Y', 'vy', float), ('Velocity Z', 'vz', float), ('Figure of Merit', 'fom', float), ('Vertical Distance Beam 1', 'd1', float), ('Vertical Distance Beam 2', 'd2', float), ('Vertical Distance Beam 3', 'd3', float), ('Vertical Distance Beam 4', 'd4', float), ('Battery Voltage', 'battery_voltage', float), ('Speed of Sound', 'sound_speed', float), ('Pressure', 'pressure', float), ('Temperature', 'temp', float), ('Status ', 'stat'), ) ################################################################## ## ## ## DVL Water Track ASCII formats ## ## ## ## Invalid estimates of Velocity are set to set to -32.768. ## ## Invalid estimates of Range are set to 0.0. ## ## Invalid estimates of FOM are set to 10.0 ## ################################################################## class NORWT4(NOR): # Water Track DF404/DF405 - NMEA $PNORWT3/$PNORWT4 # Example: $PNORWT4,1.2345,-1.2345,1.234,23.4,12.34,12.3*1C fields = ( ('Time Trigger ', 'dt1', float), ('Time NMEA', 'dt2', float), ('Speed of sound', 'sound_speed', float), ('Direction', 'dir', float), ('Figure of Merit', 'fom', float), ('Vertical Distance', 'dist', float), ) class NORWT7(NOR): # Water Track DF406/DF407 - NMEA $PNORWT6/$PNORWT7 # Example: $PNORWT7,1452244916.7508,1.234,-1.234,0.1234,0.1234,0.1234,12.34,23.45,23.45,23.45,23.45*2C fields = ( ('Ping Time', 'timestamp', lambda x: datetime.utcfromtimestamp(float(x))), ('Time (Trigger)', 'dt1', float), ('Time (NMEA)', 'dt2', float), ('Velocity X', 'vx', float), ('Velocity Y', 'vy', float), ('Velocity Z', 'vz', float), ('Figure of Merit', 'fom', float), ('Vertical Distance Beam 1', 'd1', float), ('Vertical Distance Beam 2', 'd2', float), ('Vertical Distance Beam 3', 'd3', float), ('Vertical Distance Beam 4', 'd4', float), ) class NORWT9(NOR): # Water Track DF408/DF409 - NMEA $PNORWT8/$PNORWT9 # Example: $PNORWT9,1452244916.7508,1.234,-1.234,0.1234,0.1234,0.1234,12.34,23.45,23.45,23.45,23.45,23.4,1567.8,1.2,12.3,0x000FFFFF*0B fields = ( ('Ping Time', 'timestamp', lambda x: datetime.utcfromtimestamp(float(x))), ('Time (Trigger)', 'dt1', float), ('Time (NMEA)', 'dt2', float), ('Velocity X', 'vx', float), ('Velocity Y', 'vy', float), ('Velocity Z', 'vz', float), ('Figure of Merit', 'fom', float), ('Vertical Distance Beam 1', 'd1', float), ('Vertical Distance Beam 2', 'd2', float), ('Vertical Distance Beam 3', 'd3', float), ('Vertical Distance Beam 4', 'd4', float), ('Battery Voltage', 'battery_voltage', float), ('Speed of Sound', 'sound_speed', float), ('Pressure', 'pressure', float), ('Temperature', 'temp', float), ('Status ', 'stat'), ) ################################################################## ## ## ## DVL Current Profile ASCII formats ## ## ## ################################################################## class NORI1(NOR): # Information Data DF101/DF102 - NMEA Format 1 and 2 # Example: $PNORI1,4,123456,3,30,1.00,5.00,BEAM*5B fields = ( ('Instrument type', 'it', int), ('Head ID', 'sn', int), ('Number of Beams', 'nb', int), ('Number of Cells', 'nc', int), ('Blanking Distance', 'bd', float), ('Cell Size', 'cs', float), ('Coordinate System', 'cy', str), ) class NORS1(NOR, nmea_utils.DatetimeFix): # Sensors Data DF101/DF102 - NMEA Format 1 and 2 # Example: $PNORS1,161109,132455,0,34000034,23.9,1500.0,123.4,0.02,45.6,0.02,23.4,0.02,123.456,0.02,24.56*51 fields = ( ('Date', 'datestamp', nmea_utils.datestamp), ('Time', 'timestamp', nmea_utils.timestamp), ('Error Code', 'ec', int), ('Status Code', 'sc'), ('Battery Voltage', 'battery_voltage', float), ('Speed of Sound', 'sound_speed', float), ('Heading', 'heading', float), ('Heading Std. Dev.', 'heading_std', float), ('Pitch', 'pitch', float), ('Pitch Std. Dev.', 'pitch_std', float), ('Roll', 'roll', float), ('Roll Std. Dev.', 'roll_std', float), ('Pressure', 'pressure', float), ('Pressure Std. Dev.', 'pressure_std', float), ('Temperature', 'temp', float), ) class NORS4(NOR, nmea_utils.DatetimeFix): # Sensors Data DF103/DF104 # Example: $PNORS4,23.6,1530.2,0.0,0.0,0.0,0.000,23.30*66 fields = ( ('Battery Voltage', 'battery_voltage', float), ('Speed of Sound', 'sound_speed', float), ('Heading', 'heading', float), ('Pitch', 'pitch', float), ('Roll', 'roll', float), ('Pressure', 'pressure', float), ('Temperature', 'temp', float), ) class NORC1(NOR, nmea_utils.DatetimeFix): # Current Data DF101/DF102 - NMEA Format 1 and 2 # Example: $PNORC1,083013,132455,3,11.0,0.332,0.332,0.332,78.9,78.9,78.9,78,78,78*46 fields = ( ('Date', 'datestamp', nmea_utils.datestamp), ('Time', 'timestamp', nmea_utils.timestamp), ('Cell Number', 'cn', int), ('Cell Position', 'cp', float), ('Velocity X', 'vx', float), ('Velocity Y', 'vy', float), ('Velocity Z', 'vz', float), ('Velocity Z2', 'vz2', float), ('Amplitude Beam 1', 'amp1', float), ('Amplitude Beam 2', 'amp2', float), ('Amplitude Beam 3', 'amp3', float), ('Amplitude Beam 4', 'amp4', float), ('Correlation Beam 1', 'r1', int), ('Correlation Beam 2', 'r2', int), ('Correlation Beam 3', 'r3', int), ('Correlation Beam 4', 'r4', int), ('Correlation Beam 4', 'r5', int), ) class NORC4(NOR, nmea_utils.DatetimeFix): # Current Data DF103/DF104 # Example: $PNORC4,1.5,1.395,227.1,32,32*7A fields = ( ('Cell Position', 'cp', float), ('Speed', 'sp', float), ('Direction', 'dir', float), ('Correlation', 'r', int), ('Amplitude', 'amp', int), ) class NORH4(NOR, nmea_utils.DatetimeFix): # Header Data DF103/DF104 # Example: $PNORH4,161109,143459,0,204C0002*38 fields = ( ('Date', 'datestamp', nmea_utils.datestamp), ('Time', 'timestamp', nmea_utils.timestamp), ('Error Code', 'ec', int), ('Status Code', 'sc'), ) pynmea2-1.18.0/pynmea2/types/proprietary/rdi.py000066400000000000000000000012261403520633500214050ustar00rootroot00000000000000''' Support for proprietary message(s) from RD Instruments ''' from ... import nmea class RDI(nmea.ProprietarySentence): ''' RD Instruments message. Only one sentence known? ''' sentence_types = {} def __new__(_cls, manufacturer, data): name = manufacturer + data[0] cls = _cls.sentence_types.get(name, _cls) return super(RDI, cls).__new__(cls) class RDID(RDI): ''' RD Instruments heading, pitch and roll data ''' fields = ( ('Subtype', 'subtype'), ("Pitch", "pitch", float), ("Roll", "roll", float), ("Heading", "heading", float) ) pynmea2-1.18.0/pynmea2/types/proprietary/srf.py000066400000000000000000000023401403520633500214170ustar00rootroot00000000000000# SiRF from ... import nmea class SRF(nmea.ProprietarySentence): sentence_types = {} def __new__(_cls, manufacturer, data): name = manufacturer + data[0] cls = _cls.sentence_types.get(name, _cls) return super(SRF, cls).__new__(cls) def __init__(self, manufacturer, data): self.sentence_type = manufacturer + data[0] super(SRF, self).__init__(manufacturer, data) class SRF103(SRF): fields = ( ("Subtype", "subtype"), ('Sentence type', 'sentence'), # 00=GGA # 01=GLL # 02=GSA # 03=GSV # 04=RMC # 05=VTG ('Command', 'command'), # 0=Set # 1=Query ('Rate', 'rate'), ('Checksum', 'checksum'), # 0=No, 1=Yes ) class SRF100(SRF): fields = ( ("Subtype", "subtype"), ('Protocol', 'protocol'), # 0 = SiRF Binary # 1 = NMEA ('Baud Rate', 'baud'), # 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200 ('Data bits', 'databits'), # 8 (, 7 in NMEA) ('Stop bits', 'stopbits'), # 0, 1 ('Parity', 'parity'), # 0, 1=Odd, 2=Even ) pynmea2-1.18.0/pynmea2/types/proprietary/sxn.py000066400000000000000000000071331403520633500214420ustar00rootroot00000000000000''' Seapath Message types: $PSXN,20,horiz-qual,hgt-qual,head-qual,rp-qual*csum term $PSXN,22,gyro-calib,gyro-offs*csum term $PSXN,23,roll,pitch,head,heave*csum term $PSXN,24,roll-rate,pitch-rate,yaw-rate,vertical-vel*csum term $PSXN,21,event*csum term Where: horiz-qual: Horizontal position and velocity quality: 0 = normal, 1 = reduced performance, 2= invalid data. hgt-qual: Height and vertical velocity quality: 0 = normal, 1 = reduced performance, 2 =invalid data. head-qual: Heading quality: 0 = normal, 1 = reduced performance, 2 = invalid data. rp-qual: Roll and pitch quality: 0 = normal, 1 = reduced performance, 2 = invalid data. gyro-calib: Gyro calibration value since system start-up in degrees on format d.dd. gyro-offs: Short-term gyro offset in degrees on format d.dd. roll: Roll in degrees on format d.dd. Positive with port side up. pitch: Pitch in degrees on format d.dd. Positive with bow up. heave: Heave in metres on format d.dd. Positive down. roll-rate: Roll rate in degrees per second on format d.dd. Positive when port side is moving upwards. pitch-rate: Pitch rate in degrees per second on format d.dd. Positive when bow is moving upwards. yaw-rate: Yaw rate in degrees per second on format d.dd. Positive when bow is moving towards starboard. vertical-vel: Vertical velocity in metres per second on format d.dd. Positive when moving downwards. event: Event code: 1 = system restart. csum: Checksum (exclusive or) of all characters between, but not including, the preceding $ and * , hexadecimal (00 - FF). term: CR-LF (2 bytes, values 13 and 10). Samples: $PSXN,20,0,0,0,0*3B $PSXN,23,0.30,-0.97,298.57,0.13*1B $PSXN,26,0,44.5000,0.7800,-0.9000,NRP*6D ''' from ... import nmea class SXN(nmea.ProprietarySentence): sentence_types = {} def __new__(_cls, manufacturer, data): name = manufacturer + data[1] cls = _cls.sentence_types.get(name, _cls) return super(SXN, cls).__new__(cls) class SXN20(SXN): fields = ( ('Blank', '_blank'), ('Message Type', 'message_type', int), ('Horizontal position and velocity quality', 'horiz_qual', int), ('Height and vertical velocity quality', 'hgt_qual', int), ('Heading quality', 'head_qual', int), ('Roll and pitch quality', 'rp_qual', int), ) class SXN21(SXN): fields = ( ('Blank', '_blank'), ('Message Type', 'message_type', int), ('Event code: 1 = system restart.', 'event', int), ) class SXN22(SXN): fields = ( ('Blank', '_blank'), ('Message Type', 'message_type', int), ('Gyro calibration value since system start-up in degrees', 'gyro_calib', float), ('Short-term gyro offset in degrees', 'gyro_ffs', float), ) class SXN23(SXN): fields = ( ('Blank', '_blank'), ('Message Type', 'message_type', int), ('Roll in degrees. Positive with port side up.', 'roll', float), ('Pitch in degrees. Positive with bow up.', 'pitch', float), ('Heading, degrees true (0.00 - 359.99).', 'head', float), ('Heave in metres. Positive down.', 'heave', float) ) class SXN24(SXN): fields = ( ('Blank', '_blank'), ('Message Type', 'message_type', int), ('Roll rate in degrees/second. Positive when port side is moving upwards.', 'roll_rate', float), ('Pitch rate in degrees/second. Positive when bow is moving upwards.', 'pitch_rate', float), ('Yaw rate in degrees/second. Positive when bow is moving towards starboard.', 'yaw_rate', float), ('Vertical velocity in metres/second. Positive when moving downwards.', 'vertical_vel', float) ) pynmea2-1.18.0/pynmea2/types/proprietary/tnl.py000066400000000000000000000065141403520633500214310ustar00rootroot00000000000000# -- TRIMBLE -- # # pylint: disable=wildcard-import,unused-wildcard-import from ... import nmea from ...nmea_utils import * """ Support for proprietary messages from BD9xx recievers. Documentation: www.trimble.com/OEM_ReceiverHelp/v4.85/en/ """ class TNL(nmea.ProprietarySentence): sentence_types = {} """ Generic Trimble Message """ def __new__(_cls, manufacturer, data): ''' Return the correct sentence type based on the first field ''' sentence_type = data[0] or data[1] name = manufacturer + sentence_type cls = _cls.sentence_types.get(name, _cls) return super(TNL, cls).__new__(cls) def __init__(self, manufacturer, data): self.sentence_type = data[0] or data[1] super(TNL, self).__init__(manufacturer, data) class TNLAVR(TNL): """ Trimble AVR Message """ fields = ( ('Empty', '_'), ('Sentence Type', 'type'), ('Timestamp', 'timestamp', timestamp), ('Yaw Angle', 'yaw_angle'), ('Yaw', 'yaw'), ('Tilt Angle', 'tilt_angle'), ('Tilt', 'tilt'), ('Roll Angle', 'roll_angle'), ('Roll', 'roll'), ('Baseline Range', 'baseline'), ('GPS Quality', 'gps_quality'), ('PDOP', 'pdop'), ('Total number of satelites in use', 'num_sats'), ) class TNLBPQ(TNL, LatLonFix, DatetimeFix): """ Trimble BPQ Message """ fields = ( ('Empty', '_'), ('Sentence Type', 'type'), ('Timestamp', 'timestamp', timestamp), ("Datestamp", "datestamp", datestamp), ("Latitude", "lat"), ("Latitude Direction", "lat_dir"), ("Longitude", "lon"), ("Longitude Direction", "lon_dir"), ('Height Ellipsoid', 'height'), ('Meters', 'meters'), ('Mode fix type', 'mode_fix_type'), ('Total number of satelites in use', 'num_sats'), ) class TNLGGK(TNL, LatLonFix, DatetimeFix): """ Trimble GGK Message """ fields = ( ('Empty', '_'), ('Sentence Type', 'type'), ('Timestamp', 'timestamp', timestamp), ("Datestamp", "datestamp", datestamp), ("Latitude", "lat"), ("Latitude Direction", "lat_dir"), ("Longitude", "lon"), ("Longitude Direction", "lon_dir"), ('GPS Quality', 'quality'), ('Total number of satelites in use', 'num_sats'), ('DOP', 'dop'), ('Height Ellipsoid', 'height'), ('Meters', 'meters'), ('Mode fix type', 'mode_fix_type'), ) class TNLVHD(TNL, DatetimeFix): """ Trimble VHD Message """ fields = ( ('Empty', '_'), ('Sentence Type', 'type'), ('Timestamp', 'timestamp', timestamp), ("Datestamp", "datestamp", datestamp), ('Azimuth Angle', 'azimuth'), ('AzimuthTime', 'azdt'), ('Vertical Angle', 'vertical'), ('VerticalTime', 'vertdt'), ('Range', 'range'), ('RangeTime', 'rdt'), ('GPS Quality', 'gps_quality'), ('Total number of satelites in use', 'num_sats'), ('PDOP', 'pdop'), ) class TNLPJT(TNL): """ Trimble PJT Message """ fields = ( ('Empty', '_'), ('Sentence Type', 'type'), ('Coordinate System', 'coord_name'), ('Project Name', 'project_name'), ) pynmea2-1.18.0/pynmea2/types/proprietary/ubx.py000066400000000000000000000041241403520633500214250ustar00rootroot00000000000000# pylint: disable=wildcard-import,unused-wildcard-import from decimal import Decimal from ... import nmea from ...nmea_utils import * # u-blox class UBX(nmea.ProprietarySentence): sentence_types = {} def __new__(_cls, manufacturer, data): name = manufacturer + data[1] cls = _cls.sentence_types.get(name, _cls) return super(UBX, cls).__new__(cls) class UBX00(UBX, LatLonFix): """ Lat/Long Position Data """ fields = ( ("Blank", "_blank"), ("UBX Type", "ubx_type"), ("Timestamp (UTC)", "timestamp", timestamp), ("Latitude", "lat"), ("Latitude Direction", "lat_dir"), ("Longitude", "lon"), ("Longitude Direction", "lon_dir"), ("Altitude above user datum ellipsoid", "alt_ref"), ("Navigation Status", "nav_stat"), ("Horizontal Accuracy Estimate", "h_acc"), ("Vertical Accuracy Estimate", "v_acc"), ("Speed over Ground", "sog"), ("Course over Ground", "cog"), ("Vertical Velocity", "v_vel"), ("Age of Differential Corrections", "diff_age"), ("Horizontal Dilution of Precision", "hdop"), ("Vertical Dilution of Precision", "vdop"), ("Time Dilution of Precision", "tdop"), ("Number of Satellites Used", "num_svs"), ("Reserved", "reserved") ) class UBX03(UBX): """ Satellite Status """ fields = ( ("Blank", "_blank"), ("UBX Type", "ubx_type"), ("Number of GNSS Satellites Tracked", "num_sv", int), ) @property def satellite_list(self): return self.data[1:] class UBX04(UBX): """ Time and Day Clock Information """ fields = ( ("Blank", "_blank"), ("UBX Type", "ubx_type"), ("UTC Time", "time", timestamp), ("UTC Date", "date", datestamp), ("UTC Time of Week", "utc_tow"), ("UTC Week Number", "utc_wk"), ("Leap Seconds", "leap_sec"), ("Receiver Clock Bias", "clk_bias", int), ("Receiver Clock Drift", "clk_drift", Decimal), ("Time Pulse Granularity", "tp_gran", int), ) pynmea2-1.18.0/pynmea2/types/proprietary/vtx.py000066400000000000000000000053741403520633500214600ustar00rootroot00000000000000# Vectronix Moskito TI (LRF) from decimal import Decimal from ... import nmea from ...nmea_utils import * class VTX(nmea.ProprietarySentence): sentence_types = {} def __new__(_cls, manufacturer, data): name = manufacturer + data[1] cls = _cls.sentence_types.get(name, _cls) return super(VTX, cls).__new__(cls) def __init__(self, manufacturer, data): self.sentence_type = manufacturer + data[0] super(VTX, self).__init__(manufacturer, data) class VTX0002(VTX): """ Vectronix measurement: laser distance and angles (degrees) with declination """ fields = ( ("Message Placeholder", "mplaceholder"), ("Subtype", "subtype"), ("Measurement ID", "measurement_id", int), ("Distance (meters)", "dist", float), ("Distance unit", "dist_unit"), ("Direction (degrees)", "direction", float), ("Direction unit", "direction_unit"), ("Vertical angle (degrees)", "va", float), ("Magnetic declination (degrees)", "decl", float), ("Magnetic declination ref (E/W)", "decl_ref") ) class VTX0000(VTX): """ Vectronix raw measurement: laser distance and angles (radians) without declination """ fields = ( ("Message Placeholder", "mplaceholder"), ("Subtype", "subtype"), ("Distance (meters)", "dist", float), ("Distance unit", "dist_unit"), ("Direction (radians)", "direction", float), ("Roll angle (radians)", "roll", float), ("Vertical angle (radians)", "va", float), ("Angular units type", "angle_units") ) class VTX0020(VTX, LatLonFix): """ Vectronix self location: lat, long, altitude """ fields = ( ("Message Placeholder", "mplaceholder"), ("Subtype", "subtype"), ("Measurement ID", "measurement_id", int), ('Latitude', 'lat'), ('Latitude Direction', 'lat_dir'), ('Longitude', 'lon'), ('Longitude Direction', 'lon_dir'), ('Altitude above WGS84 ellipsoid, meters', 'altitude', float), ('Altitude units', 'altitude_units') ) class VTX0012(VTX, LatLonFix): """ Vectronix target location: lat, long, altitude, gain """ fields = ( ("Message Placeholder", "mplaceholder"), ("Subtype", "subtype"), ("Measurement ID", "measurement_id", int), ('Latitude', 'lat'), ('Latitude Direction', 'lat_dir'), ('Longitude', 'lon'), ('Longitude Direction', 'lon_dir'), ('Altitude above WGS84 ellipsoid, meters', 'altitude', float), ('Altitude units', 'altitude_units'), ('Gain (meters)', 'gain', float), ('Gain units', 'gain_units') ) pynmea2-1.18.0/pynmea2/types/talker.py000066400000000000000000001065171403520633500175420ustar00rootroot00000000000000# pylint: disable=wildcard-import,unused-wildcard-import from ..nmea import TalkerSentence from ..nmea_utils import * from ..seatalk_utils import * from collections import namedtuple from decimal import Decimal #pylint: disable=missing-docstring #pylint: disable=no-init #pylint: disable=too-few-public-methods class AAM(TalkerSentence): """ Waypoint Arrival Alarm """ fields = ( ("Arrival Circle Entered", "arrival_circ_entered"), ("Perpendicular Passed", "perp_passed"), ("Circle Radius", "circle_rad"), ("Nautical Miles", "circle_rad_unit"), ("Waypoint ID", "waypoint_id"), ) class ALM(TalkerSentence): """ GPS Almanac data """ fields = ( ("Total number of messages", "total_num_msgs"), ("Message number", "msg_num"), ("Satellite PRN number", "sat_prn_num"), # 01 - 32 ("GPS week number", "gps_week_num"), # Week since Jan 6 1980 ("SV Health, bits 17-24 of each almanac page", "sv_health"), ("Eccentricity", "eccentricity"), ("Almanac Reference Time", "alamanac_ref_time"), ("Inclination Angle", "inc_angle"), ("Rate of right ascension", "rate_right_asc"), ("Root of semi-major axis", "root_semi_major_axis"), ("Argument of perigee", "arg_perigee"), ("Longitude of ascension node", "lat_asc_node"), ("Mean anomaly", "mean_anom"), ("F0 Clock parameter", "f0_clock_param"), ("F1 Clock parameter", "f1_clock_param"), ) class APA(TalkerSentence): """ Autopilot Sentence "A" """ fields = ( ("General Status", "status_gen"), ("Cycle lock Status", "status_cycle_lock"), ("Cross Track Error Magnitude", "cross_track_err_mag"), ("Direction to Steer (L or R)", "dir_steer"), ("Cross Track Units (Nautical Miles or KM)", "cross_track_unit"), ("Arrival Circle Entered", "arr_circle_entered"), # A = True ("Perpendicular passed at waypoint", "perp_passed"), # A = True ("Bearing origin to destination", "bearing_to_dest"), ("Bearing type", "bearing_type"), # M = Magnetic, T = True ("Destination waypoint ID", "dest_waypoint_id"), ) class APB(TalkerSentence): """ Autopilot Sentence "B" """ fields = ( ("General Status", "status_gen"), ("Cycle lock Status", "status_cycle_lock"), ("Cross Track Error Magnitude", "cross_track_err_mag"), ("Direction to Steer (L or R)", "dir_steer"), ("Cross Track Units (Nautical Miles or KM)", "cross_track_unit"), ("Arrival Circle Entered", "arr_circle_entered"), # A = True ("Perpendicular passed at waypoint", "perp_passed"), # A = True ("Bearing origin to destination", "bearing_to_dest"), ("Bearing type", "bearing_type"), # M = Magnetic, T = True ("Destination waypoint ID", "dest_waypoint_id"), ("Bearing, present position to dest", "bearing_pres_dest"), ("Bearing to destination, type", "bearing_pres_dest_type"), # M = Magnetic, T = True ("Heading to steer to destination", "heading_to_dest"), ("Heading to steer to destination type", "heading_to_dest_type"), ) # M = Magnetic, T = True class BEC(TalkerSentence): """ Bearing & Distance to Waypoint, Dead Reckoning """ fields = ( ("Timestamp", "timestamp", timestamp), ("Waypoint Latitude", "waypoint_lat"), ("Waypoint Latitude direction", "waypoint_lat_dir"), ("Waypoint Longitude", "waypoint_lon"), ("Waypoint Longitude direction", "waypoint_lon_dir"), ("Bearing, true", "bearing_true"), ("Bearing True symbol", "bearing_true_sym"), # T = true ("Bearing Magnetic", "bearing_mag"), ("Bearing Magnetic symbol", "bearing_mag_sym"), ("Nautical Miles", "nautical_miles"), ("Nautical Miles symbol", "nautical_miles_sym"), ("Waypoint ID", "waypoint_id"), ("FAA mode indicator", "faa_mode"), ) class BOD(TalkerSentence): # 045.,T,023.,M,DEST,START fields = ( ('Bearing True', 'bearing_t', Decimal), ('Bearing True Type', 'bearing_t_type'), ('Bearing Magnetic', 'bearing_mag', Decimal), ('Bearing Magnetic Type', 'bearing_mag_type'), ('Destination', 'dest'), ('Start', 'start'), ) class BWC(TalkerSentence): fields = ( ('Timestamp', 'timestamp', timestamp), ('Latitude of next Waypoint', 'lat_next'), ('Latitude of next Waypoint Direction', 'lat_next_direction'), ('Longitude of next Waypoint', 'lon_next'), ('Longitude of next Waypoint Direction', 'lon_next_direction'), ('True track to waypoint', 'true_track'), ('True Track Symbol', 'true_track_sym'), ('Magnetic track to waypoint', 'mag_track'), ('Magnetic Symbol', 'mag_sym'), ('Range to waypoint', 'range_next'), ('Unit of range', 'range_unit'), ('Waypoint Name', 'waypoint_name'), ) class BWR(TalkerSentence): fields = ( ('Timestamp', 'timestamp', timestamp), ('Latitude of next Waypoint', 'lat_next'), ('Latitude of next Waypoint Direction', 'lat_next_direction'), ('Longitude of next Waypoint', 'lon_next'), ('Longitude of next Waypoint Direction', 'lon_next_direction'), ('True track to waypoint', 'true_track'), ('True Track Symbol', 'true_track_sym'), ('Magnetic track to waypoint', 'mag_track'), ('Magnetic Symbol', 'mag_sym'), ('Range to waypoint', 'range_next'), ('Unit of range', 'range_unit'), ('Waypoint Name', 'waypoint_name'), ) class GGA(TalkerSentence, ValidGGAFix, LatLonFix): fields = ( ('Timestamp', 'timestamp', timestamp), ('Latitude', 'lat'), ('Latitude Direction', 'lat_dir'), ('Longitude', 'lon'), ('Longitude Direction', 'lon_dir'), ('GPS Quality Indicator', 'gps_qual', int), ('Number of Satellites in use', 'num_sats'), ('Horizontal Dilution of Precision', 'horizontal_dil'), ('Antenna Alt above sea level (mean)', 'altitude', float), ('Units of altitude (meters)', 'altitude_units'), ('Geoidal Separation', 'geo_sep'), ('Units of Geoidal Separation (meters)', 'geo_sep_units'), ('Age of Differential GPS Data (secs)', 'age_gps_data'), ('Differential Reference Station ID', 'ref_station_id'), ) class GNS(TalkerSentence, LatLonFix): fields = ( ('Timestamp', 'timestamp', timestamp), ('Latitude', 'lat'), ('Latitude Direction', 'lat_dir'), ('Longitude', 'lon'), ('Longitude Direction', 'lon_dir'), ('Mode indicator', 'mode_indicator'), ('Total number of satelites in use', 'num_sats'), ('HDROP', 'hdop'), ('Antenna altitude, meters', 'altitude'), ('Goeidal separation meters', 'geo_sep'), ('Age of diferential data', 'age_gps_data'), ('Differential reference station ID', 'diferential'), ) class GRS(TalkerSentence): """ Order of satellites will match those in the last GSA """ fields = ( ('Timestamp', 'timestamp', timestamp), ('Residuals mode', 'residuals_mode', int), ('SV 01 Residual (m)', 'sv_res_01', float), ('SV 02 Residual (m)', 'sv_res_02', float), ('SV 03 Residual (m)', 'sv_res_03', float), ('SV 04 Residual (m)', 'sv_res_04', float), ('SV 05 Residual (m)', 'sv_res_05', float), ('SV 06 Residual (m)', 'sv_res_06', float), ('SV 07 Residual (m)', 'sv_res_07', float), ('SV 08 Residual (m)', 'sv_res_08', float), ('SV 09 Residual (m)', 'sv_res_09', float), ('SV 10 Residual (m)', 'sv_res_10', float), ('SV 11 Residual (m)', 'sv_res_11', float), ('SV 12 Residual (m)', 'sv_res_12', float), ) class BWW(TalkerSentence): """ Bearing, Waypoint to Waypoint """ fields = ( ("Bearing degrees True", "bearing_deg_true"), ("Bearing degrees True Symbol", "bearing_deg_true_sym"), ("Bearing degrees Magnitude", "bearing_deg_mag"), ("Bearing degrees Magnitude Symbol", "bearing_deg_mag_sym"), ("Destination Waypoint ID", "waypoint_id_dest"), ("Origin Waypoint ID", "waypoint_id_orig"), ) class GLL(TalkerSentence, ValidStatusFix, LatLonFix): fields = ( ('Latitude', 'lat'), ('Latitude Direction', 'lat_dir'), ('Longitude', 'lon'), ('Longitude Direction', 'lon_dir'), ('Timestamp', 'timestamp', timestamp), ('Status', 'status'), # contains the 'A' or 'V' flag ("FAA mode indicator", "faa_mode"), ) class GSA(TalkerSentence, ValidGSAFix): fields = ( ('Mode', 'mode'), ('Mode fix type', 'mode_fix_type'), ('SV ID01', 'sv_id01'), ('SV ID02', 'sv_id02'), ('SV ID03', 'sv_id03'), ('SV ID04', 'sv_id04'), ('SV ID05', 'sv_id05'), ('SV ID06', 'sv_id06'), ('SV ID07', 'sv_id07'), ('SV ID08', 'sv_id08'), ('SV ID09', 'sv_id09'), ('SV ID10', 'sv_id10'), ('SV ID11', 'sv_id11'), ('SV ID12', 'sv_id12'), ('PDOP (Dilution of precision)', 'pdop'), ('HDOP (Horizontal DOP)', 'hdop'), ('VDOP (Vertical DOP)', 'vdop'), ) class GST(TalkerSentence): fields = ( ('UTC time of the GGA or GNS fix associated with this sentence.', 'timestamp', timestamp), ('RMS value of the standard deviation of the range inputs to the navigation process. Range inputs include preudoranges & DGNSS corrections.', 'rms', float), ('Standard deviation of semi-major axis of error ellipse (meters)', 'std_dev_major', float), ('Standard deviation of semi-minor axis of error ellipse (meters)', 'std_dev_minor', float), ('Orientation of semi-major axis of error ellipse (degrees from true north)', 'orientation', float), ('Standard deviation of latitude error (meters)', 'std_dev_latitude', float), ('Standard deviation of longitude error (meters)', 'std_dev_longitude', float), ('Standard deviation of altitude error (meters)', 'std_dev_altitude', float), ) class GSV(TalkerSentence): fields = ( ('Number of messages of type in cycle', 'num_messages'), ('Message Number', 'msg_num'), ('Total number of SVs in view', 'num_sv_in_view'), ('SV PRN number 1', 'sv_prn_num_1'), ('Elevation in degrees 1', 'elevation_deg_1'), # 90 max ('Azimuth, deg from true north 1', 'azimuth_1'), # 000 to 159 ('SNR 1', 'snr_1'), # 00-99 dB ('SV PRN number 2', 'sv_prn_num_2'), ('Elevation in degrees 2', 'elevation_deg_2'), # 90 max ('Azimuth, deg from true north 2', 'azimuth_2'), # 000 to 159 ('SNR 2', 'snr_2'), # 00-99 dB ('SV PRN number 3', 'sv_prn_num_3'), ('Elevation in degrees 3', 'elevation_deg_3'), # 90 max ('Azimuth, deg from true north 3', 'azimuth_3'), # 000 to 159 ('SNR 3', 'snr_3'), # 00-99 dB ('SV PRN number 4', 'sv_prn_num_4'), ('Elevation in degrees 4', 'elevation_deg_4'), # 90 max ('Azimuth, deg from true north 4', 'azimuth_4'), # 000 to 159 ('SNR 4', 'snr_4'), ) # 00-99 dB class HDG(TalkerSentence): """ NMEA 0183 standard Heading, Deviation and Variation Format: $HCHDG,<1>,<2>,<3>,<4>,<5>*hh <1> Magnetic sensor heading, degrees, to the nearest 0.1 degree. <2> Magnetic deviation, degrees east or west, to the nearest 0.1 degree. <3> E if field <2> is degrees East W if field <2> is degrees West <4> Magnetic variation, degrees east or west, to the nearest 0.1 degree. <5> E if field <4> is degrees East W if field <4> is degrees West """ fields = ( ("Heading", "heading", Decimal), ("Deviation", "deviation", Decimal), ("Deviation Direction", "dev_dir"), ("Variation", "variation", Decimal), ("Variation Direction", "var_dir"), ) class HDT(TalkerSentence): fields = ( ("Heading", "heading", Decimal), ("True", "hdg_true"), ) class RMA(TalkerSentence): fields = ( ("Data status", "data_status"), ("Latitude", "lat"), ("Latitude Direction", "lat_dir"), ("Longitude", "lon"), ("Longitude Direction", "lon_dir"), ("Not Used 1", "not_used_1"), ("Not Used 2", "not_used_2"), ("Speed over ground", "spd_over_grnd"), # Knots ("Course over ground", "crse_over_grnd"), ("Variation", "variation"), ("Variation Direction", "var_dir"), ) class RMB(TalkerSentence, ValidStatusFix): """ Recommended Minimum Navigation Information """ fields = ( ('Status', 'status'), # contains the 'A' or 'V' flag ("Cross Track Error", "cross_track_error"), # nautical miles, 9.9 max ("Cross Track Error, direction to corrent", "cte_correction_dir"), ("Origin Waypoint ID", "origin_waypoint_id"), ("Destination Waypoint ID", "dest_waypoint_id"), ("Destination Waypoint Latitude", "dest_lat"), ("Destination Waypoint Lat Direction", "dest_lat_dir"), ("Destination Waypoint Longitude", "dest_lon"), ("Destination Waypoint Lon Direction", "dest_lon_dir"), ("Range to Destination", "dest_range"), # Nautical Miles ("True Bearing to Destination", "dest_true_bearing"), ("Velocity Towards Destination", "dest_velocity"), # Knots ("Arrival Alarm", "arrival_alarm"), ) # A = Arrived, V = Not arrived class RMC(TalkerSentence, ValidStatusFix, LatLonFix, DatetimeFix): """ Recommended Minimum Specific GPS/TRANSIT Data """ fields = ( ("Timestamp", "timestamp", timestamp), ('Status', 'status'), # contains the 'A' or 'V' flag ("Latitude", "lat"), ("Latitude Direction", "lat_dir"), ("Longitude", "lon"), ("Longitude Direction", "lon_dir"), ("Speed Over Ground", "spd_over_grnd", float), ("True Course", "true_course", float), ("Datestamp", "datestamp", datestamp), ("Magnetic Variation", "mag_variation"), ("Magnetic Variation Direction", "mag_var_dir"), ) class RTE(TalkerSentence): """ Routes """ fields = ( ("Number of sentences in sequence", "num_in_seq"), ("Sentence Number", "sen_num"), ("Start Type", "start_type"), # The first in the list is either current route or waypoint ("Name or Number of Active Route", "active_route_id"), ) @property def waypoint_list(self): return self.data[4:] @waypoint_list.setter def waypoint_list(self, val): self.data[4:] = val class R00(TalkerSentence): fields = () @property def waypoint_list(self): return self.data[:] @waypoint_list.setter def waypoint_list(self, val): self.data[:] = val class STN(TalkerSentence): """ NOTE: No real data could be found for examples of the actual spec so it is a guess that there may be a checksum on the end """ fields = ( ("Talker ID Number", "talker_id_num"), ) # 00 - 99 class TRF(TalkerSentence): """ Transit Fix Data """ fields = ( ("Timestamp (UTC)", "timestamp", timestamp), ("Date (DD/MM/YY", "date"), ("Latitude", "lat"), ("Latitude Direction", "lat_dir"), ("Longitude", "lon"), ("Longitude Direction", "lon_dir"), ("Elevation Angle", "ele_angle"), ("Number of Iterations", "num_iterations"), ("Number of Doppler Intervals", "num_doppler_intervals"), ("Update Distance", "update_dist"), # Nautical Miles ("Satellite ID", "sat_id"), ) class TXT(TalkerSentence): """ Text Transmission """ fields = ( ("Number of Messages", "num_msg"), ("Message Number", "msg_num"), ("Type of Message", "msg_type"), ("Text", "text"), ) class VBW(TalkerSentence, ValidVBWFix): """ Dual Ground/Water Speed """ fields = ( ("Longitudinal Water Speed", "lon_water_spd", Decimal), # Knots ("Transverse Water Speed", "trans_water_spd", Decimal), # Knots ("Water Speed Data Validity", "data_validity_water_spd"), ("Longitudinal Ground Speed", "lon_grnd_spd", Decimal), # Knots ("Transverse Ground Speed", "trans_grnd_spd", Decimal), # Knots ("Ground Speed Data Validity", "data_validity_grnd_spd"), ) class VTG(TalkerSentence): """ Track Made Good and Ground Speed """ fields = ( ("True Track made good", "true_track", float), ("True Track made good symbol", "true_track_sym"), ("Magnetic Track made good", "mag_track", Decimal), ("Magnetic Track symbol", "mag_track_sym"), ("Speed over ground knots", "spd_over_grnd_kts", Decimal), ("Speed over ground symbol", "spd_over_grnd_kts_sym"), ("Speed over ground kmph", "spd_over_grnd_kmph", float), ("Speed over ground kmph symbol", "spd_over_grnd_kmph_sym"), ("FAA mode indicator", "faa_mode"), ) class WCV(TalkerSentence): """ Waypoint Closure Velocity """ fields = ( ("Velocity", "velocity"), ("Velocity Units", "vel_units"), # Knots ("Waypoint ID", "waypoint_id"), ) class WNC(TalkerSentence): """ Distance, Waypoint to Waypoint """ fields = ( ("Distance, Nautical Miles", "dist_nautical_miles"), ("Distance Nautical Miles Unit", "dist_naut_unit"), ("Distance, Kilometers", "dist_km"), ("Distance, Kilometers Unit", "dist_km_unit"), ("Origin Waypoint ID", "waypoint_origin_id"), ("Destination Waypoint ID", "waypoint_dest_id"), ) class WPL(TalkerSentence): """ Waypoint Location """ fields = ( ("Latitude", "lat"), ("Latitude Direction", "lat_dir"), ("Longitude", "lon"), ("Longitude Direction", "lon_dir"), ("Waypoint ID", "waypoint_id"), ) class XTE(TalkerSentence): """ Cross-Track Error, Measured """ fields = ( ("General Warning Flag", "warning_flag"), ("Lock flag (Not Used)", "lock_flag"), ("Cross Track Error Distance", "cross_track_err_dist"), ("Correction Direction (L or R)", "correction_dir"), ("Distance Units", "dist_units"), ) class ZDA(TalkerSentence): fields = ( ("Timestamp", "timestamp", timestamp), # hhmmss.ss = UTC ("Day", "day", int), # 01 to 31 ("Month", "month", int), # 01 to 12 ("Year", "year", int), # Year = YYYY ("Local Zone Description", "local_zone", int), # 00 to +/- 13 hours ("Local Zone Minutes Description", "local_zone_minutes", int), # same sign as hours ) @property def datestamp(self): return datetime.date(year=self.year, month=self.month, day=self.day) @property def tzinfo(self): return TZInfo(self.local_zone, self.local_zone_minutes) @property def datetime(self): d = datetime.datetime.combine(self.datestamp, self.timestamp) return d.replace(tzinfo=self.tzinfo) # Implemented by Janez Stupar for Visionect class RSA(TalkerSentence): """ Rudder Sensor Angle """ fields = ( ("Starboard rudder sensor", "rsa_starboard", Decimal), ("Starboard rudder sensor status", "rsa_starboard_status"), ("Port rudder sensor", "rsa_port", Decimal), ("Port rudder sensor status", "rsa_port_status"), ) class HSC(TalkerSentence): """ Heading Steering Command """ fields = ( ("Heading", "heading_true", Decimal), ("True", "true"), ("Heading Magnetic", "heading_magnetic", Decimal), ("Magnetic", "magnetic"), ) class MWD(TalkerSentence): """ Wind Direction NMEA 0183 standard Wind Direction and Speed, with respect to north. """ fields = ( ("Wind direction true", "direction_true", Decimal), ("True", "true"), ("Wind direction magnetic", "direction_magnetic", Decimal), ("Magnetic", "magnetic"), ("Wind speed knots", "wind_speed_knots", Decimal), ("Knots", "knots"), ("Wind speed meters/second", "wind_speed_meters", Decimal), ("Wind speed", "meters"), ) class MWV(TalkerSentence, ValidStatusFix): """ Wind Speed and Angle NMEA 0183 standard Wind Speed and Angle, in relation to the vessel's bow/centerline. """ fields = ( ("Wind angle", "wind_angle", Decimal), # in relation to vessel's centerline ("Reference", "reference"), # relative (R)/true(T) ("Wind speed", "wind_speed", Decimal), ("Wind speed units", "wind_speed_units"), # K/M/N ("Status", "status"), ) # DBT - Depth below trasducer, depth referenced to the transducer # Used by simrad devices (f.e. EK500) class DBT(TalkerSentence): """ Depth Below Transducer """ fields = ( ("Depth below surface, feet", "depth_feet", Decimal), ("Feet", "unit_feet"), ("Depth below surface, meters", "depth_meters", Decimal), ("Meters", "unit_meters"), ("Depth below surface, fathoms", "depth_fathoms", Decimal), ("fathoms", "unit_fathoms"), ) class HDM(TalkerSentence): """ Heading, Magnetic """ fields = ( ("Heading degrees", "heading", Decimal), ("Magnetic", "magnetic"), ) class MTW(TalkerSentence): """ Water Temperature """ fields = ( ('Water temperature', 'temperature', Decimal), ('Unit of measurement', 'units'), ) class VHW(TalkerSentence): """ Water Speed and Heading """ fields = ( ('Heading true degrees', 'heading_true', Decimal), ('heading true', 'true'), ('Heading Magnetic degrees', 'heading_magnetic', Decimal), ('Magnetic', 'magnetic'), ('Water speed knots', 'water_speed_knots', Decimal), ('Knots', 'knots'), ('Water speed kilometers', 'water_speed_km', Decimal), ('Kilometers', 'kilometers'), ) class VLW(TalkerSentence): """ Distance Traveled through the Water """ fields = ( ('Water trip distance', 'trip_distance', Decimal), ('Trip distance nautical miles', 'trip_distance_miles'), ('Water trip distance since reset', 'trip_distance_reset', Decimal), ('Trip distance nautical miles since reset', 'trip_distance_reset_miles'), ) # --------------------- Implemented by Joachim Bakke (joabakk)---------------- # # ---------------------------------------------------------------------------- # class ROT(TalkerSentence, ValidStatusFix): """ Rate of Turn """ fields = ( ("Rate of turn", "rate_of_turn"), #- indicates bow turn to port ('Status', 'status'), # contains the 'A' or 'B' flag ) class RPM(TalkerSentence, ValidStatusFix): """ Revolutions """ # 1 2 3 4 5 6 # | | | | | | # $--RPM,a,x,x.x,x.x,A*hh # Field Number: # 1) Sourse, S = Shaft, E = Engine # 2) Engine or shaft number # 3) Speed, Revolutions per minute # 4) Propeller pitch, % of maximum, "-" means astern # 5) Status, A means data is valid # 6) Checksum fields = ( ("Source", "source"), #S = Shaft, E = Engine ("Engine or shaft number", "engine_no", int), ("Speed", "speed", float), #RPM ("Propeller pitch", "pitch"), #- means astern ("Status", "status"), #A means valid ) class VPW(TalkerSentence): """ Speed, Measured Parallel to Wind """ fields = ( ("Speed knots", "speed_kn", float),#- means downwind ("Unit knots", "unit_knots"),#N means knots ("Speed m/s", "speed_ms", float), ("Unit m/s", "unit_ms"),#M means m/s ) # VPW - Speed - Measured Parallel to Wind # 1 2 3 4 5 # | | | | | #$--VPW,x.x,N,x.x,M*hh # Field Number: # 1) Speed, "-" means downwind # 2) N = Knots # 3) Speed, "-" means downwind # 4) M = Meters per second # 5) Checksum class VDR(TalkerSentence): fields = ( ("Degrees True", "deg_t", float), ("TRUE", "true"),#T means true ("Degrees Magnetic", "deg_m", float), ("Magnetic", "magnetic"),#M means magnetic ("Speed of Current", "current", float), ("Unit", "unit_kn"), #N means knots ) # VDR - Set and Drift # 1 2 3 4 5 6 7 # | | | | | | | # $--VDR,x.x,T,x.x,M,x.x,N*hh # Field Number: # 1) Degress True # 2) T = True # 3) Degrees Magnetic # 4) M = Magnetic # 5) Knots (speed of current) # 6) N = Knots # 7) Checksum class VWR(TalkerSentence): fields = ( ("Degrees Rel", "deg_r", float), ("Left/Right", "l_r"),#R means right ("Wind speed kn", "wind_speed_kn", float), ("Knots", "unit_knots"),#N means knots ("Wind Speed m/s", "wind_speed_ms", float), ("m/s", "unit_ms"),#M means m/s ("Wind Speed Km/h", "wind_speed_km", float), ("Knots", "unit_km"), #K means Km ) # TODO # getters/setters that normalize units, # apply L/R sign, and sync all fields # when setting the speed #VWR - Relative Wind Speed and Angle # 1 2 3 4 5 6 7 8 9 # | | | | | | | | | # $--VWR,x.x,a,x.x,N,x.x,M,x.x,K*hh # Field Number: # 1) Wind direction magnitude in degrees # 2) Wind direction Left/Right of bow # 3) Speed # 4) N = Knots # 5) Speed # 6) M = Meters Per Second # 7) Speed # 8) K = Kilometers Per Hour # 9) Checksum Transducer = namedtuple("Transducer", [ "type", "value", "units", "id", ]) class XDR(TalkerSentence): fields = ( ("Transducer type", "type"), ("Transducer data value", "value"), ("Transducer data units", "units"), ("Transducer ID", "id"), ) @property def num_transducers(self): return len(self.data) // 4 def get_transducer(self, i): return Transducer(*self.data[i*4:i*4+4]) # ------------------------- Implemented by Casper Vertregt ------------------- # # ---------------------------------------------------------------------------- # class OSD(TalkerSentence, ValidStatusFix): """ Own Ship Data """ fields = ( ("True Heading", "heading", Decimal), ("Status", "status"), # A / V ("Vessel Course true degrees", "course", Decimal), ("Course True", "course_true"), # T / R (True / Relative) ("Vessel Speed", "speed", Decimal), ("Speed Reference", "speed_ref"), ("Vessel Set true degrees", "set", Decimal), ("Vessel Drift(speed)", "drift", Decimal), ("Speed Units", "speed_unit"), ) class TLL(TalkerSentence, LatLonFix): """ Target Latitude & Longitude """ fields = ( ("Target Number", "target_number", int), ("Target Latitude", "lat"), ("Latitude Direction", "lat_dir"), ("Target Longitude", "lon"), ("Longitude Direction", "lon_dir"), ("Target Name", "target_name"), ("Timestamp (UTC)", "timestamp", timestamp), ("Target Status", "target_status"), ("Reference Target", "reference"), ) class TTM(TalkerSentence): """ Tracked Target Message """ fields = ( ("Target Number", "target_number", int), ("Target Distance", "distance", Decimal), ("Bearing from Own Ship", "bearing", Decimal), ("Bearing Reference", "brg_ref"), # T / R (True / Relative) ("Target Speed", "speed", Decimal), ("Target Course over Ground", "cog", Decimal), ("Course Units", "cog_unit"), # T / R (True / Relative) ("Distance of CPA", "dist_cpa", Decimal), ("Time until CPA", "time_cpa", Decimal), ("Distance Units", "dist_unit"), # K / N / S (Kilometers / Knots / Statute miles) ("Target Name", "name"), ("Target Status", "status"), # L / Q / T (Lost from tracking process / Query - in process of acquisition / Tracking at the present time) ("Target Reference", "reference"), # R, null otherwise ("Timestamp (UTC)", "timestamp", timestamp), ("Acquisition Type", "acquisition"), # A / M (Automatic / Manual) ) # ---------------------------------- Not Yet Implemented --------------------- # # ---------------------------------------------------------------------------- # #class FSI(TalkerSentence): # """ Frequency Set Information # """ # fields = ( # ) #class GLC(TalkerSentence): # """ Geographic Position, Loran-C # """ # fields = ( # ) #class GXA(TalkerSentence): # """ TRANSIT Position # """ # fields = ( # ) #class LCD(TalkerSentence): # """ Loran-C Signal Data # """ # fields = ( # ) #class MTA(TalkerSentence): # """ Air Temperature (to be phased out) # """ # fields = ( # ) #class OLN(TalkerSentence): # """ Omega Lane Numbers # """ # fields = ( # ) #class RSD(TalkerSentence): # """ RADAR System Data # """ # fields = ( # ) #class SFI(TalkerSentence): # """ Scanning Frequency Information # """ # fields = ( # ) #class XTR(TalkerSentence): # """ Cross-Track Error, Dead Reckoning # """ # fields = ( # ) #class ZFO(TalkerSentence): # """ UTC & Time from Origin Waypoint # """ # fields = ( # ) #class ZTG(TalkerSentence): # """ UTC & Time to Destination Waypoint # """ # fields = ( # ) # ---------------------------------------------------------------------------- # # -------------------------- Unknown Formats --------------------------------- # # ---------------------------------------------------------------------------- # #class ASD(TalkerSentence): # """ Auto-pilot system data (Unknown format) # """ # fields = ( # ) # ---------------------------------------------------------------------------- # # -------------------------- Obsolete Formats -------------------------------- # # ---------------------------------------------------------------------------- # #class DCN(TalkerSentence): # """ Decca Position (obsolete) # """ # fields = ( # ) # ------------- Implemented by Rocco De Marco (CNR/ISMAR ITALY) -------------- # # ---------------------------------------------------------------------------- # # DTM - NMEA 0183 standard Datum Reference class DTM(TalkerSentence): fields = ( ('Local datum', 'datum'), ('Subdivision datum', 'subd_datum'), ('Latitude', 'lat'), ('Latitude Direction', 'lat_dir'), ('Longitude', 'lon'), ('Longitude Direction', 'lon_dir'), ('Signed altitude', 'altitude'), ('Datum code', 'datum_code'), ) # Examples: $GPDTM,W84,,0.0,N,0.0,E,0.0,W84*6F # $GPDTM,999,CH95,0.08,N,0.07,E,-47.7,W84*1C # MDA - NMEA 0183 standard Meteorological Composite # Used by Airmar PB 150 weather station class MDA(TalkerSentence): # Example: # $WIMDA,30.2269,I,1.0236,B,17.7,C,,,43.3,,5.0,C,131.5,T,128.6,M,0.8,N,0.4,M*54 fields = ( ('Barometric pressure, inches of mercury', 'b_pressure_inch', Decimal), ('Inches', 'inches'), # I = Inches ('Barometric pressure, bars', 'b_pressure_bar', Decimal), ('Bars', 'bars'), # B = bars ('Air temperature, degrees C', 'air_temp', Decimal), ('Celsius', 'a_celsius'), # C = Celsius ('Water temperature, degrees C', 'water_temp', Decimal), ('Celsius', 'w_celsius'), # C = Celsius ('Relative humidity, percent', 'rel_humidity', Decimal), ('Absolute humidity, percent', 'abs_humidity', Decimal), ('Dew point, degrees C', 'dew_point', Decimal), ('Celsius', 'd_celsius'), # C = Celsius ('Wind direction true', 'direction_true', Decimal), ('True', 'true'), # T = True ('Wind direction magnetic', 'direction_magnetic', Decimal), ('Magnetic', 'magnetic'), # M = Magnetic ('Wind speed knots', 'wind_speed_knots', Decimal), ('Knots', 'knots'), # N = Knots ('Wind speed meters/second', 'wind_speed_meters', Decimal), ('Meters', 'meters'), # M = Meters/second ) # VWT - NMEA 0183 True wind angle in relation to the vessel's heading, and true wind # speed referenced to the water. class VWT(TalkerSentence): fields = ( ('Wind angle relative to the vessel', 'wind_angle_vessel', Decimal), ('Direction, L=Left, R=Right, relative to the vessel head', 'direction'), ('Wind speed knots', 'wind_speed_knots', Decimal), ('Knots', 'knots'), # N = Knots ('Wind speed meters/second', 'wind_speed_meters', Decimal), ('Meters', 'meters'), # M = Meters/second ('Wind speed km/h', 'wind_speed_km', Decimal), ('Km', 'km'), # K = km/h ) # DBS - Depth below surface # Used by simrad devices (f.e. EK500) # Deprecated and replaced by DPT class DBS(TalkerSentence): fields = ( ('Depth below surface, feet', 'depth_feet', Decimal), ('Feets', 'feets'), ('Depth below surface, meters', 'depth_meter', Decimal), ('Meters', 'meters'), ('Depth below surface, fathoms', 'depth_ fathoms', Decimal), ('Fathoms', 'fathoms'), ) # DPT - water depth relative to the transducer and offset of the measuring # transducer # Used by simrad devices (f.e. EK500) class DPT(TalkerSentence): fields = ( ('Water depth, in meters', 'depth', Decimal), ('Offset from the trasducer, in meters', 'offset', Decimal), ('Maximum range scale in use', 'range', Decimal), ) #GBS - GPS Satellite Fault Detection #used by devices such as [MX521] [NV08C-CSM] #description retreived from: "https://www.xj3.nl/download/99/NMEA.txt" class GBS(TalkerSentence): fields = ( ('Timestamp','timestamp', timestamp), ('Expected error in latitude', 'lat_err'), ('Expected error in longitude', 'lon_err'), ('Expected error in altitude', 'alt_err'), ('PRN of most likely failed satellite','sat_prn_num_f'), ('Probability of missed detection for most likely failed satellite','pro_miss', Decimal), ('Estimate of bias in meters on most likely failed satellite','est_bias'), ('Standard deviation of bias estimate','est_bias_dev'), ) # STALK Message - # Used by gadgetpool Seatalk to nmea devices class ALK(TalkerSentence,SeaTalk): fields = ( ("Command", "cmd"), ("Data Byte 1", "data_byte1"), ("Data Byte 2", "data_byte2"), ("Data Byte 3", "data_byte3"), ("Data Byte 4", "data_byte4"), ("Data Byte 5", "data_byte5"), ("Data Byte 6", "data_byte6"), ("Data Byte 7", "data_byte7"), ("Data Byte 8", "data_byte8"), ("Data Byte 9", "data_byte9") )pynmea2-1.18.0/setup.py000066400000000000000000000025141403520633500146710ustar00rootroot00000000000000from setuptools import setup import imp _version = imp.load_source("pynmea2._version", "pynmea2/_version.py") setup( name='pynmea2', version=_version.__version__, author='Tom Flanagan', author_email='tom@zkpq.ca', license='MIT', url='https://github.com/Knio/pynmea2', description='Python library for the NMEA 0183 protcol', packages=['pynmea2','pynmea2.types','pynmea2.types.proprietary'], keywords='python nmea gps parse parsing nmea0183 0183', classifiers=[ 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Scientific/Engineering :: GIS', 'Topic :: Software Development :: Libraries :: Python Modules', ] ) pynmea2-1.18.0/test/000077500000000000000000000000001403520633500141345ustar00rootroot00000000000000pynmea2-1.18.0/test/test_ash.py000066400000000000000000000032441403520633500163230ustar00rootroot00000000000000import datetime import pynmea2 def test_ashrltn(): data = '$PASHR,LTN,3*3D' msg = pynmea2.parse(data) assert type(msg) == pynmea2.ash.ASHRLTN assert msg.manufacturer == 'ASH' assert msg.subtype == 'LTN' assert msg.latency == 3 assert msg.render() == data def test_ashratt(): data = '$PASHR,130533.620,0.311,T,-80.467,-1.395,,0.066,0.067,0.215,2,3*0B' msg = pynmea2.parse(data) assert type(msg) == pynmea2.ash.ASHRATT assert msg.data == ['R', '130533.620', '0.311', 'T', '-80.467', '-1.395', '', '0.066', '0.067', '0.215', '2', '3'] assert msg.manufacturer == 'ASH' assert msg.timestamp == datetime.time(13, 5, 33, 620000) assert msg.true_heading == 0.311 assert msg.is_true_heading == 'T' assert msg.roll == -80.467 assert msg.pitch == -1.395 assert msg.roll_accuracy == 0.066 assert msg.pitch_accuracy == 0.067 assert msg.heading_accuracy == 0.215 assert msg.aiding_status == 2 assert msg.imu_status == 3 assert msg.render() == data def test_ashratt_with_2_vs_3_decimal_timestamp(): msg_3 = pynmea2.parse('$PASHR,130533.620,0.311,T,-80.467,-1.395,,0.066,0.067,0.215,2,3*0B') msg_2 = pynmea2.parse('$PASHR,130533.62,0.311,T,-80.467,-1.395,,0.066,0.067,0.215,2,3*3B') assert msg_3.timestamp == msg_2.timestamp def test_ash_undefined(): ''' Test that non-ATT messages still fall back to the generic ASH type ''' data = '$PASHR,XYZ,123' msg = pynmea2.parse(data) assert type(msg) == pynmea2.ash.ASH assert msg.manufacturer == 'ASH' # assert msg.sentence_type == 'XYZ' assert msg.data == ['R', 'XYZ', '123'] assert msg.render(checksum=False) == data pynmea2-1.18.0/test/test_file.py000066400000000000000000000031411403520633500164630ustar00rootroot00000000000000try: from StringIO import StringIO except ImportError: from io import StringIO import pynmea2 TEST_DATA = """$GPRMC,181031.576,V,3926.276,N,07739.361,W,99.7,18.30,250915,,E*79 $GPGGA,181032.576,3926.276,N,07739.361,W,0,00,,,M,,M,,*5F $GPGLL,3926.276,N,07739.361,W,181033.576,V*3E $GPRMC,181034.576,V,3949.797,N,07809.854,W,18.8,34.66,250915,,E*75 $GPGGA,181035.576,3949.797,N,07809.854,W,0,00,,,M,,M,,*5A $GPGLL,3949.797,N,07809.854,W,181036.576,V*39 $GPRMC,181037.576,V,4040.018,N,07808.022,W,32.9,16.43,250915,,E*77 $GPGGA,181038.576,4040.018,N,07808.022,W,0,00,,,M,,M,,*58 $GPGLL,4040.018,N,07808.022,W,181039.576,V*39 $GPRMC,181040.576,V,4133.618,N,07725.034,W,96.8,44.47,250915,,E*7F""" def test_file(): nmeafile = pynmea2.NMEAFile(StringIO(TEST_DATA)) nmea_strings = nmeafile.read() assert len(nmea_strings) == 10 assert all([isinstance(s, pynmea2.NMEASentence) for s in nmea_strings]) del nmeafile with pynmea2.NMEAFile(StringIO(TEST_DATA)) as _f: nmea_strings = [_f.readline() for i in range(10)] assert len(nmea_strings) == 10 assert all([isinstance(s, pynmea2.NMEASentence) for s in nmea_strings]) with pynmea2.NMEAFile(StringIO(TEST_DATA)) as _f: nmea_strings = [s for s in _f] assert len(nmea_strings) == 10 assert all([isinstance(s, pynmea2.NMEASentence) for s in nmea_strings]) with pynmea2.NMEAFile(StringIO(TEST_DATA)) as _f: nmea_strings = [_f.next() for i in range(10)] assert len(nmea_strings) == 10 assert all([isinstance(s, pynmea2.NMEASentence) for s in nmea_strings]) if __name__ == '__main__': test_file() pynmea2-1.18.0/test/test_nor.py000066400000000000000000000201141403520633500163410ustar00rootroot00000000000000import datetime import pynmea2 def test_norbt0(): data = '$PNORBT0,1,040721,131335.3341,23.961,-48.122,-32.76800,10.00000,0.00,0x00000000*48' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORBT0 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORBT0' assert msg.beam == 1 assert msg.datestamp == datetime.date(2021, 7, 4) assert msg.timestamp == datetime.time(13, 13, 35, 334100) assert msg.dt1 == 23.961 assert msg.dt2 == -48.122 assert msg.bv == -32.76800 assert msg.fom == 10.00000 assert msg.dist == 0.00 assert msg.stat == '0x00000000' assert msg.render() == data def test_norbt4(): data = '$PNORBT4,1.234,-1.234,1.234,23.4,12.34567,12.3*3D' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORBT4 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORBT4' assert msg.dt1 == 1.234 assert msg.dt2 == -1.234 assert msg.sound_speed == 1.234 assert msg.dir == 23.4 assert msg.fom == 12.34567 assert msg.dist == 12.3 assert msg.render() == data def test_norbt7(): data = '$PNORBT7,1452244916.7508,1.234,-1.234,0.1234,0.1234,0.1234,12.34,23.45,23.45,23.45,23.45*39' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORBT7 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORBT7' assert msg.timestamp == datetime.datetime(2016, 1, 8, 9, 21, 56, 750800) assert msg.dt1 == 1.234 assert msg.dt2 == -1.234 assert msg.vx == 0.1234 assert msg.vy == 0.1234 assert msg.vz == 0.1234 assert msg.fom == 12.34 assert msg.d1 == 23.45 assert msg.d2 == 23.45 assert msg.d3 == 23.45 assert msg.d4 == 23.45 assert msg.render() == data def test_norbt9(): data = '$PNORBT9,1452244916.7508,1.234,-1.234,0.1234,0.1234,0.1234,12.34,23.45,23.45,23.45,23.45,23.4,1567.8,1.2,12.3,0x000FFFFF*1E' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORBT9 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORBT9' assert msg.timestamp == datetime.datetime(2016, 1, 8, 9, 21, 56, 750800) assert msg.dt1 == 1.234 assert msg.dt2 == -1.234 assert msg.vx == 0.1234 assert msg.vy == 0.1234 assert msg.vz == 0.1234 assert msg.fom == 12.34 assert msg.d1 == 23.45 assert msg.d2 == 23.45 assert msg.d3 == 23.45 assert msg.d4 == 23.45 assert msg.battery_voltage == 23.4 assert msg.sound_speed == 1567.8 assert msg.pressure == 1.2 assert msg.temp == 12.3 assert msg.stat == '0x000FFFFF' assert msg.render() == data def test_norwt4(): data = '$PNORWT4,1.2345,-1.2345,1.234,23.4,12.34,12.3*1C' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORWT4 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORWT4' assert msg.dt1 == 1.2345 assert msg.dt2 == -1.2345 assert msg.sound_speed == 1.234 assert msg.dir == 23.4 assert msg.fom == 12.34 assert msg.dist == 12.3 assert msg.render() == data def test_norwt7(): data = '$PNORWT7,1452244916.7508,1.234,-1.234,0.1234,0.1234,0.1234,12.34,23.45,23.45,23.45,23.45*2C' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORWT7 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORWT7' assert msg.timestamp == datetime.datetime(2016, 1, 8, 9, 21, 56, 750800) assert msg.dt1 == 1.234 assert msg.dt2 == -1.234 assert msg.vx == 0.1234 assert msg.vy == 0.1234 assert msg.vz == 0.1234 assert msg.fom == 12.34 assert msg.d1 == 23.45 assert msg.d2 == 23.45 assert msg.d3 == 23.45 assert msg.d4 == 23.45 assert msg.render() == data def test_norwt9(): data = '$PNORWT9,1452244916.7508,1.234,-1.234,0.1234,0.1234,0.1234,12.34,23.45,23.45,23.45,23.45,23.4,1567.8,1.2,12.3,0x000FFFFF*0B' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORWT9 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORWT9' assert msg.timestamp == datetime.datetime(2016, 1, 8, 9, 21, 56, 750800) assert msg.dt1 == 1.234 assert msg.dt2 == -1.234 assert msg.vx == 0.1234 assert msg.vy == 0.1234 assert msg.vz == 0.1234 assert msg.fom == 12.34 assert msg.d1 == 23.45 assert msg.d2 == 23.45 assert msg.d3 == 23.45 assert msg.d4 == 23.45 assert msg.battery_voltage == 23.4 assert msg.sound_speed == 1567.8 assert msg.pressure == 1.2 assert msg.temp == 12.3 assert msg.stat == '0x000FFFFF' assert msg.render() == data def test_nori1(): data = '$PNORI1,4,123456,3,30,1.00,5.00,BEAM*5B' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORI1 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORI1' assert msg.it == 4 assert msg.sn == 123456 assert msg.nb == 3 assert msg.nc == 30 assert msg.bd == 1.00 assert msg.cs == 5.00 assert msg.cy == 'BEAM' assert msg.render() == data def test_nors1(): data = '$PNORS1,161109,132455,0,34000034,23.9,1500.0,123.4,0.02,45.6,0.02,23.4,0.02,123.456,0.02,24.56*51' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORS1 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORS1' assert msg.datestamp == datetime.date(2009, 11, 16) assert msg.timestamp == datetime.time(13, 24, 55) assert msg.ec == 0 assert msg.sc == '34000034' assert msg.battery_voltage == 23.9 assert msg.sound_speed == 1500.0 assert msg.heading == 123.4 assert msg.heading_std == 0.02 assert msg.pitch == 45.6 assert msg.pitch_std == 0.02 assert msg.roll == 23.4 assert msg.roll_std == 0.02 assert msg.pressure == 123.456 assert msg.pressure_std == 0.02 assert msg.temp == 24.56 assert msg.render() == data def test_nors4(): data = '$PNORS4,23.6,1530.2,0.0,0.0,0.0,0.000,23.30*66' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORS4 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORS4' assert msg.battery_voltage == 23.6 assert msg.sound_speed == 1530.2 assert msg.heading == 0.0 assert msg.pitch == 0.0 assert msg.roll == 0.0 assert msg.pressure == 0.0 assert msg.temp == 23.30 assert msg.render() == data def test_norc1(): data = '$PNORC1,161109,132455,3,11.0,0.332,0.332,0.332,0.332,78.9,78.9,78.9,78.9,78,78,78,78*56' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORC1 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORC1' assert msg.datetime == datetime.datetime(2009, 11, 16, 13, 24, 55) assert msg.cn == 3 assert msg.cp == 11.0 assert msg.vx == 0.332 assert msg.vy == 0.332 assert msg.vz == 0.332 assert msg.vz2 == 0.332 assert msg.amp1 == 78.9 assert msg.amp2 == 78.9 assert msg.amp3 == 78.9 assert msg.amp4 == 78.9 assert msg.r1 == 78 assert msg.r2 == 78 assert msg.r3 == 78 assert msg.r4 == 78 assert msg.render() == data def test_norc4(): data = '$PNORC4,1.5,1.395,227.1,32,32*7A' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORC4 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORC4' assert msg.cp == 1.5 assert msg.sp == 1.395 assert msg.dir == 227.1 assert msg.r == 32 assert msg.amp == 32 assert msg.render() == data def test_norh4(): data = '$PNORH4,161109,143459,0,204C0002*38' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NORH4 assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORH4' assert msg.datestamp == datetime.date(2009, 11, 16) assert msg.timestamp == datetime.time(14, 34, 59) assert msg.ec == 0 assert msg.sc == '204C0002' assert msg.render() == data def test_nor_undefined(): ''' Test that non-NOR messages still fall back to the generic NOR type ''' data = '$PNORTT3,XYZ,123' msg = pynmea2.parse(data) assert type(msg) == pynmea2.nor.NOR assert msg.manufacturer == 'NOR' assert msg.sentence_type == 'NORTT3' assert msg.data == ['XYZ', '123'] assert msg.render(checksum=False) == data pynmea2-1.18.0/test/test_proprietary.py000066400000000000000000000213301403520633500201240ustar00rootroot00000000000000import pynmea2 import datetime def test_proprietary_1(): # A sample proprietary sentence from a LCJ Capteurs # anemometer. data = "$PLCJ,5F01,66FC,AA,9390,6373" msg = pynmea2.parse(data) assert msg.manufacturer == "LCJ" assert msg.data == ['','5F01','66FC','AA','9390','6373'] assert msg.render(checksum=False) == data def test_proprietary_2(): # A sample proprietary sentence from a LCJ Capteurs anemometer. # Note: This sample is the main reason why we can't assume # anything about the content of the proprietary sentences # due to the lack of a comma after the manufacturer ID and # extra comma at the end. data = "$PLCJE81B8,64A0,2800,2162,0E," msg = pynmea2.parse(data) assert msg.manufacturer == 'LCJ' assert msg.data == ['E81B8', '64A0', '2800', '2162', '0E', ''] assert repr(msg) == "" assert msg.render(checksum=False) == data def test_proprietary_3(): # A sample proprietary sentence from a Magellan device # (via ). data = "$PMGNST,02.12,3,T,534,05.0,+03327,00*40" msg = pynmea2.parse(data) assert msg.manufacturer == 'MGN' assert msg.data == ['ST','02.12','3','T','534','05.0','+03327','00'] assert msg.render() == data def test_extra_comma(): # extra comma after name data = "$PTNL,AVR,212604.30,+52.1800,Yaw,,,-0.0807,Roll,12.579,3,1.4,16*21" msg = pynmea2.parse(data) assert msg.manufacturer == 'TNL' assert msg.data == ['', 'AVR','212604.30','+52.1800','Yaw','','','-0.0807','Roll','12.579','3','1.4','16'] assert msg.render() == data def test_proprietary_type(): class ABC(pynmea2.ProprietarySentence): fields = ( ('Empty', '_'), ('First', 'a'), ('Second', 'b'), ) data = '$PABC,1,2*13' msg = pynmea2.parse(data) assert isinstance(msg, ABC) assert msg.manufacturer == 'ABC' assert msg.a == '1' assert msg.b == '2' assert repr(msg) == "" assert str(msg) == data def test_proprietary_with_comma(): # class with no extra comma class TNLDG(pynmea2.tnl.TNL): fields = () # raise Exception(TNL.sentence_types) # raise Exception(pynmea2.ProprietarySentence.sentence_types) data = "$PTNLDG,44.0,33.0,287.0,100,0,4,1,0,,,*3E" msg = pynmea2.parse(data) assert isinstance(msg, TNLDG) assert msg.data == ['DG', '44.0', '33.0', '287.0', '100', '0', '4', '1', '0', '', '', ''] assert str(msg) == data # type with extra comma data = '$PTNL,PJT,NAD83(Conus),CaliforniaZone 4 0404*51' msg = pynmea2.parse(data) assert type(msg) == pynmea2.tnl.TNLPJT assert msg.manufacturer == 'TNL' assert msg.sentence_type == 'PJT' assert msg.coord_name == 'NAD83(Conus)' assert msg.project_name == 'CaliforniaZone 4 0404' assert str(msg) == data def test_srf(): # implemented sentence data = '$PSRF100,0,1200,8,1,1' msg = pynmea2.parse(data) assert type(msg) == pynmea2.srf.SRF100 assert msg.sentence_type == 'SRF100' assert msg.protocol == '0' assert msg.baud == '1200' assert msg.databits == '8' assert msg.stopbits == '1' assert msg.parity == '1' # unimplemented sentence data = '$PSRF999,0,1200,8,1,1' msg = pynmea2.parse(data) assert type(msg) == pynmea2.srf.SRF assert msg.render(checksum=False) == data def test_grm(): data = '$PGRME,15.0,M,45.0,M,25.0,M*1C' msg = pynmea2.parse(data) assert type(msg) == pynmea2.grm.GRME assert msg.sentence_type == 'GRME' assert msg.hpe == 15.0 assert msg.hpe_unit == 'M' assert msg.vpe == 45.0 assert msg.vpe_unit == 'M' assert msg.osepe == 25.0 assert msg.osepe_unit == 'M' assert msg.render() == data def test_tnl(): data = '$PTNL,BPQ,224445.06,021207,3723.09383914,N,12200.32620132,W,EHT-5.923,M,5*60' msg = pynmea2.parse(data) assert type(msg) == pynmea2.tnl.TNLBPQ assert msg.datestamp == datetime.date(2007,12,2) assert msg.latitude == 37.384897319 assert msg.longitude == -122.00543668866666 assert msg.render() == data def test_ubx00(): data = '$PUBX,00,074440.00,4703.74203,N,00736.82976,E,576.991,D3,2.0,2.0,0.091,0.00,-0.032,,0.76,1.05,0.65,14,0,0*70' msg = pynmea2.parse(data) assert type(msg) == pynmea2.ubx.UBX00 assert msg.identifier() == 'PUBX' assert msg.ubx_type == '00' assert msg.timestamp == datetime.time(7, 44, 40) assert msg.latitude == 47.06236716666667 assert msg.lat_dir == 'N' assert msg.render() == data def test_ubx03(): data = '$PUBX,03,20,3,e,281,72,36,062,5,e,034,10,23,000,8,U,328,11,44,064,9,-,323,-2,,000,13,-,341,01,,000,16,U,307,45,49,064,18,e,144,18,,000,21,U,150,74,35,037,25,e,134,06,,000,27,U,271,20,52,064,29,U,074,36,36,063,31,U,209,26,37,040,120,e,210,31,,000,126,-,157,33,,000,66,U,036,19,34,015,67,e,090,20,22,000,68,-,136,00,,000,73,e,273,60,47,064,74,U,330,24,44,064,80,U,193,36,36,023*33' msg = pynmea2.parse(data) assert type(msg) == pynmea2.ubx.UBX03 assert msg.num_sv == 20 assert msg.render() == data def test_ubx04(): data = '$PUBX,04,073824.00,131014,113903.99,1814,16,495176,342.504,21*18' msg = pynmea2.parse(data) assert type(msg) == pynmea2.ubx.UBX04 assert msg.date == datetime.date(2014, 10, 13) assert msg.time == datetime.time(7, 38, 24) assert msg.clk_bias == 495176 assert msg.render() == data def test_create(): sentence = pynmea2.srf.SRF100('SRF', [ '100', '%d' % 1, '%d' % 9600, '%d' % 7, '%d' % 1, '%d' % 0]) data = sentence.render(checksum=True, dollar=True, newline=False) assert data == '$PSRF100,1,9600,7,1,0*02' def test_unknown_sentence(): data = 'PZZZABC,1,2,3' msg = pynmea2.parse(data) assert type(msg) == pynmea2.ProprietarySentence assert msg.manufacturer == 'ZZZ' assert msg.data == ['ABC', '1', '2', '3'] assert msg.render(checksum=False, dollar=False) == data def test_proprietary_VTX_0002(): # A sample proprietary sentence from a Vectronix device (laser distance) data = "$PVTX,0002,181330,00005.22,M,262.518,T,-01.967,09.358,W*7E" msg = pynmea2.parse(data) assert msg.manufacturer == 'VTX' assert msg.dist == 5.22 assert msg.direction == 262.518 assert msg.va == -1.967 assert msg.render() == data def test_proprietary_VTX_0012(): # A sample proprietary sentence from a Vectronix device (target position) data = "$PVTX,0012,177750,3348.5861,N,10048.5861,W,00045.2,M,038.8,M*22" msg = pynmea2.parse(data) assert msg.manufacturer == 'VTX' assert msg.latitude == 33.80976833333333 assert msg.longitude == -100.80976833333334 assert msg.altitude == 45.2 assert msg.gain == 38.8 assert msg.render() == data def test_proprietary_GRMW(): # A sample proprietary Garmin Waypoint sentence, generated by DIREWOLF data = "$PGRMW,AC7FD-1,,000A,AC7FD local DIGI U=12.5V|T=23.9C*1A" msg = pynmea2.parse(data) assert msg.manufacturer == 'GRM' assert msg.wname == 'AC7FD-1' assert msg.altitude == None assert msg.symbol == '000A' assert msg.comment == 'AC7FD local DIGI U=12.5V|T=23.9C' def test_proprietary_MGNWPL(): # A sample proprietary Magellan Waypoint sentence, generated by DIREWOLF data = "$PMGNWPL,4531.7900,N,12253.4800,W,,M,AC7FD-1,AC7FD local DIGI U=12.5V|T=23.9C,c*46" msg = pynmea2.parse(data) assert msg.manufacturer == 'MGN' assert msg.lat =='4531.7900' assert msg.lat_dir == 'N' assert msg.lon == '12253.4800' assert msg.lon_dir == 'W' assert msg.altitude == None assert msg.altitude_unit == 'M' assert msg.wname == 'AC7FD-1' assert msg.comment == 'AC7FD local DIGI U=12.5V|T=23.9C' assert msg.icon == 'c' assert msg.latitude == 45.529833333333336 assert msg.longitude == -122.89133333333334 def test_KWDWPL(): # A sample proprietary Kenwood Waypoint sentence, generated by DIREWOLF data = "$PKWDWPL,053125,V,4531.7900,N,12253.4800,W,,,200320,,AC7FD-1,/-*10" msg = pynmea2.parse(data) assert msg.manufacturer == "KWD" assert msg.timestamp == datetime.time(5, 31, 25) assert msg.status == 'V' assert msg.is_valid == False assert msg.lat == '4531.7900' assert msg.lat_dir == 'N' assert msg.lon == '12253.4800' assert msg.lon_dir == 'W' assert msg.sog == None assert msg.cog == None assert msg.datestamp == datetime.date(2020, 3, 20) assert msg.datetime == datetime.datetime(2020, 3, 20, 5, 31, 25) assert msg.altitude == None assert msg.wname == 'AC7FD-1' assert msg.ts == '/-' assert msg.latitude == 45.529833333333336 assert msg.longitude == -122.89133333333334 pynmea2-1.18.0/test/test_pynmea.py000066400000000000000000000107251403520633500170430ustar00rootroot00000000000000import pytest import pynmea2 data = "$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D" def test_version(): version = '1.18.0' assert pynmea2.version == version assert pynmea2.__version__ == version def test_sentence(): msg = pynmea2.parse(data) assert msg.talker == 'GP' assert msg.sentence_type == 'GGA' assert str(msg) == data def test_checksum(): d = data[:-2] + '00' with pytest.raises(pynmea2.ChecksumError): msg = pynmea2.parse(d) def test_attribute(): msg = pynmea2.parse(data) with pytest.raises(AttributeError): msg.foobar def test_fail(): with pytest.raises(pynmea2.ParseError): pynmea2.parse('FOOBAR') with pytest.raises(pynmea2.SentenceTypeError): pynmea2.parse('$GPABC,1,2,3') def test_mixin(): msg = pynmea2.parse(data) assert msg.latitude == -19.484083333333334 assert msg.longitude == 24.175100000000000 assert msg.latitude_minutes == 29.045000000000073 assert msg.longitude_minutes == 10.506000000000085 assert msg.latitude_seconds == 2.6999999999970896 assert msg.longitude_seconds == 30.360000000000582 def test_missing(): msg = pynmea2.parse("$GPVTG,108.53,T,,M,0.04,N,0.07,K,A*31") assert msg.mag_track == None def test_missing_2(): # $GPGSV,3,1,09,12,28,063,33,14,63,000,32,22,68,150,26,25,40,109,23*7B # $GPGSV,3,2,09,31,42,227,19,32,17,313,20,01,09,316,,11,08,292,*73 # $GPGSV,3,3,09,24,03,046,*47 msg = pynmea2.parse('$GPGSV,3,3,09,24,03,046,*47') assert msg.snr_4 == '' def test_missing_3(): data = '$GPVTG,,T,,M,0.00,N*1B' msg = pynmea2.parse(data) assert None == msg.spd_over_grnd_kmph assert msg.render() == data def test_missing_4(): data = '$GPVTG,,T,,M,0.00,N*1B' msg = pynmea2.parse(data) assert None == msg.spd_over_grnd_kmph assert msg.render() == data def test_dollar(): data = 'GPGSV,3,3,09,24,03,046,*47\r\n' msg = pynmea2.parse(data) assert msg.render(dollar=False, newline=True) == data def test_whitespace(): data = ' GPGSV,3,3,09,24,03,046,*47 \r\n ' msg = pynmea2.parse(data) assert msg.render(dollar=False) == data.strip() def test_nmea_util(): assert pynmea2.nmea_utils.dm_to_sd('0') == 0. assert pynmea2.nmea_utils.dm_to_sd('12108.1') == 121.135 def test_missing_latlon(): data = '$GPGGA,201716.684,,,,,0,00,,,M,0.0,M,,0000*5F' msg = pynmea2.parse(data) print(msg) assert msg.latitude == 0. def test_query(): data = 'CCGPQ,GGA' msg = pynmea2.parse(data) assert isinstance(msg, pynmea2.QuerySentence) assert msg.talker == 'CC' assert msg.listener == 'GP' assert msg.sentence_type == 'GGA' msg = pynmea2.QuerySentence('CC', 'GP', 'GGA') assert msg.render() == '$CCGPQ,GGA*2B' def test_slash(): with pytest.raises(pynmea2.nmea.ParseError): msg = pynmea2.parse('$GPGSV,3,3,09,24,03,046,*47\\') def test_timestamp(): t = pynmea2.nmea_utils.timestamp("115919") assert t.hour == 11 assert t.minute == 59 assert t.second == 19 assert t.microsecond == 0 assert pynmea2.nmea_utils.timestamp('115919.1' ).microsecond == 100000 assert pynmea2.nmea_utils.timestamp('115919.12' ).microsecond == 120000 assert pynmea2.nmea_utils.timestamp('115919.123' ).microsecond == 123000 assert pynmea2.nmea_utils.timestamp('115919.1234' ).microsecond == 123400 assert pynmea2.nmea_utils.timestamp('115919.12345' ).microsecond == 123450 assert pynmea2.nmea_utils.timestamp('115919.123456' ).microsecond == 123456 assert pynmea2.nmea_utils.timestamp('115919.1234567').microsecond == 123456 def test_corrupt_message(): # data is corrupt starting here ------------------------------v data = '$GPRMC,172142.00,A,4805.30256324,N,11629.09084774,W,0.D' # fails with strict parsing with pytest.raises(pynmea2.ChecksumError): msg = pynmea2.parse(data, check=True) # lazy parsing succeeds msg = pynmea2.parse(data, check=False) assert isinstance(msg, pynmea2.types.RMC) # corrupt data assert msg.spd_over_grnd == '0.D' # missing data assert msg.true_course == None # renders unchanged assert msg.render(checksum=False) == data # # ^o^ # |\ ship it! # | \ / # /| \ # __/_|___\_ # ~~\________/~~ # ~~~~~~~~~~~~ pynmea2-1.18.0/test/test_rdi.py000066400000000000000000000005151403520633500163240ustar00rootroot00000000000000import pynmea2 def test_rdid(): data = '$PRDID,-1.31,7.81,47.31*68' msg = pynmea2.parse(data) assert type(msg) == pynmea2.rdi.RDID assert msg.manufacturer == 'RDI' assert msg.subtype == 'D' assert msg.pitch == -1.31 assert msg.roll == 7.81 assert msg.heading == 47.31 assert msg.render() == data pynmea2-1.18.0/test/test_stream.py000066400000000000000000000035451403520633500170470ustar00rootroot00000000000000import pytest try: from StringIO import StringIO except ImportError: from io import StringIO import pynmea2 DATA = "$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D\n" def test_stream(): sr = pynmea2.NMEAStreamReader() assert len(list(sr.next(''))) == 0 assert len(list(sr.next(DATA))) == 1 assert len(list(sr.next(DATA))) == 1 sr = pynmea2.NMEAStreamReader() assert len(list(sr.next(DATA))) == 1 assert len(list(sr.next(DATA[:10]))) == 0 assert len(list(sr.next(DATA[10:]))) == 1 sr = pynmea2.NMEAStreamReader() assert list(sr.next()) == [] f = StringIO(DATA * 2) sr = pynmea2.NMEAStreamReader(f) assert len(list(sr.next())) == 1 assert len(list(sr.next())) == 1 assert len(list(sr.next())) == 0 def test_iter(): sr = pynmea2.NMEAStreamReader(StringIO(DATA)) for batch in sr: for msg in batch: assert isinstance(msg, pynmea2.GGA) break break def test_raise_errors(): sr = pynmea2.NMEAStreamReader(errors='raise') assert list(sr.next('foobar')) == [] with pytest.raises(pynmea2.ParseError): assert list(sr.next('foo\n')) def test_yield_errors(): sr = pynmea2.NMEAStreamReader(errors='yield') assert list(sr.next('foobar')) == [] data = list(sr.next('foo\n' + DATA)) assert len(data) == 2 assert isinstance(data[0], pynmea2.ParseError) assert isinstance(data[1], pynmea2.GGA) def test_ignore_errors(): sr = pynmea2.NMEAStreamReader(errors='ignore') assert list(sr.next('foobar')) == [] data = list(sr.next('foo\n' + DATA)) assert len(data) == 1 assert isinstance(data[0], pynmea2.GGA) def test_bad_error_value(): with pytest.raises(ValueError): sr = pynmea2.NMEAStreamReader(errors='bad') pynmea2-1.18.0/test/test_sxn.py000066400000000000000000000012331403520633500163540ustar00rootroot00000000000000import pynmea2 def test_sxn20(): data = '$PSXN,20,0,0,0,0*3B' msg = pynmea2.parse(data) assert isinstance(msg, pynmea2.types.sxn.SXN20) assert msg.message_type == 20 assert msg.horiz_qual == 0 assert msg.hgt_qual == 0 assert msg.head_qual == 0 assert msg.rp_qual == 0 assert msg.render() == data def test_sxn23(): data = '$PSXN,23,0.30,-0.97,298.57,0.13*1B' msg = pynmea2.parse(data) assert isinstance(msg, pynmea2.types.sxn.SXN23) assert msg.message_type == 23 assert msg.roll == 0.30 assert msg.pitch == -0.97 assert msg.head == 298.57 assert msg.heave == 0.13 assert msg.render() == data pynmea2-1.18.0/test/test_types.py000066400000000000000000000164401403520633500167160ustar00rootroot00000000000000import pytest import pynmea2 import datetime from decimal import Decimal def test_GGA(): data = "$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,100.00,M,-33.9,M,,0000*6D" msg = pynmea2.parse(data) assert msg.talker == 'GP' assert msg.sentence_type == 'GGA' assert isinstance(msg, pynmea2.GGA) # Timestamp assert msg.timestamp == datetime.time(18, 43, 53, 70000) # Latitude assert msg.lat == '1929.045' # Latitude Direction assert msg.lat_dir == 'S' # Longitude assert msg.lon == '02410.506' # Longitude Direction assert msg.lon_dir == 'E' # GPS Quality Indicator assert msg.gps_qual == 1 # Number of Satellites in use assert msg.num_sats == '04' # Horizontal Dilution of Precision assert msg.horizontal_dil == '2.6' # Antenna Alt above sea level (mean) assert msg.altitude == 100.0 # Units of altitude (meters) assert msg.altitude_units == 'M' # Geoidal Separation assert msg.geo_sep == '-33.9' # Units of Geoidal Separation (meters) assert msg.geo_sep_units == 'M' # Age of Differential GPS Data (secs) assert msg.age_gps_data == '' # Differential Reference Station ID assert msg.ref_station_id == '0000' assert msg.is_valid == True msg.altitude = 200.0 assert str(msg) == "$GPGGA,184353.07,1929.045,S,02410.506,E,1,04,2.6,200.0,M,-33.9,M,,0000*5E" def test_RTE(): data = "$GPRTE,2,1,c,0,PBRCPK,PBRTO,PTELGR,PPLAND,PYAMBU,PPFAIR,PWARRN,PMORTL,PLISMR*73" msg = pynmea2.parse(data) assert msg.talker == 'GP' assert msg.sentence_type == 'RTE' assert "2" == msg.num_in_seq assert "1" == msg.sen_num assert "c" == msg.start_type assert "0" == msg.active_route_id assert msg.waypoint_list == [ "PBRCPK", "PBRTO", "PTELGR", "PPLAND", "PYAMBU", "PPFAIR", "PWARRN", "PMORTL", "PLISMR"] msg.waypoint_list = ['ABC', 'DEF'] assert str(msg) == "$GPRTE,2,1,c,0,ABC,DEF*03" def test_R00(): data = "$GPR00,A,B,C*29" msg = pynmea2.parse(data) assert msg.talker == 'GP' assert msg.sentence_type == 'R00' assert msg.waypoint_list == ['A', 'B', 'C'] msg.waypoint_list = ['ABC', 'DEF'] assert str(msg) == "$GPR00,ABC,DEF*42" def test_MWV(): data = "$IIMWV,271.0,R,000.2,N,A*3B" msg = pynmea2.parse(data) assert msg.talker == 'II' assert msg.sentence_type == 'MWV' # Wind angle in degrees assert msg.wind_angle == Decimal('271.0') # Reference type assert msg.reference == 'R' # Wind speed assert msg.wind_speed == Decimal('0.2') # Wind speed units assert msg.wind_speed_units == 'N' # Device status assert msg.status == 'A' assert msg.render() == data def test_GST(): data = "$GPGST,172814.0,0.006,0.023,0.020,273.6,0.023,0.020,0.031*6A" msg = pynmea2.parse(data) assert isinstance(msg, pynmea2.GST) assert msg.timestamp == datetime.time(hour=17, minute=28, second=14) assert msg.rms == 0.006 assert msg.std_dev_major == 0.023 assert msg.std_dev_minor == 0.020 assert msg.orientation == 273.6 assert msg.std_dev_latitude == 0.023 assert msg.std_dev_longitude == 0.020 assert msg.std_dev_altitude == 0.031 assert msg.render() == data def test_RMC(): data = '''$GPRMC,225446,A,4916.45,N,12311.12,W,000.5,054.7,191194,020.3,E*68''' msg = pynmea2.parse(data) assert isinstance(msg, pynmea2.RMC) assert msg.timestamp == datetime.time(hour=22, minute=54, second=46) assert msg.datestamp == datetime.date(1994, 11, 19) assert msg.latitude == 49.274166666666666 assert msg.longitude == -123.18533333333333 assert msg.datetime == datetime.datetime(1994, 11, 19, 22, 54, 46) assert msg.is_valid == True assert msg.render() == data def test_TXT(): data = '$GNTXT,01,01,02,ROM BASE 2.01 (75331) Oct 29 2013 13:28:17*44' msg = pynmea2.parse(data) assert type(msg) == pynmea2.talker.TXT assert msg.text == 'ROM BASE 2.01 (75331) Oct 29 2013 13:28:17' def test_ZDA(): data = '''$GPZDA,010203.05,06,07,2008,-08,30''' msg = pynmea2.parse(data) assert isinstance(msg, pynmea2.ZDA) assert msg.timestamp == datetime.time(hour=1, minute=2, second=3, microsecond=50000) assert msg.day == 6 assert msg.month == 7 assert msg.year == 2008 assert msg.local_zone == -8 assert msg.local_zone_minutes == 30 assert msg.datestamp == datetime.date(2008, 7, 6) assert msg.datetime == datetime.datetime(2008, 7, 6, 1, 2, 3, 50000, msg.tzinfo) def test_VPW(): data = "$XXVPW,1.2,N,3.4,M" msg = pynmea2.parse(data) assert isinstance(msg, pynmea2.VPW) assert msg.talker == 'XX' assert msg.speed_kn == 1.2 assert msg.unit_knots == 'N' assert msg.speed_ms == 3.4 assert msg.unit_ms == 'M' def test_BOD(): data = "XXBOD,045.,T,023.,M,DEST,START" msg = pynmea2.parse(data) assert isinstance(msg, pynmea2.BOD) assert msg.talker == 'XX' def test_XDR(): data = "$YXXDR,A,-64.437,M,N,A,054.454,D,E,C,17.09,C,T-N1052*46" msg = pynmea2.parse(data) assert isinstance(msg, pynmea2.XDR) assert msg.talker == 'YX' assert msg.type == 'A' assert msg.value == '-64.437' assert msg.units == 'M' assert msg.id == 'N' assert msg.num_transducers == 3 t0 = msg.get_transducer(0) assert t0.type == 'A' assert t0.value == '-64.437' assert t0.units == 'M' assert t0.id == 'N' t1 = msg.get_transducer(1) assert t1.type == 'A' assert t1.value == '054.454' assert t1.units == 'D' assert t1.id == 'E' t2 = msg.get_transducer(2) assert t2.type == 'C' assert t2.value == '17.09' assert t2.units == 'C' assert t2.id == 'T-N1052' def test_GLL(): data = "$GPGLL,4916.45,N,12311.12,W,225444,A,*1D" msg = pynmea2.parse(data) assert msg.is_valid == True assert msg.render() == data def test_GSA(): data = "$GPGSA,A,3,02,,,07,,09,24,26,,,,,1.6,1.6,1.0*3D" msg = pynmea2.parse(data) assert msg.is_valid == True assert msg.render() == data def test_VBW(): data = "XXVBW,1.2,3.4,A,5.6,7.8,A" msg = pynmea2.parse(data) assert msg.is_valid == True assert msg.render(checksum=False, dollar=False) == data def test_STALK(): data = "$STALK,9C,C1,2A,E5*4A" msg = pynmea2.parse(data) assert msg.render() == data assert msg.command_name == 'Compass heading and Rudder position' def test_STALK_unidentified_command(): data = "$STALK,AA,C1,2A,E5*30" msg = pynmea2.parse(data) assert msg.render() == data assert msg.command_name == 'Unknown Command' def test_GRS(): data = "$GNGRS,162047.00,1,0.6,0.1,-16.6,-0.8,-0.1,0.5,,,,,,*41" msg = pynmea2.parse(data) assert msg.render() == data assert msg.talker == 'GN' assert msg.sentence_type == 'GRS' assert msg.residuals_mode == 1 assert msg.sv_res_01 == 0.6 assert msg.sv_res_02 == 0.1 assert msg.sv_res_03 == -16.6 assert msg.sv_res_04 == -0.8 assert msg.sv_res_05 == -0.1 assert msg.sv_res_06 == 0.5 assert msg.sv_res_07 == None