pax_global_header00006660000000000000000000000064125420107440014511gustar00rootroot0000000000000052 comment=b949681af69b45f0f7f4bb53b6770037b5b02178 rtimulib-7.2.1/000077500000000000000000000000001254201074400133475ustar00rootroot00000000000000rtimulib-7.2.1/.gitignore000066400000000000000000000007771254201074400153520ustar00rootroot00000000000000Makefile* *.autosave *.opensdf *.pro.user *.cache Makefile* !Linux/RTIMULibDrive/Makefile !Linux/RTIMULibDrive10/Makefile !Linux/RTIMULibDrive11/Makefile !Linux/RTIMULibCal/Makefile !Linux/RTIMULibvrpn/Makefile build/ debug/ Debug/ Release/ ipch/ release/ objects/ Output/ GeneratedFiles/ *.deps *.log.* *.exe *.lib *.ilk *.pdb *.log *.ncb *.user *.suo *.aps *.sdf *.obj *.msi *.pch *.ini *.srf *.srx *.dta *~ *.a *.o *.axf *.bin *.map !.gitignore .metadata/ *.dat *.tgz *.bz2 *.pro.user* .DS_store octave-core rtimulib-7.2.1/Calibration.pdf000066400000000000000000025211101254201074400162730ustar00rootroot00000000000000%PDF-1.4 %äüöß 2 0 obj <> stream xZ˪#7߯:`GV?4ܾm/0d1dI @fߏ*JOݭGQ銋m73^dŜ~˓mUHֳ U #FhZ]*|NW|uww@/o̢ۗaYIi;K8007Gv֤Jj;8UA:7(7s_ f.}8F-m$ggG0pnNb2s3z\& }~ %?NKFjW\JC:4c=O[9pG"dR$M*p4=PP0/-0{ppR,O>2SlXѮ `C?bg ;9ߩ[??%[|:UNC,dqAYv,3=ƸH\_oP3*tm4.Č9^O;̍Gu|ZF\nztSB2,F1<8fZ SDXMh]}cFFfHH|.[Rd8;vgTwjLsqD=xl|7Ѻ#>k{V4e4Ed1@3Hr3Ƅ~d<@3֯WhT';Kd0K&8W@XBDlMZH eEd޽;E;IMRTgUzOU>rLOt5P;Qzku0ȡ<2t~~9YW֔[yrcoʑxGp1/ƣ(ʨw %lRzNst3kw*Q2zd\%82xT8Mnq,xѿ#˚Nb&ho~G2Rhv4KtM ֊ƐهoӑFiq^]N-_oa[,njC YfIUnWiT?inr~K t5rCwW)p}k*PEɹ.c^pdRL %?Ef[f!d86=9!_DDc̙yk]ezgzj(qŌkU&ο%N 1 endstream endobj 3 0 obj 1910 endobj 5 0 obj <> stream xVKk0W\#,?.B$CnJ Go>}3[*޾?b::]9u.FǗA 4= MGK?{_Gx${8vKgk2^?kBT. cpqdc86mh2 ֹ^e':+PEN>hz_քE-i+ѻS0ʚ$׎ѐ#KBړ/]8qEo 6Y;sYJލbȌq01綼B B<Ĵ8|P`ryδ7!9aMY؈DsE} ?y G"Zs!2-; ǘ%7bI,ǾSE 3.k|7.ї]-jP03}GPy N~ߔf=惡;f6m 0iN5CPoၦe8) #pfLY%EW1k'i(`N8A^-nk5rf3EI#v[NqMaٗO=N'zA7\۽d9%1sPzfu^֚̆ i``#:)Wtwsj=#3e+[X|_31vw8 Tpz$mOQGvyL2"{~TX;. Q}4Ǖ w[%}J^U5h7iehV68PK?JɎ:hш*cE-%;$otP endstream endobj 6 0 obj 835 endobj 8 0 obj <> stream JFIFC     C   " ֱҸ7T;]vEWm.7\lk֙쩦6>r.|3Ww\u돗ZOIk,+d=0[NgKrarŘÀ8}ݱf%1 LC11 LC11 LC11 LC11 LC11 LC11 LC127DƟeYv)Ř4 V*Z߾Y_؃Fc%vf~.*KŚ?Vr,'o%{VS䰫+iev{܋] k;wd˼^ *ZI#ȀN'mMwl ,1:`Ae@g@ ٫M6@ƙ&@i{9?vߌ/AAG9i1Ưݎ~΀Cκi:E蘞}ܥ=~zuxzFFFFoHs=ܰ..wϠwpwp8;@@hqWbr{ya dwc0G?S\bթI֟qVcbr|=rRb[N6j%fwF1YL~(aͥ3c͌#6=H4 0CL4 0F4 0CL>R45 !$%0F`"#1463@p\HFNUULLdmgI*m)Oҍ>{&tݤX]]>dėaqSd+Gמc!ζNG*Uzdyvv, l@qW$t:oca["$Kd4m7M5u8smLޜҷ;ĭr:ihŻj L^qy^qy^qy^qy^qy^qy^qy^qy^qy^qy^qy^qy^qy^qy^qy^qy^qy^qy^qy^qy^qy^qy^qy^qy^qy^qy^qy^qy|L7駪^ZH.|2*7(Hx.IF"/*fL9o?D9ǾPĵb/bKjnɖNw\o.$CGBCVۜHf7;V.dkr뜖-/ǑlȎėkcq)5f9Fzo#I.7In&Wm:I6yj-v/HB&UkF# &$\3qqDuuR2SI Vq56ܿ0LB*u=ԢN FB8ZdZhĒEy@&x L{Њ2 Ē?0 SiFApp)ǘ88b2*P{ L졗 (-A4(ȃ9V IN7 裲j18b9B#=EswT.<8[7JR.&;q=uv1H6ҫ^09pQRDflԳ4F˵(o:U9yNo9h/xK x^MgX?Gffoxv~YOxy&D]G3o3ƫ׀c7EY6{$Eib4>!O(EKiio'qmIOiNf׈6ǐ*xR&rfyRdшexdDTFrZkӴer(^1^1^uE\s,g;|^c@G2FÚGMMoQ"|`3 xf3tUsFwh9ݣG;H$@`q'46SP<2o9c,񌫛/8!1AQRaBPq p"#2@S?XL(Mg}GdnݶKpR[3)jO!FNP j ;Je-]{%j[1]$Uw}Ha"$!ILxṊc(tx!IdW]0zO/de1m0g'pT@HHCvhznlJ%.U^To\-]I1&n9.m&7sD\fJR]+Z4Ɍ4T0>&X}7M@xdPm&',PVTPV%%lLD\@"v =.Ŭ-j!5V] JLIg8hZjF "e,ST9"bԼ3SDᔮu ̺2*}&J˞qJ%B8 (sg IKi{ {u`9p-?$%DMO8T!#%B8 (sgˍŗSi/>ö83'מ ƹv!tLxP&aoҢ ݚ>T˩)gIH,XfLB&Ru )ϒNeR[hP+9!@*jzmiZm>M1ڜ.@xOS(ܻE>SZf.]!UA1@*I*NS[2mx_SQ &]Is+v夓*Opzi8ǙyuK37K"P)CCekSqՠ[ oPH-2u{|@v< rM6T.mum,E 580\=3R-2u{|@vaA@mãbinɥy+`sxCfXʡA?$ó$u`2_t?+6r2+ ()WCHrAu0kOLNKs;}ibMd\MvCR_;_|cK5-!HSnUx?(,54q x884Gӛ,3762RhR8ID33f.ZW%"I8qgyBZq>o'%О}+S C PJgĒp.)=*fLT2T G.%)Iw&oF k5Veda0%$g\g(A54>6WkGjaW%|_)Gqr+Jsii2ۗM}^L ARBFq+8Ojr(ŘX_|>Sj_NNl޿T6 KȺ < GiV+y@3- ȴ7_;c%+-"rˮDd${c^Cՠ Ac XK(H֣>No]chI~YB2}\ma&[l7&Ir_zk}\.$Q8qɇFLIFiԯ(Im8(AQ#0]rMज_7&G3+{~ͲW%ݑ2yj4 ѷT~eo+l=<,M G$sa*U~<'6MyOdIJ3"h8ZSyq0÷-(G4qЬ)uI<(f]GTLERCThT>Jq2B/'THO2V-\^˽7}MI9FONXLvY'MŠe_Iğ5KR%JN.Cc(fXODpu+)(x% 3KB9<9zrݶyk۹uG'G'G'G'G'G'G'G'G'G9?_9?_9?_9?_9?_9?_9?_9?_9?_ s_mUH srfR\+\ވQ%([F͓.hi7h!WG0JRTLq|PZmiNJҕԐ^Q?{>|TUDAQݒb )ʀ=qSslHÊ..!ԪzNN}({ K&tAE@J2.)+Ea:کr(E}0+%Tw.>.uԂ/Si$F\JUE 5aX`:|eŧRչf?Q }<(*d BUФҐnB2*jf_+BUPt dTQEi>tTpUD$zb9SL<9vU2t) jJRUTZ"8%| m+7 :]R *TBA'1{>| Zk mmrְ9/&b̶SJﲞ㮭P>pѭ,Uߧ>ߔ=p!]HAqےpHM cEhTBP^ ZT (yaŭwt8?0vg*Mk qRiBF%YJ(6ګ(|khN_|)Ժ\V>ߗ Æ2PBWUJ4Jm) w DS_~?x:(EIB:9oFn@ kHʪBrwr B u%!]PN O ϟ\m+>ms3@s*^M*i.դ*ܾ)%wOnxXģ+UE?:!mbϷ{>QCqJ,@AqےpHM! q2E T*'BwʩW)e? p!WJ׈+Jd\m+>ms3hTP%BBmU:5wM4C>ߗt,gJIWy_Wli/+4K%Evƒ"cI]Ja VIPzGyߜǝyߜǝyߜǝyߜ&2,ǝyߜǝ$P5ƕ;9;9;9;PmW,%QUSOH] ]w+rGTEK"@=UV oԭbIKJ+ϐdadQQNLKEsӗ,ҴfbbWܴEиQdsDJͦ2S$%m><`3W$-}rڅ>}Uz'%x$SV>q_Wlh/⫶4U UvƂ*cA]qGPaOsزi;UNUvƓ]Wli;UNUvƓ]Wli;UNUvƓ]Wl]J*Tw⫶4'~*cIߊw⫶4'~*cIߊw⫶L׬-!1AQa0q `@Pp?! :#q=! *W+Ȝ::::::::::::::::SSSSSSSSSSSSSSS=::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: 8;Synzum ~Fb\l}F;.dX@.,QɃdWm~e OPt)d(tuP2""Zhd;y_r69,_61k Gja S]) qK>!ᓽ $D\,$Th%FA,`-U d]ۼ8dLYcHX*TACBkVIxT# g`PQÉh-y2$ CcRX-@4 #jǚ蕓:!Ԍ"> LHj?2^UpV'hB Z4^jZU0Cz D4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD7204'wQ: 2au*..1? & c{cQZil9/1mB#P  ~|:m/6L&pmnd@9 7X $2h@?>Ŕu WHX*{=9iyimpu/tRP'sP4dt([Zop6UvۤZJ8+L#8)y+-X`va?y+lB&R$k̦lAp90֐Tt6ZitZi̻ Q@B&BY(GvX $hKƥihf~9FD0P&d(gDIik]_wG !?s1=B䊌UFL:s'̴,k'BB^Ma}yeJӳ/0FW 1lP9\,5_hx=sCp08 ;[e LsaבP/4^ m/=ŀi*, Yt!T 0gSZ@`,Z#CjƗX 3333nH+M}ffff2uv@zu`giDq🕡08mUUUU#Ǵy"8z*lLͺ] N&ԭ6;y_A'6,5CS$\nUxv6 |FM'!3f//#$Z@^FT}13lB ̦.@ !ϡ>_A'6,5CP=MAlL!4"Ŧo? OOp dpvNT >#W B/&Qt%K@逈ϸ2ڷʔ@fFb%@9YWXGX|PB|W `G"0tY\ؐmhH<iN@*1SgfD>0 0 YAdx_@0CYlc@`h:i+c ( e&L2dȋ 6(jHߏPꂃ~~~~~~~~ٗiGlϳϳϳ0,~}XiD ڮ`}HZ_ >; 0fK;`#nLn X!nGmADZ0B&lQ`Uo"baoIcsY޿"4ToH RưGֈM>T  i9PGۅVw țA. e _&&RkJ5 bn)Q CP@4z]DDNO_!%cC8cD  L!( HP7/V'@cH*?um;>~,smw u0Ï<0gl\}N8_*!1AQaq Pp@?UD] "OsSSb{6PSe~Mt~)}χPmnh6 /HL-~WFog;ǁ[q}~ Qh.flfSjN c>~]=m:_~6+}V߷A!3dI' X6=ϖߪh>-ӯt>]jC5c5Jʃg2&cM+9]jƺK|=εmX4/f7UA@L.8Ƶ"KQlZ F"Sc+:ͧj 9;j-,(+D zg=LW{~(R瞞bǞ][]@>Yج$|U"ȷvqK$+N X)܀,KD#3\xaEb\gV6w׸7}M  FgW_ufԆ/ߍs:怍/ؾlVcq;zg0gH_ +l8H(`!D0"[cg6 ͙^r:+V beX}R6%&\'z; ޙOjjjjjIe*LGI%@Ll*2Zx=j?m5QQQQ"h>kE) !1AQaqPp ? VH#XW'n*8 g`G0'8B nXPOif~2`q'g!0Rm9^ Jnqh͐<(<#t@8Hs@Ne <"cJŰ9V EQEQEQEQEQEQEQE`kb "oI'X4P p @ 1K)n܈aI+ }!d7Ԟ&͎ Iij/X;)2!`K %$fE~"!,=} s.  Db1F#b1F#b1F#b1F#\[ b7pAym;Yg/e" ,MZV`,isl՘BEQ-dU((ѻh1~%YPz<%5ŮϘǕyGY„ v9r %_q@?>/*B`0ӻ-;Ey*XZkz /m"ՉU blIs"o#*@AI tH^ ZvRG2Yu$Y\,;4gOqF^_DH343D  (/ʊ(%Vbǟoj5JR[RѮZr9q8?ҒThxCx?E,!1qAQa0 `@p?^>'|(ɂ* 0Ii}`׈:z3:z3:z3:z3:z3:z3:z3:z30kgOFtgOFtgOFtgOFtgOFO&"Q|ΞΞΞΞΞΞΞRΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞΞmt RX ,R%6T n ^yϖ &oVh.SpR`r8"?PaLrs6;:spQ URW9CjЈe8F 81 5Ej# (^Zs7F3$P.?cf0c& B?QOd_.G2Ag XyEc L]ߌ)GZA_: F60=Drxݛ:I)ܮt, 837JƱg-If͌Puf[GF霜ٿ4[dn C~c"@#,<5~U3K}w1dObNjVϽ o+7αi:#-R`&!5ƼM/YUqxo[w;|w;|w;|w;|w;|w;|w;|:ɫbD({ ̦`)W+l XW%&Q÷vB618 \U iW:Sx6: C%D6 :֑9h6b+^T#Z3:vmq v6ϕ6 fH0VCD$j <8qvt6ϕ) bEYHȁڧiEzLJU6M-^L@׌8Mߩk{AQ4p>J#I/2! rPP *-I8WPx1LyWl_&%_;KloXNȍ]oˉ5ek؜4 e0dIfqg) @/!VV3|Wfi.*d8Tf†d+l !廇tnB3Y[Fw;|w;|w;|w;|w;|w;|w;|w]G1c1c1c1c1c1c1c1c1c1c1c1c1c1b$}Wrpoc{,ç':@eypX潀.`؈c<̀h./n< ?g$hMD@ہD˘BuNr)4`lp!d ObA-°<-σ4Fkgzs҅͘lrPB20GOםF9pHlkƃsA/$c"4J'P:JxK'P&㴫xX/p]7G xx=jfԛf!uඌXɂ"r A)hRx8 |Qle& F 1,qݘhjGm ZGJ`staQѺ)* J!&Q5A0ݠ im?MdRv8DTv{p)Rhw L8-qKRi(qa[Pl5HS[\dY!5Y`1P {1Y(buf, A]r/qlCB'ULl| bg'dQ[-jokA r7CL AzU  V1B#Ku5_~Q/聳7an:^S7ńO8h@Q:h ;Qd7KNͣM dhx+UdUnC_Lдh9?.R"TWFU7t)bL'tA CFyqffffgI&s?ƚIsß""""-Mɓn<~2[>snt3ˏ3333?*I7ß94KQn>VffffTo> Ve%S f`X?6! }EA>,ܔ8ЃID\,SbcM /6ymkƓ^5&J `[q y/!*8UnuBEl]Сq0-TG~N?0֪y&8U DEf!~G)*(B~sO3t<B 838"GNJ9,?߁/Yi%Kv0bロD8u&S(QPƃH7daJÖ ÑIvР@?-h`W4 /-?;HvkV5iU8  Ź|\a#+W[B-Ps5w6w a_Wd5CμBZتy88sBO7p0M_ŨCfGnP)}~|˼CX1*9r4Ç 鷋AsS`)1.#yKw=[ qAOs~8S#9?۔;+QɾZDTX. uV2W"P%([2)J,`|SU%aAׯ^j"H{?%vdPM/?FOѓd?FOѓdUSTgѓd$ "Cr'BO'2~'&gT TG~d˧T'D}@Gg?a4`A  oSF#%*iq #PB!ã(Bg>p3̚sJ$ 2R&;|6vDj bʅf Ȗ $k*&;T)t6Fט8[4Dh?V&Ɂ(e~Y(P'B~NxX )l1A_td*%hXC6He'fX/o8b.B`  ,r0 o$;$5/| S1:Na pFDAE*^t;1HMtQ_ON%&ibysʐ+!;k])^IqRJ(bAx;ʊHkpIgx#\9J NT v;]bzӴJL{5 *%"nAF,~!t?y ;Lk 2Ѐf Me5~OaRʠJ(Yx:%(5w扇CCDseb0 0`QHy@IB ?"b!/ܿxǏ> stream x1 Oi  endstream endobj 10 0 obj 197 endobj 7 0 obj <> stream JFIFC     C   "  ഥqr  1[ 2~'_[vβz#KH:U!Ax8C]kCzu$}رtk38/e:M!R\JFkǒ - 59ηY ;2!Pbqȥk!֙{DkSob N:]q'yuhz+<򍛀 ع9H|04 , 00 , 00 , 00 , 0ƇXT,ϛ|gO2iU 79wowUv zjXceW1o6S9zlpqpp,=ߔxajj؎ Y ?»O;(h,Y?3so46/}SqZ[\1W,p%[8eܚ!V@'tlE ֿy*c]"K&is8l"r' "r' "r+S,j/*բk1wnvY;i]`!iml@D!i`p1ʌjKj]/2N6mӧO)Q짍G5x{)Q짍G5x{)Q짍G5x{)Q짍G5xzGAyzGAyzGAyzGAyzGTw^3enu͙Rlff'>Px\8ad.ٯBdLϤg+\pa7+@o畇0XrGclMyeEea5ŬN8rQ)/+`BwJueĪB([_e:U׉Fiw\v|Y@@a{(QE ׋5XZ8nQkҵaVJΫO7M6Xmd?i"E*=UuKvUF 'C<9Ôe ;6qmηK̈́Hڿoit]vc}I-Z˶0Ղn9?rh29swk`/+`Bǰ],>Oa̜+&~,F\V"GmlV|Mb4ɐ1 BiZFuNsg).E` [XLkFݴnAinMt"nAiB?\jv!˔(Ɍs54ntH HmPe𹴈n PSԸvvkTb]T`v=A+AJ\JM, ~42!Ut27 Ԫ Ѐ/+`-;]S`wkY+vN-۬[*v[=bTve ;*Kgb$̛#sG5Z0ڥYJ[HakGZ 7Ø,9@ %o;pZV ̴.6,6vEa6Mv׬u[7v؁>uV־Koc]^U&oؾ 9"'͡lbXTK;dÒem:wv-kAv'=G=nZ9H2!&>xY,%3RqJɯٕ}mah磹 &="[ƺQ[X\ͭn'FHn~y~yXs(CM;n%ƪU^ןF|4V8͹ʟ:d'8ogVEƀϤcee ;D?d^\}aKƑBd؂ז7hi4ív g +hlcI?O&|s˼͎ik濖`ɨї2o[4{"[e;vmgl2n&-Hn~y~yXs(Mw>?C \@e ;ҕZEVq^`NVnЭJݥNkn6TfanE_XVc=WƓ~y~yXs(0扶`9q26Z3@e ;q06"l|vì8`,0wX12G5{۲4)  P@|2[m>;BBǰƼDZE>}nIa&gm5kHamts\Qӏu|xS{(QMyH#@!bJCOkrP7Ø,9@jZN,ؾ6C>Ba %aE^c f+0]I %koRFQ~y~yXs(F'.,ؾ&M/NQP5VVXeY߫phE6lDo畇0Xr QÖ<=MfRGBӇeUdۢ3=z{en5ϙ^7gaGص8u@8`B}^rx02ulSmYG_an ^DcaU`!o 9j:OIڶ^ᆱo&˧.muCp/3( /+`Bǰz߼9uĪl6ݩYK{JE,m& P=(lQ8}* z}PہRti*ȡ E܀?+zˎHqr y- ΰ0d 0 g X8 ZK[(CS$9D eC| M<3I66$ ~1}r0'W!7 6P`!4"135@#$02%ABFp66.XӶf7Mn[un[un[un[un[un[un[un[un[un[un[un[un[un[un[4F3kE>L_YWSzbHUR ո 0l& )w10ʎ!. [y֓_sMVF<;l[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-eꆴnedss`NkLG1lq1p]uAgȩ9GRmf557|##~"vx0ℝl[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-򧳭 yYCN`IJdPKZDmNT >+y0Hp8"pmCĒF_ٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯ8a\[$-K3 cR2{-JO׋CKD)Sc}q 3bK8"|E2b=L3'ǽ2,mB&G; l&BGn($e8sㅄy2gAFC(>dt̆@4SK!lsOgk-"U7RMgS37&CD~EF0I5tϖY5 1ʃ(61V.^˘̜$evӟ~ 6UکMltw3-NKhiXSIDS洘 (Yw.ƭ L c0H$WI4r|ls`,5NlirP-iE/%xK8#d!+ŏ0rqXa`GgȰC t]qrb&bGd M7)/=ɠqsrGu㌁hZބ~@qF"@y+Śa]x967DKc>+/n1bHxO] M$0 x6k YK2 q{fLFA<]8 1Y9PLDd<&9Y3bln²pY$vC8R@_&!f 7qڲSvnlb,?) 11 2S;Xy4ƅnJwAq4Fxw?w3ឡl@Iaԉ5M˃bVKw=cfpERGYSlAd[-el[-el[-êpfnH߶d;VfV1e,QӐ׶F;AY{4/`DPWYf3H!Fd#ٛ:iʣ@jNx'E椖hPI H%ҶA$Lpsg t?pZ.e|(_0O$|ytK¯VAXfi#ce22;>bslL_ /5 f\CaYzp[z=r\k\- XHїˑz=r\C`L+L˃_\i/G_@lepC:a!&\q$YE^9kS[>[iDW?{?DGQ>߉TS2v' 3'gr#Q?dZK$XlICrMtC{Bt1Kd3 + i/نK羼cgdœl`g ?E1ϜxՁ&\1M`s۫glM.^\s}q;ulIP,睔>g˱%;NNN']u']u']u']u']u'N|J i NNNNNN;_gH% @srQtz% {߿Ȳ"G$Ӳu?QsۖpbU9qci[TqsN.X[#L9i",n)?iH7 wDf_G62ξrpGI,}#F9B=Ip, N[cmq Ǝq);dKd4C.(yI.`N F4d .m.m#>N"* "ikl4lDךKKҞX0pl65U $+@&+Jq(m.mz}#l,%xn7 Q0lJ;ėgK1w;Q`40xrtv/n:LMpVw8 v-D~M5p`aS[ La6<߉{?Gk'; ak~[Yem4eN:lU?bwc*J pq39j"!~|0f鳑ہ]Qd2s(\-HMZnjk&! 2.*/@DKZz$u?zQr*U}H1S8|sB >ӯ'?$FJ.m.m.m.m)b:i2zvsl6sl6sl6sl6sl6sl6 lYsl6sl6sl6sl6sl6sl7?GBu};gH uBezQ [#1:߿[psҊ?OąЮ 6ˈlJϜiMD|G}.8 ep+b&ms0L~տN!"_?@8~Q5FlOAE3})J*r*RYVY펜@oS t3PDtY.UY!KGlwI(ue^3'G02r*rO|COm[ igH!_pzoL*a27:ݱ|FuFGy w)#dp8is,ϸuAt'31Xp"}cLHN`5=m3M bN{L]{(7~i.O4O)NC|tFhg,zz8D5X$aP|Q fbURrGgV\ƮcW1s0\\[oV[ezc| 2a<`Q`"JgvxgF>?}؎l^ŖƳV0ӿak93&0$L-OƤ;++++++K~_gH[&UzQ+L%M_<z:*|%A,p~>:i^z/O~0/k'rh/.j܁3DK$$g|-pE/zajZǣOgHW:Wc]v:Wc]v:WcCֈS,eVFV@|w4;oO<\xnqjaȑ0]3њhW1sV%\\\\ź'>ĸXilg喣 ] X˼@ @ @ @ @ @}uW2d6YBvȡ $[怩GNNX$L XM1x>hr_90UsNH5a?7z#|mI{a,̋sXE/?Z,fDBgHKyY/Y82n-C{UL{i\j&CÅKV)aSgOBX4wNƊgX 2$͸ d#% {q";`͈VFdҮIƱtG#&gށ E1gQReeՄKQiwmiĸ( Ҋh/7 \I:BtWM+]³4A/?YX[a A_8OK_/hS`ZɊ̰c_1Q  (l>aL3x3039ɖi`t |HaA)eb K)r*rFtufNʲ\i$> \9s2r*!?y}#UЉV=Յ[J{ >^5Y=51tTM 1}%A'Y$dAX@IKY?-`E߾'>|fLIlciqJK1-!8qOz>g6  S$Q||U$}8 voKI;Ι0,d1c"D=I):U]uQRt}T'I*mTԛ lӹGcR5LI]tsP'?ce O?F̩TSݙ䷕{ru ̉,J,y(V,&^gW1tQ0? 2TQ8:i K섟.x!KXLmbtbZHul#fN ߀T?~JgAv0)_`X2 Ù p`Zj`#w4߬*?}? v$)@lߜ/$ڷK7Sm0%4L,/I=C@~exgNcσ;a'8?vSgH&9Q7#k qkHہ n <Ȱ6Aa/oKc\颯*8>Wz`6%9K-Vl0ÅǛ`5ba[ܹصÌe[^6͏䲞="X.&)?h# $$z9)¦xrbKҔU>"""Ol{="qXo@eL"0cG3cgfǤ_`;Zǒ\spK`HNkC I 84YّLS@8Y k^$hˇp l80zX8JeDZ' 8u h8 ?-/._%a`袰baZMMq]r-4m3-0y' V#v,I?L܇1@}<= NEsI#tS+}s2HֵؙE;f3vgݶ0a%~NH C-%>Z)7R,ie* j "‚t̐!f0mY-qڛ#) +)2Gn;W.ѼdT'BH}JWF8g dJE!|U1媊e%31-?.d E҇TG[CEM?"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""P^zW2cW^zW^zW^zW^zW^m^zW^zW^zW^zW^zW^zW^zW^zW^zW^zW^z>j֯M^zW^7gW^zW^zW^zW^z6rW^z-z2jzW^ݝ^zW^zWuZM,.8 1ċlV^ hڃ8qɒy˫>КYhekH0>XXLe2.pdP_> YxGK9f)u }Xq$l|Ehn՜S̅Ѷ9jVt峖[9l峖[9l峖[9l峖[9l峖[9l峖[9l峖[9l峖[9l峖[9l峖[9l峖ZjmuW f}4,ik7)lVFײ6Vˎ՜E#"=22"d x²ڟ&&IQJ6>GTFDd`MdS`I&8\PZxVTJUaG~#꾧>lYwfu_S" "JMDJ#IBlޡ?Ϙi2m#4+(VPYBe +(VPYBe +(VPYBe +(VPYBe +(VP-/z1&?' b3ՏC3Ր;]Yw x[iD*pq9tY-N[~:ww{`*Sh$ʕ NA0q{x8(3~po mt+^ @L[fтum[<:~T,s[v$᠕fnXOa)R2jǏl;K~ 5O/ '-Ġ|CXT,iۍ~"2 fr#moJ1FM>T,s^Qk% W+|KN7pn1J8e+`[WG;Ů4͒$Ϛ#2)ůϕ8K8̥1/B~Rݿ=FτD2Y~"I,IXWpFw9YǴeaҙl%eP) [Qj FwYǴȝ)YǴ"%{LN=DIYǴIYǴ*CLpq2 +)AG+MÍYU9WdS,ZnsD]5 f dF,I8HlJ#a'=A X"M\Yy4 RE"y>qZӠӄ$pMB5 j)SPMB5 j)SPMB5 j)SPMB5 j)SPMA;I 2@AQ!10Paq"3BR 4r#CSb$D`p?})lax\2!ʄfAIARuS%Cxb[%:ͤj.]❟\˼SYwڎIQ]S.F,Qk==Ve35(x(T[ lVPVɖY}4*5#ȹ/40ptvcRl'7n8cİx4 >XϼCXU4ỈE#*~I)p099UU@UP*T UU@UP*T UU@UP*T UU@UP*T UE%_!17-Cr!17-Cr!17-Cr!17-Cr!17-Cr!17-Cr!17-4̋i/m8TsSVӊOX??+ИeYq?('9l l#")BЩ'"R/H_hnu]eE](b0{X8_AЅjg @㎧Xȏ,"Is* !MKy ܔpl^k%O-N]BR `HTaAF!D&{1UQYu]d}1Ui~RONX#ϻEG#"S.n14s=7 fݴ4%|cGADh '2|ԤaZ2ҺءLG* eJ]rJҲNanb>[O糡j<]--[Ex7s2Xh"o_ןgxxh ^4 C2L[;&7JyII\\wLn ȁL)> : d'峾Bcp׊ | O\wLnJ|Ng|sQD4Ieqr!1x2ܚ4mlÏfYM7Ln>Rl)(4 LnF* .]@%&R/AbUw-5RH!z͑ޡ17-~$T3 ]:רLy fa.+X6m:zǐܶww v)So1Nb;yv)So1Nb;yv)So1Nb;yv)So1Nb;yvsQW !1"AQ235aq #4BPR`$CSbr0@tcsd%pÄ? kRkK)Vaeucʣ0TVc*d`5(֤S%[?#WiYP 3:w{I$ev=>*clhՌj5=$g<3 8hR\;jK(m6{EXyT&%lO?S =ۧԒ(ը;wC)::j. O͊{2lNqxgrpjG1"ffգT*RM#::jO$CI#rVnLD=l* 2<(Jl1b]X@ xt4Mpc1*O:#\+wvT5=iLiՍsC5ziX#|FOLa(Kq(fhvlP] kx!5`PvRCoO]F$ xj<[4 ^3c2iO8 xra{54flrP5x}.ޫ Pa pToJ]P}5,Ѻ%&#Qx]%D=Lدb3lWb]_+1v|f.جNNM|f.د\ud-xDWNdp UؒXg+v|ٯ5fWl]_+kv|ٯ5fWl]_+kv|ٯ5fWl]_+kv|ٯ5fWl]_+kXVgzw%֠W1*wu1hu,:3Nu+ACLZzckZA̱ƤqbqD/sΤFhVP'槓0hCmS[I(POGq#<#s0wuw ,riCg>lPDREDs*{>h U У-HūHɴ `u*4n5)M,FIZ],2)fGOX$kY\`9LEK(9XԱ$dⶐʒ0'|Z&^4W"9{F)"I&:޻7W^Si$=F*q { d;*t=[,e`6za ,fTӸ=Ug,;BDo,@+c:kglv1Fl XƅsEx4Qh6r(8$ zU~$ 3d|¥m%WZ 6t4gV#v9pxh&9S9~=#5"<ȘsMGol`Hm݃\9]0sF%W:7U`cӟEJʋ2"M~권wS\2nrIcPʰY%@C;H>Z}ZIZ2;[W{Tm_/80PUCyE@r5u0v2I%jg#_V];ƮoQ!$(Ph\$BoeƐ~|U%AYƝ:o U^r#A+ᑧFh`3 F1&ٿw/R5v[MŐkUW-ĺ\9Xɳ.$|nx k&]ѭ!U-㹥C\m4 ϟMhg;"9oAx^wٙ  {+{vwc5uYƒ5c~?Q=Ҭq#L#hd"C6:3og-FECWvr5$iN4 w\YIjᝓHƜc?+&bbp1L21sUf7kh[9$f>z -CZ496zߣ壤rap] \WP,*ݎ ,W칽dp5N8e<^O3F^ J%d \6sOӲ;_ Swsgl4 oLNBZqj1ul>aO~b)eh厓pN=G%xO,]Hᙇ7"tߨ窣5v%xg kRw8e\ E*Iq?sYp7p]<{'l΁&eB;299BXMdбyl ,4K0R|$k}|>AdPtRA(8^g@V#PvI,Vf+â&G cWYwNn%  oH-J2zYy]Pm#gf<6InTٖng~bmm(2?ܔc-p 6<.i|gފTgOz?_'/-P3,2E|bOB= W$/ZczP_пu+4̻(富I_1'~ĞzI_1'~hF`cF1~buFx|Y$xN?|bOB= W$/_пu|bOB= W$'E!Rs xN+>􏺌q \(x砣_________________'I%_+2c/2c/2c/2c/2c/2c/2c/2c/2c/2c/2c/2c/2c/2c/2c/2c/}R?u%ի |lӎڭ? q ty[[a1ChVV^@V4d(q W&V*$GGU fti<:cӾDr}NWDw5]8Q|io>$Z9˞5Km3c%w g*VddL۸.n$jF! Ǘ5d5I4o9G4UrZ; :wb RGV/S soɲG{G čDVR|/yRHt̙8cVk>+.u'7UG&ѢI$[quiw,[Z㻧$kި?&>oڕ wT*sG#ֻB2;F-Ch8)nOy5k"~:^@ mFhi>E 3J;NI ?xgI49W}4#Z`o>fvI\~#EetS<ek û޹(o%{٘Z]AcOFUI#ц "4N/\Yv#Ս` q b˹d ʐr#}ɰ0yܭ>cW6 A JA~9b~Urb<"nORwȮAY 9AAbBč5ȲDwZ|*!`y vhL͓~>Oʫڕ)tXFpU4ěiVc VdK8.gMpRtR#yƯo93[LQKL;wB-{0rNuE y{׫^;46G68m6)c)#^9-axӽVj;xӜi@15"A9Fc=|ߕVOmVs>tT$ֻ훩u@)JH"t%Qr*`u'TUw̒KIJlaQfQpөeH$~ui8eeF(fёq$9"%aS8as%qSE76X"⑿-fdI ;FQ n jG ) |[j"[wd=句H&;hY $pQXFGOLI< kk (.St+hJ{(yR=L/yvjv '{wU8wjnT#bc=cF.K5nF@&$ g 8yS3tjc\ BY$VD"-eclÏVֻ+i0 ?$KXx̰m*sǦwKiČw]%bGi%t$ƭX)nBnfe1I=wt 阕(O}r}S+],b/X?7g$h;} <]Ì ޭ%ٗBd$#G9.tBm34.tnm{? ըcF1Q?G髒o23 ;NW6Mͨ37ژYnYIH[&8 ACa~^}?  kT?66]:xцEg\G@CkHShq 'ݚ"Ơ|OrX2xU;ުŚTPP>b1ݍ٧dmQlo% G_sȉ!X\\CɫWFkUBy*)`G6ϔRM #R;;oq3!W->O G3s CO ,ҫ4|;uuVFM\}-\2.Ag\e7%;q;oq3!W->O G3s CO N6=?^701\q'jdFrB8zxuȗR;p8 ?ƹjngpb !> )4j HĄW?O+Rޱh4?"6ƽ`A^גdHKF xǛYlN?HȲ'zͱ%|ێCM릙ya+w69?>c" n%6#?']#T\۫{&hZ6fm~F8뭿MW]m!&uzuL0]4g2"Nr77By&|7ϣom=_Ƅe3NG{7&He4Gy*TnYuڋׯ]vŗ]zej/^Yuڋׯ]vVW2:p>o9׋.Qzˮ^x^,E׋.Qzˮ^x^,E׋.Qzˮ^xI5NrMNb;uˮ^x^,E׋.Qzˮ^x^,E׋.Qzˮ^x^,EԱ5FؓNsOkNorϴzNu[W8r:+M1Cwߝ?7m)q$-7\ik-ćwMAia?<$% 擻 wpGNi$3OUmB'Rzqj ˢ9Xd]>^ ڼ1:cm$ugYwYPF[C 7Cgf\gUn"@aF=\G 0 )ԑzH PnηL3uL19'_|PndXl J2<áu08gT0.8қ{}.61+GY#Bi.wr˧Püu?T`RBHVr:B!yRvD6 dKU$m Kn`l/u+it샧V1:b#WIVV -[u[_8 hkkQKkm",K=8*}( gӬ> mn6R .kLhw[iUC],TrG 6F0An- m9ɷ)#:He]l㵷;k%Om~˯e_.H X/~75m{aU0>j@5qKܯnIdž:|'*rV8Нdէ/srR#!wyxUܖfXb/Rݻqc6)򉧚V W @$/Q\q%PF`Kgɸy,JeOr&H(Tu! Q.tu+J_R0 bчRގ|b=gKGtja*{1,ɜ"O*9'yd Yfϳήc4+uCdwK]CH-wEFL]kRU]0CYaG[G&7nᾠk(433ov8ys=1L ~Vsմ$x%b#hMI2db$[g94UWp±~V*i \ڵ0%K{%2;]YC+SLϋY!U. rn̯m-W?}5[k.I8<=|&VЌX`gd]fNTxg)-L,gHIFY@'pgUq+ QߏG-a.aA`q=rsoso! 6(5o$jdp%npܼftQn6Eq׼dRK"sd󌊀XZDe\/[PDR *g.~%s^3TV"y*b(4sgH"z ]ٹgGǫ8ўnRha=X19^8!َ5lǕNZ8ԏSĜrAXʗiթUg,]3cPqCf=nZMG8;=;^xQ/R mOU)HwdSn4p,ˆ\2xo׾:;g+d~\kv⓹O?6oq6񹳜I}Xݍ~l(If.|5P$W(+đpw#<^ C ,_-] 's\g44bs?k$hsN6k/6o;ietU DWZ!t\=d$zg$|Ƥgs\ NUPDˀ?_|\UK 8ZYd-wr4cFfXfMq#k s䝇mX:jb%̊2NN}n_ZWu}j/]ܾw_[֮r}n_ZWu}jm{ ݿv/]ܾw_[֮r}n_ZWu}j/]ܾw_[֮n1cr}n_ZWu}j/]ܾw_[֮r}n_ZRg},A[N#eQ}T:AOYY$ϭӾMOI#̓>'yW[T(),6##)ь3V;pKȥN1'FA^y6bGrdRIߎ Mp<*IlP %>\n fVmGJtڙ/ OVEBѩ+$O<Cj'b"GSt[m NX#ړYdxΤ3SWeH9uf`x9K-4!88hUgLYb/|2]Gsqױؾ'] !5C4 xakI4:6vOcXvmGη #L#D$lo"bIz=+4$'mm#z5yqqKG᳭cWj4`לG[Iarxp3m=MXVM[R\?Ѿcuh!Qw r]_.1䝇mg\rAഓrl/#`s<&{} MMYѡ&IyUSihЭyZMahܒ$J8 r-ֶR_ѺrK$wNv]}R_V˯KռCro տrE[[,h.-f%"#dy.Oq/6ڛKGօm&sVk1O&w Է neҌ 'P:[]0fAe)Wy%Xwf^GWyBfI'M*8ȻrQkw_Tծq$ܪJ \[nA䷸YV`{\1|)/se{} -}nZmk%^a>Iswgy|EZ-$N?sQ\w9m ֳis?X;i'F`~F+Crr$w6˯KyuI}Z(.g﶑F~I}?ߗOU[nG Q2A"IJaUNDf4zq'Mmofcտrfxw*`X}ZRnm$U ?@e.PYI6mn9h3LEM'ti3s5g-*9wIOWui4e.տrs\KՀr[O*p7vh&ҡ*pFqwSQ=\Ӈ&;gysMrF?SyDx7?(h"62(i^Rֹ:Ge#/Ѹz\RS\&FʄNx<2yvjY^7#V3'J?5Kv_޵9=V=Iw+4 X\2u*}$bQ&tr?>ZAdVi3?*v>oz1#/pA:Xy+⶿Yom~˯e_.+k]|V-[u[_⶿Yo2"Jt?t:6gѝT6~Z.g_.+k]|V-[u[_⶿Yom~˯e_.+k]|V-[ti?\w3[_⶿Yom~˯e_.+k]|V-[u[_⶿Yom~ˤim` KHWu%ܻ1(Ng$$X}?$#\?\- _k1$C\QD'vI9o*̑j?ƹ٬sңwG_;e)tnM9R@,., 8z8yYUbp˴O6`r1yak0n#<1OKGđǝjoy [&Cje`6;WDrg?{~i)3M&v~,%ҷ U [a6pC*!0[c&ܺ˹8 d\D!fy ԇ"ԧ<p\I1J1єwSlwrF0})nᗸ] \tGk~71ss'~X>Hy7J?둇e?>姐Sa{;};$$wZg%ֺ9<j- 3c»vr"$xBDTksm>L'?Mr[B" w;H9DR>'+f1KZ;8=9Wkvd 2R +RJׯA*$%^yEY!y6rCvZ{yPI"](2F% 15 lII c;.Mp;! b}4@gI3ZpI& 4)Mr;FT`j=uO6+v1BJh5aq(8HH4Ja ^9ɞ8?4Ksss}S?.rV [ܫk6#vIȕOO&nMH1ZI'22e qOxxpF)sY6V||/Oe#3eߌa[[pq[>,a1xz*"i`V˽;UkJd&Dg 8nȊ$I#N)g!l!uYLb)ԙ7餐wA'k?MX'^ Iگj9M땢 Rd恿\CZ3W~źB[*G^?G0xO$w:o=ȸTP0immlx[p?W)#n㙇7οo-56efp3=pq/<*}eA%9KBӗO\k]> \+>o{,Kq̊0=U0XW:X Ӭ72eY/%oc})9~} ͵宥5-Qr\ɫq#>Q( q^/16]w_[֮r}n_ZH䘣!+$s&8)g6Ӄ|]5ܹAvc]{T>T_9'_J/OjiLnT+ﰝݽpOl*YXpGG{j0 9  qsca.ybRAr)Fy&:Ѹa;ogh#uF5 Gӗ=lxOGwbw HՐ/hj]v@8Gϙ}N$]\0 (4㎬tl`?^(?kwXymYٝK&_kekZ}V_kekZ}V_kekZ018ŸÐ_ލdGпUq:Ej_{yƶ It >e=5$kkZl>o{ly3u͡:NTWOd DIfSP1϶^Zu'9q=cP`3'k>[?sHՏXkHYOElKIm-K+ 3-OV켷2ۺ/0iliiQ#pxoF7i}r4{$!T٪ufE22*T|B).{k @427'!&RvI a3*n\ wDdƃ⥵31yȑlz[8!3O4}X1lͩA;=5u ͰUR<;i;'RմЊI-5mth>5}߻mѤ*7 +ĒEEJAlR̪{wwEbY gUJc%|LXt#긽KEqXP 6&XMxΗ.qENz ccwςnэc#qCw{˩:hTλ՜ N>=>O ? @lT$Ả|0FN{|;"h؄9SmCg5l:a󱿦ovgW6}9c,;&Օ6ɧ] ko9o܈.]Ep(@mad198M$ R5{H'Y}m7[M<,f/tvw~-mdcuϟ}m |r8sq)b+ث(%Xp멡k]vSGlc^y8Tl\qÈxΤ qTeM{=Ynxiƣq-^ I3w #n%kw_Tծ]}R_Vg8BF7ݽO˯KyuI}Z./]%kw_Tծ]}R_V˯KyuI}Z./J50P^E'y_V_tYH<š7aoݥPįV6\NMb0|ڪF`zܡ*VڒGcZbk2?ֹ3YJs+Yr}+D+,>o{yq[).V\6=] 0wy)-a"Bdp7noM]dҁ=[O4rA:|5qV9j@8z3¤x.{X/ڵcLA{ͰDy7,=%ERycZ.@8gRi`f\y,ἓIHg:pjP3o-Ϲ [H9㚶 uKٌO^;~Pe|*hB(tbŠ?z7XbP ie?HڕCuy8bia,69ߏm $Kt+/"$6rhi1&H[ۨ2aOj3SZlmZ='IA}(fґƊ;V(4 Dh85`\MtO!~R<);#Lm46480GG6zF=[T_da?lV9N$AG$q4ǯ>S?vO%I$ nK$DchIMu$Cue$a!n si5=)ڶN,sX[9?.Yuڋׯ]v84LY<moxNt.s|/qfpȋś!toxHcD.Sr}&׷_t2[{/"0RntLcG:bZX{ OYQDfGOhjs$G"r^a¥NWHŰQA!{[cu9LX_S[Mt#ܹ 8H'Aƍ_R?aU͑}|mn6FҶ+ӴNThH|/| ~6-@tv)SGqhSjY_2m!n"~5}I"Znm9J^(G+\ܡe +lܣbh`:};JP1qNҌVA V.P?X Q0q)?6ǔleӸ耜~5kfe|jկ՛,B[hCy0/)Ysu[]wi]VdxJկ՛#ltp`yg^S02N(<0!"QX9Sa-S .s1=+#_7+VVoTr<&4* e< _~?B ƍ nt7P(|jߓ̐b%daA {١*Cۦ1\Fin"0-hf]q䑦k$\y<5mi.ٛ2g<'bm#en]\0X`$?cF"9"=4HPG 0 Fq䣲ܸ8VFT.y3=4 Y0<1a|}1Wp^Ro7 7*7&ǚm{=?=X}ow9z u [bPlC)اe g$[R+^!˃oɳaUQ2*Nf{$qJUT>j[hI`WO] p'>MCQil.5'Iq-2gq0tU3\GY4|SOg(ti^]'Jot4y+\[8VmV8c%Z]`^UđZmf4^iAi01R03#&SaGy&03)- ozoz+.C,iSEđE k2%R=Xk$&0v˂Fp"HѡtMQN^9Ս%wg9OC"J`$>pEb{ң""Ӡ&w]& rv!8qjd#Xp|lӅ$d z7梱1U4658ߺpY~ ?VӰriƮ"x[q<9VVD=.tq5uihf<6:s+kyީN'";9jk\J"݌cs_'3ʛ~K 77B{!=fs$`7EZ)NO2ζm| G?X}?ifGϗx>.%{ xcE&@ά&7TVMHȨ0<'mT͹choZXf2@bZm X<7e}9"pOXrBCz*99*`m,+}V_k~緊 VfsΏ %͸fUS)%&@4l>y<uX%Hʳ]ݿNM[-e#;%kK֭tt `UQ$'>~QDϳ#f*5xR& ^ 4j&;k5u5tkgnlfakZyj߹"Mٸlso?VOaP7<65Rdh#gt:=V;I|בFh*,Si=u&zǿ =P$pC x"dp@9f]oΝQ~o7kڹ,Bc<*Urb*(m}1ӲNΉw66u7֍#9D,Nv.Qsɿ#\L5*!e<~nojq7yG"YnoD;YfM7$=4'wwNϹ{hX3ϮP 4uUM4{6ȔWױ*I:ʷ"$pC^&d$W |73]7qdy톒Y%} cfNy5rvg]W gɝɣu{?챿?pqNGڕ)tXMJKZ@c^ c0] uS4PG7|Q@F$ xj <1̣EF1x4oH(dczkooR.tɽ;= gj@tdXeӸpbB$9q>Zmq#[R#4.u~:D&7zT$;(Ѕs9Œo><|߀l>ou}?iKo[T<UrKϬÙK,WFdCh w1u]Ie4A3*8TcJ dR+|' mVSstCP>QV=nRo _zl>j'R?)m˟5](%BcVzJwTx.2(+O Z4fJy]R|V"i$|\kė Gס_Kri[#Njs4],lqBy{B\O;qGض164{tV2xf養]-"&=#G%Nյ6A߿/umu#qo7L \jIɭē'2.EA:cxO$m{妍-H߾EhP*4/wRJ"-mB8FPi5EO3ǹVN+.|ԑ E~z?}#4o)wnj|O"љ'd4xU e[#usm$wfF@yXݭ,H 7A4s(c`iwH*ќ\aƑՖĤq.P9O Lc 5 gKSoR+yNdR竍 [ndpuo13wJN5\ְ:8p*KkY!:h2?FKڅףBO_C [Xd{eW1nbi`cEqMw:A4aY^ڞ8Fdʰc=Pt}\KqXUݟl2ˉN;htf jEQ2ߤ9Tiz"t?5\rF[5,Y gKC$s8­JtY"t$ìUЭlP*7?=wNWGsӜmؼmlBV7%Xʣ秅AkvBQv],H@ոjݺ˻YaQf88Rf8Qğt.FORaaT/~m^u)6a՚/ S#̑}&Ӊ= DN=1$-# 9#(l9%G(phLƍ2xVRyLxոx u' g#,w䭮ŭfӝkz3iM]װѴ[ɲĀrNV.ѤHƾ*"ջ|+s3~SGlUN'}d¡CX5yy4QmLa 4jsî9 j#'iQ]d?ݍյy휨V10wV%bqF3K40V,TPiDq9T՟47F}O42J+ƿU_xjk?^5گWgƿU_xjk?^5گWgƿU_xjk?^5گWgƿU_xjk?^5گWgƿU_xjk?^5گWgƿU_xjk?^5گWgƿU_xjk?^5گWgƿU_xjk?^5گWgƿU_xjk?PXv-\:s'@qf׳\Gkٮ#UՇ;^qf׳\Gkٮ#k{5v;^qf׳\Gkٮ#k{5v;^qf]\qٮ#k{5v;^qf׳\Gkٮ#k{5v;^qf׳\Gkٮ#k{5v;^qf׳\Gkٮ#k{5v;^qf׳\Gkٮ#k{5v;^qf׳\Gkٮ#k{4xV>o'׳\Gkٮ#hu888~#k{5v溶nfyٮ#k{5vөu~qf׳\Gkٮ#k{5v;^qf׳\Gkٮ#hu888~壆Sk{5v;^iԺ_\Gkٮ#fT;Wk{5v;^qf =!;^qf׳\Gkٮ#k{5vSW|ߢ/[7FuXvYهpB-A$;*ΰ2dz+>k ABFG1\L( ӴtrR^M-1ssqՍj 4/"2g,:oq.X\'Ƒ(fYuz"Crz]YӾ.DYWwCId̆ -mŀ; IM4p1%Z[lM*yh]A8Vu3VcpxUX;I4-ү{ ĐK$ bw}P}ht{s{܅`";O XE\$tsY7WgL˻da_i[ōlokwN馷؁k#('ǚH6)#s %5eC&gw5r~Fm):6u]dɃ-O"!]8 Mp<*(}m՗~^e6uw9%<,Q|<~"LUmvwxSg,1FWNI|@Ū]gZ[V#M@8]c69;1eUkQsNsѸՉa!dݝuasp..#2QD)S26x\ V+g7pxIagqVO#WMVwFGMrAP\OEgEW1A-#lp;]OQrbѳ )mZ9륀sG*3${'֨sUɒloxcMF&~ݲRwl\(+'(J(Uy#P "ZHh8睝x$;h X݌V$3 RX0;mY# 뿈ϓ`vs̙$+;E`jeH+Boբ]:nӲP8=u*Hyӫ9W]I#r`#*jd28i@DNF8Te!˳!Pƭ]G{9J)t[#F"uӜ\箙ɖA/z3ըUXQ~oU\mR6L'LǑ4,ˠ6P:gWI)c[wp*-Rܙ$XGU@LbƠƟ)bu5Il`M (+=AQ$7=貺OVNX=z(E`Mh6Q<9D96ї"y]ěr0h&ij;=*-ȏ&.Aq36N3aWFHݻ(qwc(^B<|Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}Ϥ}yG-H39\Ne%O{-:ep,2D#> &OmeK.QmW La5g9%es&ѹwE5mqn{'\>:Q(ڤ@m?IMpnB\9;ZmD VsLntI_`Į w! 4.7TUi3$tǯR#:"Z{_`tf!G,!1AQaq `0P@p?!<Z2ܟ*ЉTƻiL<= {/x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{OhP.deи*by:hZ 71uvmw-^T[}ez:w|q.IQW-RaIՋH\8MraZRb|e2fϮ&s~/Gmwq8q ieT)Z֮/«rO3* 5i}j{θ1$8zxT,gu͓LyXϨWzJ+g]b]c5dm'b֭Fsj}<@4(`H=%N<λFV(iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiol8{J O%Tvz"zZ9% A#A}<,Nx=k7.G/D 6-:j@)R 8UwȪ u:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tig,,3|8e ٰ/D|ar;93m, o_ fs:w jm/%1R@I u$7F| 8ydʩGjg΀]亩jЁ"7HW~V9JQLKO*YlR ӭ?O?w/eo[7(R3C1c+h=NbqX] /u31 ~S VϠ8fdj*BSE[v/5)TSZju} У7*}TF˾[4RnC*r>cy; sui]⡫ 9]<=uF?XYSIIVXWXM`k5A]/lq?m*ŏIt?€q6ZTxyv\:5R֬ŭWX%a^Gn%t) HX+"ݍ]\W3E%I.˫+_24܍}Gx`)]r9:GZ]0AUN7+P r%ytei7eZӼn0i(}eRrC `%KS8\=Iz~Ffa2&>I.Qq%ۃZ1~zى6 յ.5>ls փ~aIsޮm^VQ%|Ne-pvy!#888888883 ˡ/Og~3_/~?Kg~3_/~?Kg~3_/~?Kg~3_/~?K˵IMcb2V)5*2HoC1D@uKjdjYG+\K9c cэEo.a 2nie24B Ґ)7zjˉ6/16m$9]E_0zZ*_yq*Yar!p/YJP) A2,&d%Em`w3TS5kͼ\(EE0֜:^K+S <e&+{nѓx#Lǜ{$7l\KCsaxDo P±bM2c ͔flp3:|{eTN"ic1+jkj,#E e1vqXˁ[ct%u.[-q\j,T1WbkY+[b*[QPR*o;PʁnǏXV5eS.p@þ.W-RK_6]z" j=NQwV*`Ȗ7Ҵh7ѝ XS*MK\c cVqxURSdóW-i H*6L,ó~˨ +G_V:l Fc)G2vr|1zw_o{}d]KZ-0yohԈJkX5Q;VpΑVBawisJ_w:bW^X1 3"PYYV+B#WU=#hf4@Ĵ_i1vv[=ȢOC+Pqk'#thfKs. wNENjrl  `tzc`r+n4<dϷXS+VywX&W9#qm@JCoG<&,4.:G롙 aS G*]]҅m7^Lzj5ԥWNӢ6. &m*ї0NhWwL\.cR] y wq#(qN+Gܬ (o_l e6ƨ]|;]ή-"~|7bGe?Q>AnĎ~|ETx,N1J'42޳ ,F 脪hB HoC95uYnۻx2VlUiGpz!^Dj4yN&ېB,4G{F0^f(CRX\HҍQCҮo|&2eTbdLٜV"VsX\wk%nxۥ1*~bQUJ%[ ^6FwX:]bGpz+4ܔ0 }3[zYj_ =^^\w!|-{]{F7UGnoU;k]*C=.H}HR/F fiz Ss4F4hT lC|4hѣF4n+ֻ%%F4hѣFud/WmEJԨ% N]%r^A7U׋)1 NeVJKIf̆p,]eF,:Nx.#n!M45Ѹg@"Ud+ۺQV}Y iR[}_UA5V4[rZ5qW;-, ($ӑR.*eP㽶]N)32 v7R<२t.mLւBk>xw(K[4bʲ:1n*:JCMɽn 2ȶ̘ fFu6~}]`n%UPy:rḂK3YColRm+Z"ˤA RA*b4edlP)L:ȗ%Z3ZE:p~2/\m;zZ*TZBV(B^4flnԡJU;JF|Iuc;4{f/AjJqKUZg{+*MRB`J+@[.[V,X|rŎZt Me /+E}MG=:rBC2}հ*RpuUe*T`:*{&3p70ZdيXYi4~XT[=d*P5ævѣG04`IFվ0J=n|i'*U;,GPiR4pv{ʑ :Yjjwe)>Eձlڼ-y DAC1wa㈩ֺ#ޑcEq)l0Lr `CbvE3In#'"=V¼A76~5,B+듘4Yh t?5%r(%{̱=hݛV=Xɍ]6pK%qnbT~uܝjThVWeSsԤS<_NT{K}g2CyKP)Cp쎓S@ }"Ⱦfs* !n5^*Al UO^;D$ZѬ^ .rU,vmۧmw  X=}lУT6E+?p*;Ux/^ÊMlFٖz*ufL|GDy0F<(}MD(:waQO\UribSS@a8JڊsyLnz}!\ՠ)uxƍsD5CB%-γ k.cbz98#x`N_2ʊzq*)~(4ZOKQJ%5r5+/ipP9 l;!GK" p.qTa۾[ RE+j5Vu˃2Ķ46^6;`|b4ϙoRUP#p_RbuG@|gY 64vۗ5,B5Ip'E;"ͧp><NB\-csc8`v= @j#f͛6lٳf͝SrWcgM6lٳf͛6lÒslٳf͛6lٱi?ƶ.~QkXlk~.!7˴8!歷=ZӐ/r?kЋjӍKrX p+.kɻKЈ(k/JpUWLڜ)Ns2W aDu0 Le ϊol6;)jV]o0sR.0QC(Վ̙3no7,6üc}#.1J*T`4*( N6,Uݾn )o(GµkJ 9>Z6ŊAKtr3ӳpP&*TaɎhnSzO4psX5S ۭf= ȿTW0SykP^}+Eiv+دJc?w/Ք> U?lS˿zyW]WfZv2/Pm-zѲi۱d%UּC#Xׁᦩ+ @VK8[Ƌa17uÎ,9%VN-TP0nؑ)28;ϊ7ڃTJZKƪgae܀uZ*c11b 3S -ޫ{թwfEdژ mzS>,^Y)9)Pml jw0>o剭b5ay+ Q?ٱ/t ҃&t.n32aBX5"d{o&eofsZh RcL.BP N,NU $IbZ3-5koIJѱ|bmW#AՆgtb5ER#*PVemoP< 䢶Zw\j:fD9˲V7HLA_iX1kVK$ٺ}q>A q@5cwa;) N;ڎK_N;$g tŶ|=DMZ>XӦs9~oNVT-*42̂)ǯ3DKA1~#f͖9M5v:rc g[SՌu䝜; %pF酤lq>ӱ5 Zzg`;bjޭRζ.r=61TSe|3 ġĬ }kb &Ǝ7> e\̄E?h(?( cJ(Ύ3/jS'scZ!+=!ͱY]Lz'_2C~8Nh=wgJ}R$0E|%ee%e%[5;#j~}_i~}_i~}_hZCc <)_YgK̟-| Ԫ~: jqesӫo|P.,CA)1fԿ ЂH ^+[ط,  h yC政 1mWڒS;YL&)ޒS r0Kbd3QL1ʫ1~oV3ۖ ?y)fXHML?NXqZ*wdS^A +a@D#DAJdžg\e ҳ;+}K6"5Զ,+Wd( `<vi3VZZ B>c e!T écJav2t@*in7{d#mYEE N7c>CVm+i;!P_{qG%{P:Ir0g@eCIÓk{L3lu_uzl_/=#t}"nPP"ڿ6k> l3h^{Aßf, :5Bͼ-ZJ7+vUb'`Ha, -&^b*)$uQ-[g2pSAbu:q3-@ːPERWLȕnT3nGCFp 滪~%sU08~Sf͛:0F6lٳf͛6lkDa P'5fEr=RVO.F,4<<%~Ȋ|WX*Rbg뉀gO}c~`PRUjj);C_ عpNhuT?58ܬ:y3ȁX{#Z5ه56qmc⡤MXMIR.++x_X=7rYRB Kb]HZ|@CЬ/Y%᜖@ﴝemct gv=Ji&ľU_3&؀1b] R-]9&pzG< Tl12XdA bZ]i7B ";9t\'ysS#~B4؍Ԯ%o<@0|TyN>?L=*NT W7RxG5cYŊuKd+jt;JLȣvgVԫ>5BUgS"T}ko/W+)K1):X+o.Dhё/J?SS@1N} Z.cYX<Ƕ.#+10#E,] q|kƸ0V"05yOP((ѣcPN(>*tM+ՇC*VKn#Qx|KH2=i,E6㎣.ٯ`ӧ(EKhjc~(6Z?`x|?KK> #!v`4 r`@F~t%:}׀n`jS4d.^J.\$ maU}OQ;_Hߗ_I׵7lpqzoFO{jU`eIT#kը&xrפU=CzZ(9Kq!#Q${ubi1): ݸo_1 d`פ\*s% ƾ-‹Zw5im*/\MaE~& ,UkMd]YaJ!ޙI\u^QDs${|.+6immnhc: (0$ V@uXaAh};griRoK2[ UQUѵHP(DHgJ~``јJ:蛂ѕ t|t=vmF*TZЦ튑Krc!Z XK3eǩ\gV5^,ulv.[EtR@I%VW-~hmWqM9V*_ utƥuSw CLr0(jnPL0e WazVQDP[}>F۸9ob"Tny}%P`&Lc.y z\~{ /zR29G"j =gmE U&^rJ([3@ CVڟ&nX>n&~B_AIįjjW.UI-U']m^qFmfD(tjb!P 5jODq殾$hT(S|G!'Jill`,XZ}Zf5]ZF2wkoI !'YQ몷kѾ>vcXzht 4_hd;v4v=[TCV?mft\X u믢zJI]x,w,ʉPV2#m8yZ} Ay*lkTmDQ%),u >Z][[\^kM.ڣo[a匼+vn`LB6t…V@:Vet 1TC8l7(3(GH+b3JZ{SPHE?T׃kYHlOR9belA] gq?{=Zԭ)q.* ˢbdb9ͻ=Q3=D2Ώn^&zf˄ݬ# = Jut_.P ;Lf;^y!lChU јI+`W/ }9[1mmUs:,#[w@\5_@ݙսNnjA1cEJC-vۆ*9qy<>Τ,I_A+x:Ү¬ t6* Tu?bOؾӴ p S>EmbBa| ;̾Y򉨥!8g0P|ʞ|2.9YP+J3[*jպ:@40l e6ƨ]|;]ήaP+-PjwusQ;[gjԷ3Tq?AX]Cc'-_TXiUfoYHH# XBStBU4POH!u$7FP\1RS V,d7;7(Үznf#2,ڡ % (/-IDđlKK[zj!|Vazb??mcmOct ToجJ?j=1w`:F]b K GceS"լ\ @4hi%=CA=2-rgD[O"ex.,֒hJeҥvScT\SSkqYzo/|%~-J"\z*&%6`Ric0`.uZ'1ee4TeЊ55PF{XR`SJoPu7'`ZqT7{*I ╠ld(-J-w~EV% \!xUJs\b;o,&Wxp\nd&,ұ؅nXL&aGK>)Ħq=H5xNi6iTfqWql*9?T 8?Pՠ>VJR*e6`.lWW¼T[,:9,X}mghh\?^EVk:KYInw^SlیABlzLRP hƽnjiL^BB/D YNw[qH7XU8f@sR\F33mo>k W!rW^`?àǬL ˹~yqZ1z %q/H6]X 6 rc8E8isOo?#@@6:OWX3!,~ i.ōm)VQNhi-P]T^&4@`Np= 5=#Z~"ցa|g 9uefSFBʵpnpWTD[$M1S]N7@4; *QX[ޥ*,au wIhw@7Q9IĵN#[_Rps+mbmU>hzXD4Z'hqN\\% wҠLdQEsPkmSszhmeVqt8ZٚĪ iwpu<r"0'|h`*bL^Uh|nUzSΤa$Bbean@U0"3nCS)j2'p =)$K5Q.?dI$I$I$I$uUa$I$I$I$I$I$I$I$I$I$I$ g|$]p[I$P)6B).̛I$B``I$I$I$rž)$J)W_$z?*I$L9eNe?I$I$8R_"O?PVN%+ S~cqUT/$ߙ^`6+Qִq} :EC<1:J$qZq&f;]2_9&CA p47V&u9k3dAL׬#ޱ2YXdGAYG) yo`k\RSdo.!=(kJq驍.UZlλyzدkYp0zu|n8onN} U3jj=#6JJˢ#!(wٳ%7vss3#%J2sNrQBSs{:&hs=Ma+ע$䑥t_J@͝D~ޥ͔ylGBe&bWewAr+*xCA+$A_\ET弢6С}87|nK; .9Wq5D(fY=hc%Dj՛wTUTͫpUQJ}t`&4/'JqMi[ӘƘб\jaJ) ] ULܶF '8f@>1<%kRB$4*P ES/aC\jI Bnab R-JN6ch| °]JZ޺FNSyXW5R+bIjC$*U%=YƖ"; jECSʣcψ}eӞ.\r˗.\r˗.\r˗.\r˗.\r˗.]Ge3fYwuQZ-WUEbr[V.hHSٜa^CCŕ._lEp\du5 K/7T=3+4xekeҸp !%YR.+7uM^TFs/;m-KԨU":Ŕ/^Lqz*,14 U歫Zrb+GTS;7v A7zm42.[52fsW/q݇ v2=iJO}1rpfAQTX%+4Prհ7MW[94jo̮4arf"0!F;B"ڷVf vZL۾`z ˝O%V-XU 1mc}:ޚ~a/D Y1M޲G &/qX.76[t5J֢X]j l 70,<<<<<<<<<<<<<8< 0 0 0 0 0 0 0 0 0 0 0 05n0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4M<4K4<4M<4<28< $0 $=Os =<2 0,q{@0F0O C<<,<|<sü<<03L= í2}>< F3<<=95"$ 2D4O0 N4W$0=9bJWuP?`C7?L?hտ4<4~O.O  #d'/w_3s<<I S= Ӊ?%H,4(# 0?<2<<1-O(pJ0S??10RDNOL s5+84Î2ӌ7qIϿA$,M,<<<<<<<<< C0<<<<<P8 0B !  <<qp<lzIw;އmC6g{M}&>lzIwм>AS'2{<٥/f?36헶~G] ێ?.g4}gXoW?`z4)wg{_NM~2>wIwܞH58OϲΒ1]u~Xb^1E0VW|&jVD !Ǫ_8G_h1{("!=((bbnF@pSP RcT8f^fXsQ=dP 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O](G)iQf7|/ +_W2Z[9rTMz{QA6?f=5ɢ=ygR1D1rջk7=?DG@E3 (Qu7{CMJ6ʽ j•OB:.0ă"FQ X/14D}x= $sC- :6 }H>B$lE| Hk1( +ۅ$ iy{hh"˾DѮp<@ !Z %BW| =f,UptkM9@d}TޟL5E4{&Ou$ t Tj_$'M{෽vVYwu? (D4izgW, DG\ KJ6Zv15(xqF$#Ƒ=,MsK 4izc._Χ)ʗH$hkSȂMno02? ,1;lUME^k "fA#ɃPBE@R>dL3[x40""@8p}"gA /?uLQ߇2J*TVD T&'i:eJ|B b5\@?Q(qC)g Hr(p&ܗLO'd2|> O'd2|> O'd2|> O'd2m%#ҠLAsLFҗ40&.solg#T4mK8#pۀo=.  3q}h-`~'JĹ$7ف*tǪ޶8;qbs8!edS@op% ؤdqрA0Pj1(f<K^0XR2Npd`:  rn.XbiVhsae &3ã8*\aK#nWc;Q:"`:6.ZpbbJ#pȤ%7װ(@IHzXc-]#7@+`ϗ:6@RV׵,U  ˳GDOeEa> ^Qcp[Z _P\|.L:q ʴ{o*S[hCTPط%a@~1=ibAtRbbYl<){ u<5!G 56_r*]NB#E!#XzhBοO˶OpRӜ6ū)#Ʀ !QՎl&0S`uP']8p\ ICbD :B-$5p>Z :γAVAA"]<&y3ɞLg<&y3ɞLg<&y3ɉ0<Hr%ɮy!ڢ(ꝹP ΰ1#D`t%M1|VԖ.Dve7mU(mۢG@R~aW%RE>۷nC\9D* x'&ev+zCsu~%nݻvۢ含mXe\Yۀ$X454GT{}S&i$_+뮺뮺뮺뮺뮺뮺뮺뮿b̹'8N?q'8N?q'8N?q'8N?qYۗ瑌!^?~؈meu@e,=ܤH] dɇ``mfATBL9EϮZѣq p8> i!CKLtjߛ =[lFL `.Q3: 0lt@,P(qzAqu)J2%PJ5r;HlQ47lt#f$ZP|}xd(V4EDl&B1n Di7HC;6k B]PlFr_Xhj\ޭZd*v3l"T(KUm$ <(Q`{٦ZɄs@Y^.MDf!YV9uht]X\1Ndj:DXb"bY%3sTQ,Ȱ~_H"Pp;+8Ƿ yq:*bNŖ$$PȈouֳuX-$-#tuۍCt͢{(R5 QA]ҁ2%W`:1mXsy'uc0 DJͯqe EAK BP'|C{[&R#jz$zGa &(+O\[(xBt!Ӂkl+ j3AHK4t)Њ6\=97z^cZj}숃AxWq"c=+ϝOaȶţLtg\_aayB W pcU?j{2zL-cBHI5==+øb1`xFӑJX`@&**TBK1Y&',dC6\ ֌5 j5@C-CpCiN1'"#7@?{r "1غp&;!۬(vNc_MдcىxG #F j\+@aCU UÔNeomxSLZC'5ExYN dRXN QQY4TTj^]5uchmW\Kpjaz27p%*anCm77JFkT xŔTAR Ap(: -SN Df3zuoIBa>{;bi  a`--!j`@qH䒽lD[؅@\XP&'م2RPVq'* +a;*NB6"00V`むQrʽ ̀olmR2$i4c60+4e=^l3"BroQŠۈw$"Qxtj 9> T\TJ/]ad)ʺ,`΀Z[@L>hɛPp{@ {(ԐȈ7Q7͠#gqd)jT}(q>#'rU)|t^\̔#2C~_Zu>+;<`uO쳥:OL`m4|uoĺ4hfT v #(,JG uai)s](!$ 큌;DyB*:2 i bT\յ@&E*ma{TM-3@wת8d2 i bT\յT6K l@L bJP-±UV [r9>IS`b!ͿشiHP q9r& (RѭdIqIyË)A$,ir]Ei,-(CF\&6AK=bjODv8XŔhJV6 ^#ETXwRPѥ霷]vռtmBm-sI8qpĻ/XF`CKF̗941hTs:nuL2dNCU" dɓ&L2dɋF6fh Q&L2dɓ&LN5! "e$P\*<7tҗ er{Ph+tE6&PriyXs1$ 9A1&v@"5W}kf3 :+)'N9a~ظ#ΧID01"[RI#u7f'Y i`cgvjJ`T@i*jAҜ;9{^:)E(MaV/ߋkHhLWe(l VŢ3[Ys (h4ޥ"qQD&u>e2".0J(O3 +aPT a0T6GB:R"ir.7B UII|t &JF; "0(6`lK?ۀ0sr:ܰ4À~ABl_ I6) XpGqHFCϿ |'’JhejU&&lEYw9C`г ]1:@ڔ  ߸rSln{hBkCzь6$NpH: IḭDč8i@[  @w۸mӉgLU%6lٳf͛6Y+0*Hѹٳf͛6lٳf%h-OVZ5K6lٳf͛6lgLPU$bS'@{{~X xM8 aX 6%XS XU25X x \@c 6"5&p@FU@ NTr+^06"V=~nB(*7a7Rb'{b@ěcaobYqtň.LO8eλ:u+?8"Cޅ:zgL ]qw1Xp꺍sSDg ^r:'(Qӷ\wdE nQCBpJ&#yDCRf#H݆` ䷎3Z໅ ,7*tάGC&-mx7ĭ+] |n4Z4x0d_Ϳ+S@g# s-#,1 3Nwv.M^v:v zf^͹d4"W rFmTIAي`lD mh ^PIj5L TZU0߱$A`V( RhdG 3 ԕ('n u鱂`@ t(dbLZM}/ ڐtfUXAiNu+-A,P<3l 0+;m8 l'}W8Ep4 ӧG-V>n~[IR?hs~D<6MY|uUG8֌m3C`^d1(Jp'7J͍cqn];T*.،_eQD1bU>ׯP qAilbEw!5X! -i9v46]2oms wCZBIu4+/;wr G=8~6poL 8^y‴5mLy?&+4d Kڴ3OaH3,k6zL23BBuRahֱ5Z+Fkz׺bU7st(YW ߁:81T17Bkg{[2mbo>x0*]6ݎ9$P+!^xcݩ/xJd]t#NRw:4 7jj"q@P:EJ e1 YFI.;A"ɨm :beUP/8\6Ab ").tsX@aa:\=RA$E84hxlǼ^ @Y6zR*f|&l"4syZ!ΣD©@b͗J7.R؞nw QTWNZp׈RLbq#Ws&b! ;2n肤9T)/{SJ+9jZuot::e_(oAd.SAqhզ4(뾪kCIV65<*2 *hՁM!,sVf^%AQA1gT ^}˻s^b <? ҩ/= Otrý82 NB0~"|cM90}7YG_1 {mr/>!0U g_B@ nj9%hP`4l0\O:za+25 x<9tF.G6}lٲBPh1~01.CSsT+Ur+ Wl WleHb93ťS|hi&C8,1g2I 676)K&= U4%;v+lG¿#lIăԙՐ/CS!K f+"⿜ q0wRV~>qPA=D!Lhn94miW)u΢kⵈAP;wN% );`/l;xdv = Zjlٳf͛6lٶAEBRo#a բSG,Pl/MA19 cm|rD::#=q8:m~G'Gzhw1)eݯЎ،?}pϤrO:$y*.đL)gY(wNLN4N\^H]qqqqL4C13]bS,;_z)U t:K='H U??IM8j?xT4dbsV_~ݶ%qٮ;~R'T5ScAg~1o,w\a1;:qE~wsk!͛!ؒSͻ2@#ȼ2qXCP@U*”Y94Qh(81!|tӢ)l;)å9_=N݇$l Z`܄ R9$pM Wev]2u@xYIp9P0jgLA!e\u ~ hH&y\bt:Z*_%U=0@\JdǘEw?S4(X7JwQ\#||'o/8kóu~P&"#GWA*@}!-7zҡGfC@Ư#gb0|C¿)? XVyw)PEг 3/"KmEf:%79vCzk&H!`V\hj޷9wDChʻ譱I@Y6(ŕA e?T(͛6Y+G0hHթYf͛6lٳg=.pbR\&ɢ>GZ:ޠw>&GsfrpԂ[wa;_ƉB J$:t5CSC\n~ 'Z BhkqqycDX5Si)Z'dQK^OIs|A]f@N\ ~D%E,!0ڼuh01`ZX:@8U ,R\6t$ 3YEAr@" &C 8gxRRGIaHEk(w͙zʐ٬1j3Q5ƚJ( 9461n6hK$CGhLl @bDv9,SI9cscg:+/ &vGȃYT^_'u%A;E[fW 3D x=5TX FpܥH$b!.āZ 8-ZjD1 7BLi`@弨M(:IR6L0 ((|kCq#*-K+@< rBs|FnA(CQ$}#@=o-n1ךxzӱq/}&Le-82%Y.E IF2<ҫt2iѫN,~|&щK3bYvuqR'8q0e+OɝH}waWsݜ pDtD;fT=lUD=3 %Z2y]a*;I_/V)(: !!t`Z] M)t~mYN0()('Z:q/B9C{"wQ>o{zD1 ˥8mZJwvtr"A)t88 aZbAHZr)*(0Bw`p-ޡ"PE{ׁ۔Ut]gY= ] fY_2|d:h/O@Kwd:.x?WT6AXsuZ,eQ6ܯ?~R%ֲdHk;\ 7!C) kKu d `(=пs7Vi'uh  ) 'GeQXS"sH6Q51)@(@*2x~h@ ɮ2Xצ%i:."(!7bQ< -} 7+^F5-.PMD`Ah1:OJY6v8Mp`02 xA>I5<5/wE-g&'d\x@ C66 2ƴkXVÈ"6A!4. p v($E 9sQOhQfZقGVIFa!J$` `BtN 6U6tL-OHA־I4- @p%x:bf-Y 1#GVUpǴA,WU}ü8:_[?H} 9Ko~`R)05U6)Ddc j}@6+hasNWK|B=bP:)4uX Cad=0d@UhXR^f`x⦀UC,BU  w+(z>q/dٓmMqqX$uRꐁErnA7E mSn1I4Vxx\F"&fxn0 څ[ņy1 ="frE3PbcNqzV́vP:uS%@>X4~O~H^+\W`fFCrB׌O <.'y4z3,Y0:u@#rahp/*g's1dJ 0<5ke@:%oJI#ى0&5Vl\<,16 @`CLfrp:<S|PfGT# bG$`NxoaMuA%BąKB׸ ;8'Kz\+/jM+#L(@G uCuH}.Y Ry{6JM&-sX]܄Oc#|0Mo`o`l!:>ijcۑn[D,7} KjH3$:g)ZH;v\h(P:Ph:"lMs'&[ ƄN.; E\ק0. mibej#Kࡎ4ymngl4B/S9Bh:p̲0by?wx }`w@kAT ]ZvƱ["G"ݹ9cAfPb@@A|wV&4v'Rb-?hC$*}$(iWj 3tT?\: aGP͗v-67AjB&^s&IU\0 )bPveE#0S K`kpwFPQ5QETd/%GKM F Ő(c FAȉlҊT @@[u16^ 8ͬ-kݿFu<͖nr|VI  ?mՀjZzZHbཕƺbƎN4e5È[Wdbɬif+=5CP%Ƶ͜`%BX`-InNޙ ~\)xЪI`t8LH=qi"~69 (dc`_.#:60B>M?/}|7NAeDkC.VAS)9KwivfjA&h* pJ0Q(RQ#"CQVP( ̶M)Cv`@QEG;7[9ITm[v'9` Ӈ7CBwSh̚/Kf#c.@o8Ox$%I ]t Q1kzg?t\$O5)l1)h!'PV:tqGE`]ϴ5zGq` ӇUqh*_$%l*:O7cpw~ AB**営EqM 5n#t͔fU(Q5jee"S'чzXGfByHC*Q7vpJH(}N4g]rpZm ~ZRkt $RںHܨK x!Bܡ6eja|HS14uHg:kDm>F*`:nC/X/.!F _0@Ɯ_#v7hqea24 ف%"}L<[G]ro୴J؆֨o;ʃ\ѮiU@|ǡU !7حrXwRr:͆ ` d#[ q$ "#}M@hA;ź V`\zdM|ghbA(Υ:Ҥ4 I (%"5t "P"uJF㕆NKhYN{&wyތ &m)UIŰK1O 4hѣF4hѣF4hѣF4hѣF4hѣF4V!-͐9]Ed@v醒fN( 5F ylp(TP#nL @WT`@u<ۊàE۾*}QP@.2A"Z9tb1[EU!wҐb2 vEr]ܭaE *$<$օY QVL"K@+$HEnXpQ`2-GTjLdvs krfEyw>2Ģ|Q@eDr7E g4SvNm܋2 5AMHQfs_-s[>^0}sG:n͘,&!JmAN!a( ;9D١b%Q> stream x  Om7x4e endstream endobj 12 0 obj 728 endobj 14 0 obj <> stream xYɎ6+t` Q [V:10 l@N0s*dOA KR˫Whe&|o|+۱hMs,PYB/g9nQ0žifqW޳/e_R,?߿?̢nIi'K:԰7Gvw`ަ.첪T# y:UYJŐy-Duϛϯ$M0|ͬ$w6Yfla-N@oK5%"^oލ¢: LA g0cpnszݫϹ^-$)-p]Zk4,Ί)UvFSc [}9BeC"1JNH/,/V.uAϸ 7f®և XNƠ ȬBKO-u 1Haz ,!h @g f駰|&x 52nrpQ[rWM]-1;d1T:рxFU.X rhLTPQ(2PIYxRt};ԏy2yS$+DoF"IOQ!P9 Z{"zꩺQn*R̐I&q( -̲bFfaj'zT(Y"+.Y@ $;=d<R^qCxϒlXf~y;3ܗ$?|{͹bh?X}.Es&aڐmC'LLƦ c/b܈8hG/ ~yuv56@.PGԬt_wkkN)G9119/aN~"|uc*3uFThQ3o<>6v>OpzOXXVt{Lw@ zG$ƥPRp+FMHqcEw8n&*-+gr @M2EzIA2Srڈ]R8w]?xkbdZQ;i N (9(jLE^B> stream JFIFC     C   8" ԣ'SڧOվ\덞ŚkkLTr+\g,w\u돗ZO2l[.YtV*5zar- mrj `[5b]} 11 LC11 LC11 LC11 LC11 LC11 LC11 LC#PLi̸\gmoy4 ]/7Vs}hк2^+t\]hKŚ=/(sIy=@'2%̺v胞7"4Ms 엟]qnEۇv]qnEۇv]qnEۇv]qnEۇv]qnEۇv]qnEۇv]qnEۇv]qwa bZfGO TښU0 `skMt' j`bPX7f6fN6dFJV+eպ+KtT zSuW:n` *U# Ʋ@k[(1ʹ]Z%0Y DM Wj#@@X6vmmso->crZ;5=QOZr*.=nKֲ|gn]}.']rs>?nue㿼kt~9]ORu#vm߈v3ֶqNkg꺃.U\:ۓPs'vtK9'We\ǭvwnot/ޮ\zӓN56;G䵭zӚrw(:O퉺b}WOԜ&}y(8Fl<ߣN[ceɩ!qQsw`ngjV3fNt56yK.Z|闘*&p_]WA>VAޞMϛQEN1<|'& Q,6>^Yq巩 /ÙJ6j\o\|O?-H_*97oz3 z`ˈvo8֪mcOFL\TtW7ns|5X fYO#;8edZ,yaY_o~9DuJz3+lQ,FVv to.M\U P @ wˊ>-, aP?325 "0!6@#$P%`13/]YQp .........................................................................................................i25Ko'{7DJ}dȚ}HqӖiypٓKpg"f!3n #Lŧ&{!RHbDS?LnD؏3-g=s*R`.^udHgmKJ Ş i5i`AǺ,\!kAɲRd[:syq BPeK[l)F4l$n7ß36J|ւqzm(۾j^]R 5 A-mgl- iF52[h-?A8(t&MCT.ƍJi$p? m+&㥥m2qu=tL\9g.Z</1ˢD2d*&b$0te/)^VJ27 3[ty2MS!nc[#1fE1O+܈c7'4;)fRϺlɉĸRyR!R!R!R@jB[QimHn*A*ȫR!R!R Z.*B#ɍ,THTETHTHTHT~ꐩ w!R 7 =!R!"Lh*B.>;"9)jo;^^듞߳i9w9Jrs>"&;o][ӉD2[YBk݈XU-V,AZjIX8eV }B,_,BJlm,2b ߺ,B= 7sb!b{~dhhr,BXXK3a3b ]r>}g_}Ih44 CAc44̞CAh'W$$THE(D$Q" qQ"HIPd7P_a8CAhh4ht$=S,..&)Jl;*Y2kUܕ=_rIuB:'Ǎ6^YGw31hý-qۛq#M̄"[V;㗟"pg<=&䗨] uBF #O8{,(AD8%o] Ϛ/Ifaǚl:,5biތsBRXǭwuiPisN_B^P,7@dG`|ӤdZtxqgȵ;!$iXcCbԆf|8t@[]V vYP0zn,5biތq+,icee[Z+g^*$^P,7@dG`|ӤNk 1v*Ǐ5 Α*!~{8t@[FRVviK)R.J5Od?t7VkX3换f>iֈ00͔BPGkV/eܛlB t7YPy%L[d]c6kMDI\NJm衶6"kϗPtXqBh-l,;GF^nIFkL<-bRkX3慢ƚ}x)BtVj{VZծx&iP˵E}~kBԕ@/N+zwzw1Q!Aa1 3BPRq@p?:h꤉9&矲n[8rmG rϠlM5BtgDJ iMi⸖pڠ f*& mwtaGd% n{V) i#Ւ0t8_Sb& Z`0mJd6}jG\|mf@}Ӄ_Ɩ:S*KJb&>'H&mSe]6#+QZpu} P.qX#f HyO0r7)%1²&Ph-T!3a;&'fH,-w(&w!:ֱyqt֖9 _e΋E3o?9.A-}M51trŦ.| 8]X.Xx8:_Cſec)cK84?խ+ZgMWfIV7ޱ}b#4M m"X&.:n:2OlM+xĶZ}Çk%m$.[2\gVV"8 Ɉ{_$Bk3hĆɦ$jm5,>/:]!!3 ]3f/==Ǚv̻Oq].yi<˴e{2=Ǚv̻Oq].yi<˴e{2=Ǚv̻Oq].yi<˴e{2=Ǚv̻Oq].yi<˴e{2=Ǚv̻Oq].yi<˴e{2=Ǚv̻Oq].yi<˴e{2=Ǚv̻Oq].yi<˴e{2=Ǚv̻Oq].yi<˴e{2=Ǚv̻Oq].yi<˴e{2=Ǚv̻Oq].yi<˴e{2=Ǚv̻Oq].yi<˴e{2=Ǚv̻Oq].yi<˴e{2=Ǚv̻Oq].yi<˴e{2=Ǚv̻Oq].yi<˴e{7hee L`rְxYӶ ;g˝%u4,;\ctL]UQ ktmi529/sҊ}B`%]5jLƱq 5Wo:YXa:µܞ^K\>cllkhO VFu- rw%q\zL1cxCRѧӑhi_Rƚ CnmNL?S۵ۗ=`l@p6Iu~K#A,b%/ۡVXIR2Ft8Qq#hex(psNg4bI$d ]V%BAͯw9 +_1Q? sý~nYzk_H6.{hWR0GG?A 1a>ƳGv7aXsV8F3,GbVBY0k5f{b@k߯WI=NMiuM7 o<Tx.]Sy຦uM炎/e_]&i,*l.]O{ພu={ |S._w~3]Sy຦uM7 o<Tx.]SyࣃuyWgIrkMo<Tx.]Sy຦uM7 o<pq{.*)0[Ie9Wgu={ |S.]O{ພu=7 o<Tx.]Sy຦uM^˫ʾ=2OeӓZm]Sy຦uM7 o<Tx.]SyࣃuyWgIK)ʾ=|S.]O{ພu={ |ߵLTx.]Sy຦uM7 o<Tx(]^Un/-,q΀ .WK.iA\=QµJ BԸ2浏鹵Z`襓F&[6\%0Muҝ׵BcJ}8$Ѹ:@2 Bח )D}dQZ)u4* jqe[cjhuFAXJ(Tt"U4\v"K]L.k{ ~ sKʧܠ+E .yTc]eZiFkMUĮW8 xqgr#`Rδb}^\U6Jw6F}>{}2Yɩ4M/_OroYZ*s "թ[xZ]sZ5 '8ISܠHƇU)<]ph-MX[uV0-ͩ00m)vBxY m[ps{Pرys%ruRڨq IS?Z}tc-TMdx#I"'EMyk>b>I}﫝"'. ;h3W1*VX ;`ذk?p}vQ0w'rκRvښU8?8 D98X&AL5Z.K~X9B?ZxqgBM{jF_AҕkcphX9B?ZV'Qݔ5W"Iθҙ.uNwk@SFи^mQ#P/#D nњ[AJU5un~_>-' Vk:<^Sp iq~9K$@C{]֎ ym9⹦126KZM+L|diwurS'"CE:% ;6R`j -sXV΃VJK$.:[AkL3s.isM,#,NYJR=a.)"ndd 4>Hgi#{>eV^),ˬryS0a"[ֱ-m<3ͥҵ[Zf &Mso,XVb&Ҷ`2A/Z#ď9_6(Ù8amq $8[\LJ Yܘys+[OwAdAdAdCR,2JF$̑'7X+ CR,,,:YYMnp4YYDVAdAdAdAY\_K=r,#B,,," ybM8 ,5?N_A-.8B?%iŸ.i_*iŸ"ܫh>p4@hqm~O?E[.`8qG_5mҞp8BK'RwҸw7$ )v{G~ۿKGArIv-6a3+r7/Z`~G)j[C=eQi>*Zߡn~jڶjڶ,bFPP(l5-jڶjڶ"@nڶjڶjڪji.jګjjڶjڶx5ۤ`4ڶjڶjڶjڶjڶjڪڶjڶjڶjڨjڶjڶjڶjڶjڶ%֏ 'wJ,T?9JGXVn|!:%!ai4ԱG雇=榅ҿ_ C>Giq+Տ~XwoٞK9#3ÿudeֻ4G2[X\?"i_w);,щpgObw^Rr4\Gkqtn ܋4[oG +xV;˿u;C=˄+fLJ'W:~K 0]s 0 0{(+~fa5&s^#4sJsX=uCW8# k0:5&4IGB3 τ.c>B`nB֞m 𮉟 𮉟 𮉟 q]ASܺ&|*0#o'2&|+gº&|+gžݍ:5"osGsGsGsGsGsGsG>ՐT֕rr$6T5{sCKNFû59WDυtLWDυtLWDυtLUe@tLWFυ9@ijqq ENNk]%жӥ18ه/-c\^`~m;ή?{YB F;v  F-ku+E8,5@e:"1xmm͍euku9ki}暖 n>@ nvv ,HX9M}'ƫ8- ڥ{ra_'0c 2eX@j+tqǺFvA{i} Bdb[S ??YTa e5Xtd(v"fkbq:gE h94U9klFJlsjMZ~Y3ݥl->qpT}NSҭÇ ^#iۅ(BqZ)#@X&K-hck4kSÇ; 8g2W4UK$ eamI5ի|>FGwc&hm*HEwv.idm:6+ݗޱ 6Kh0`q^nj;OY,o)ZctfК49c@ZH&@BdDe<1H6Vj-qiAɺnԸm0[ÜMkLþ,[LV0iߊ1"'*u;_)E':{3X4tL0^3ˮD ^üg@P ǁBl7j-mݣB#g+r<8b$5_ao0@PL&peWpg07d!XR±Q^G!Xy~=dBXVQ-Ӊ~OCϲ AlؐFa;p5 arZ3JV ` $֦ZU` ثxGlR S*'*|lr.[2e*@UZ5MEIr!X ٪na ?M B" Fa (!`- ,ZCVOF b.AT rFP4L%M[ " UbUA bj&bELp8"K x#g$̶h(UW#&@c ,RY*1 )xBB^ ` w2uEL#&4yL~6()^˩hhy8,Yp uU@D bRv+]A0^ʆyfK 6_@ f(+#M3.,X#C*hV N$FhL ?ĪD|-^H=}`_Dm=քٳJG߉V N$FhL ?ĪD|-^H=CJ $ $iG$#K-&e/{"ڀnС4QZ0 \A",Z[j@j0Ukq<G%i)<(,A,A^B5xGģ 5UpH0l-:Cפ@HRNC.T8 fx|43)0{tCDhJ(q@U:#I5U1 )0pA%(jQT`ek/X5$PW`B^T԰}-z}! 4 Bxkѹh,y$jfCWz8^-2JASP B\ 0 Z$ \` d*}}Ps*KjA5B{ @pUN vǼ-T} Paa%&EFa- \%Bt^:cPp"*R.S& :NSʬV)@RX ҔPU11BTuR.ME D)R~ @7HBeE((!M%)\lx 9Z Ն@53PWERXW6tA *X` P@S*n [ {MAv I,I ØQjYF\ YH6$GɀŀP7פ,0S'a낇8ՋIe ITě,0$)`D@š4f V  (z=p@n'QV8P"E(I %PtS$%ԕ!t-פl^*6E;箆:т/;V f$B 'Dɗ筕UP ]BP¾AX9g 5bX Qh1V D@`!P^q Vvh0\\+$uaQ9H)&IT ȘP~*oMNMi!t!GWJ%^a aLʌ ht,L_-aLg SJ1jh@嶅m#VgozNuӮt련g]:NuӮtDk:NhW uӮt)@κuӮt]:RuӮtSpyyNuT?`E)P{ qK ~_5 b|sBJx5,W8o>_LiUlYc Z7 #"ؘ;=,9o_'  D!q G>aUupTm<1{/_qqqp NiƜ $x_ l 4N0@8Ӎ8Ӎ8ӃSN4L˥Wq ιM8Ӎ8Ӎ8Ӎ8 iƜiƕXiƜ Fqqqp2Ӎ8ӋvfiƜ׍x׍x׍x׀Ru 3^5^5^5 CL׍x׍x׍x8`+:e5^5^5^520׍x׍x׍xׁ?S^5^5^5^@ N\> FN 1 aL%3I`m@xxҠ+zP E ڕzLs7N!*K/2qo* a/SWmf镍sGǍWLycKkq\ScBj)j84TT};@xUpiKNwT> B 7,, $W8O 'S)N  };*H1UQ+&SYN x*3d-7d*Y};*3uϦ0hb!$KH i;NpsiNM<'};B¿1}b8Bo0w-+^j9 N&}-u#SX 6wӾA]N31MDcGGGGGGꎅ|GGH4)H|ι:DN:Dc )%JN:ϩ IL>tO>tok5:|u 1yXH8ԭhu'H"t!XApّ:DҺ\P=3OM-a7 CBAv3G$xkŌV}"R@k )fq Nx(e9E0F|@dP@ VN3^K˩SrVI ,+@V71՛T8a" JP]! 5QxAV Ws@E2oAI`a,TBo]!(bS5*j5#T+]r w-  P*+n Ѝ$k ZDVtJ"V3݄ma87eQ.Y钚T,q$(^cw EB@ ycH V3Vw2Ck tAeGj-hE 1EB~(yjLta^bjdE3-QI  9"a[8Vl@]r~ | 443k@!J:J8@TRrq$i! /$G Va"UQb BX|TyWMP*l!eSiFTaʥ6JU $h- Q0ɘA\DɂD hNtVXD<4L 9A ؘ9,Ѣ%BPBB$:`!d&GWOŃ}}}}}}}}}Rp0g>'>'>'>'>'>'>'>'>'>'>'>" 0:sη:sη:sη:s, d>'>'>'>'>'>'>'>'> (ð I(d cAC<<<<<<<]v߯^sߜ]z<<<<<<<?Ӎ7?2s?q<;?<gt\}w^5 ~͟<?t̿1=6}~묶mq Ͽ , 71? 0Cp????00B<0 ?0 ,/ 8?yH&o1S@!N*  ~czpD4Bls~@ 1?Cf 4aò=IiPc$7 3 9D,dž%_z:Cyus | șCRk7\fY+hFgf5&$28 7gJږx 눀hf `7pZ@`f`fUwOBt| ;FtM3#ܾWr[,Zr6lr4oEň㼸1$M]Cam(bdH@}K1dL;a8*XA=f* UxT%Lt˧GڌBrmep=T)L"Ftb+ u6N@Dc;I-q8$7Q,$O H l4j.&LIdpe,7gk-Rڵ@U4ĭ"2H,lD4Y7@`f/"Tj%d7!cKR[I#"Ҽ7\wPM`A/h_( oVhE)m #bQ8,* CJމjV΢Í@" BO9gN;øsI;Tln&yF1[JrӰapu`7`z Ag;n|B5 np^V"am  #$!`4?a08-F̨p9P,ΝsLwG1sTPZI^PM`Yd` |4(#;d-9AAb u绸CJ6P#G{ D2  :c-ma-fblktĘex d i?=TRJ2JTPzjTPʕ+RA*T_?4IK8np Nzqqqqq兆&<"5 Ti`e`=\$p(3QEb3~+WS &7-!1AQa q0@P`p?LJBkD6! |4\T*DCJg2)obROOe#P>-89[1E7z[ԡK#7N#ni KRO=-6IvBzNJ}kJlUWF䄥"K]2 ²"ҶȘ        fg8h۫o2Ze> Eͷb$}uMn$|C]r[mk.?r̷ݾrPWfi۽99,|6EҙG=m\<#H9mjj"GE+IKU}.=g!d2 C!d2 C!d2 C!d2\ͥƔ̽ ы/CRȊu)}v!mi5M"tIneM/m6u SxNT/xZW4X,8TOB"tIeД k km,T|E_;/x cJ*T^T_J J*"JTQ*Tp_W4Ι:gH # [$6Ӫzoki8j"v$I'$Ǝe.Re.R%-!1AqQa0 @P`?Eq*жBG'Td-zI0˰z`J8v'؜;bpNñ8v'؜;bpNñtC JbpNñ8v'؜;bpJbh;bpNñ8v'؜;bpNñ8v")] fpNñ8v'؜;bpNñ8v'؜;bpNñ8v'؜;bpNñ8v'؜;bpNñ8v'؜;bpNñ8v'؜;bpNñ8v'؜;bpNñ8v'؍ #O*VTjŨ w (^CE؁աzІo7dIM@6ފ~&lii?T97C$۝6=RD(VRhql3f.a?Z Ԓ-hiv7q$KtKj0D~ajz9 5(mHVVͲaK8D(Јd,CҔ (^ցk95v%ڰ.?^t(aĄХZ̾"Zmj;Phſoyxv5톱ޢ yaNeiE10T5bBhRf_VC`QBk_չ<^ŽQu1.[lëU7nInCB[ޮyr{D = +'}wZ-3ܠ5~&k cB08*d glA4:і<6e(l (M`&^'tPq̼KWX-TƕGx0?WTДQ iAC]W}[CADL Z7{+΋@΋Uq YW^/|^KCEL %.Xқ} $n9ȮXʻz6414oJJZ K(+R) u#lqաW"UZ[zʆ N z: u0mbs[Sxa8Uފy\( [h OY[ֶ qʭZẗl#N bĥV 76am}X u`zapbEzDiP+K]4(չŠ[ +!mFߠ'۷EiPŹ(j-3%-U]zR1#N b­w]Yx9V *u56K3xtP0XBSci]PyB-d: u`z^+|3¿{a ,XnYN%Vb[Z\ʪ25M5Mצ=!Dmh?j:z~!bX.)trS~~А ` d!C.]_-YH>G8TŲhBijpu1ZMѢ-*'^/Nv=UVARV_35VjW_̩gd˾_HteK-.eqK 2"CS3sbHɄ3@м k3DpBך0}?kvQheMc@*5{@p Y{,)5A[e;u:zFJP(n L)DqLQO`HJ$@w. @ 'Fq* ?DY׸eLn eCn2MdӞ*Юt (5\\UKe&u"E]4hںT -d2fPA,L cQ&eddd{UDԻ?SbTaj1zf`HV!q4i^Y2gG5 '|k%p&UR&@/L•QKX7Z+x_P,j'O<x v/ ݞ<x"s/ ݞ<x " ɣvxi'6p<xѻUK<x 'O^ '³u<x l}` /fx 'O'Etd YW &ۺWO،\.v(5FQ`eCCkzg~'~߯o+JTa= jV=K_~t 1jxĢLu/>O׽fxz}ߓRɉB hx]BjqE)mOj:)k5_??GhwpAތuP@w6RS/xz N{jf/?_======= _di3'b=wTO{{‘Z:E/@)C3+xx.RO1˗ "0Z8V* " k!/N'=======Z~W `*U-u7aO})KvXn EC ~'_<xY,O!P%ЇwN}'?ȨxjUS+$ǟ T%P ?Y)w+(|>C!O'%47|1|[f4PkW+TZ:H1^ۃѲ+Yif@0* T]A0C>(C8eκFZgu+ihX,DZ:W +])MЙQ :.i!E$l[c¦%1^Ue(IQaym^7 kL´==YI4b7!8?J\mtV7f,X589+LWE&` h"4$ǎ0ڈe9.ӔU+XSAvuqjmE\i ^aKƱ9hg]Bnݗ[JDdnѪ `j u%%,En5+rՄ=DQK%*%{!t£$ ,vI%lz1DLn@.<#B+XA`~<JYQz׬KSbASwI:mД[75$4ĮX< Xu0?SjOaaia)ڵ &=n"0lZlce]UQP6v`т9Sʲ6*NgP*ږQaƩc!cwgЪ /^RQu 5e\$w)_֫ʻ ̰R c m+MGhxl(:@l B҇FMuAceK"FXt8*tZ Qez7{l}W.r_,+x\-zzR *n/y"AKXz1 Qm4h'3# }U_ M ,6 EF*PP!58V[uu. H&H&!bIKt Ԏ!(Bk*m%A>H"D$Gvu@_ԌQСB (PB7 2 (VESD|{>=O'ǿ|{>=O'ǿ`-oKJڡB (P&i endstream endobj 17 0 obj <> stream x1 Om D< endstream endobj 18 0 obj 294 endobj 20 0 obj <> stream xXI#G W9`O]4_Ar0r,K~jQIÄ湬Ri$}U&/_F7U1|=/vMfoÇw9x,B ^"z0Zc\r_jq[ُÂϟ.ceARR,I'I΋H#m_t~_aZTgj <12WT w0#~=ޒ{a=G1-nLZjoSo՞m;;4 cL)ۓr%WY3E{6&N,-쨢>XK6㝊_>YagHzox2ڽ> stream JFIFC     C   8" 9' [kc&tz슮\lq'>{C<匽sIܲ\Myƛ'SvKX*I{p1<%s(:eq;"GGmHO,}߲*x JV@7pwȯ1.4 cVv-111111111$vcOeY̹vS}0C7/±R m+-+*Bg ʳb?-Qr,7K\Xtyeo?KX!>Słz͑_o>Y6,) |K\MJfp+EY[&`\5eq"HHHHHHHHHHHHHHHHH]eRTē5 8[EIR;H]9:ߤ:;H4#ݮӀ@$#RW+:4T/r8]gAgBL@Br8]kjJ#ʫ#:A] "Uݬp%5moJ5Fڊ,JuQ-Ө$즜WOi*OmSTKv~򴢳ɺ-oZJ$KSV^]^iZ}M2UK¨-Ν5-KK&UeW巢V}~~K?{[7Vd]­[ G,Y=ߌ\lFv{!Ϛ~s{1~8Ӥ_!LD_ϖ-a@k|yXU0[DjܒjJ^Kϗr|1gT1"O|2k-}<+ȭڟDÊϠW*~cAׇos/ߧrp2D>e*0oYެ57,mL"z^)h}?"~c@::*K}r@lM$?HJroVpʅx/fV!dE)$H_kZ3ֱ؅}ʊ14F4noVlb !앭[!;̀ٳ\z aތYFk#J /_)B[bMn2VzqkF `<}L14xPY 0 ii@`OP d 5ud'%ŔvR[e> w"S܈?3Hc@{k,~Iǧ#ЮljNm?mi*afܖ#*▏{ P~{xPY@4025 !"#6@$%PF`1CCb<-Զn[un[un[un[un["n[un[un[un[un[un[un[un[un[y"3GV>b;0Xu㖱nOI\7O{;1&lF}2*:c\'6C&Dd* (c+Ë \ Zf(Tc,:zO{'zzic#bLlսM/3wk]j:9&XFѹ&aX 8h+Ï\كcZH8S):8@8+9G_Nzޛ.F\3H}K9*laJW1Tb1Wzsڏ&Z۱'=^'zk5Ņ>UNr)Oe l[-el[-el[-el[-el[-VEs# 68'#UlkVs [27u3P1zu(. .jߋu_`u'j XٙǷUy:r\ot +ӫcLcD9mXb籍ȀU1b ƗPFc8̝W2\Sq\ԑ7?[1kխu l oԥp2XN{ťOX4if Lr"`6܂K?Rs姉|HgkW5̟GcZz)fB\ku_`' eMel[-el[-el[-el[-exxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx231̋6ԯ`v塏FVfεwG. ,eŘd!7R0*3i i sc1e)Y)XF,-%VG>8 Uؑ`,nNױTVwqvrVM6N1'c^xzĩ[1=-91qOL .0mRw'[9)V r?'_?^ƽ0͏K5{v~z\[8>yҵ8?ם b/OtohO}w%Ky.]K >u]we˽{.Ar;w*UܫWr]ʻR>/Ky.]w%KԿ0ꏗ]e˽{.]wd.#Wr]ʻw*UܫU,߼鑹}w%Ky.]K 3~r.]Ȼw"E܋r/*UܫWr]ʻw*}.Xc'fюJWزolzȖc R3\$'#đ$\) ?3ߛ$ҰYE&G)cۖ%#<hD5Q3Ĝh(r ?N2!#v r`9G0N~g7) IɤiN248{r)*(vMlZ I"QيLOӨ o/W֧!5)E]Z܁$dIޜJS{6#fvs^JgK/tK&يLO6Fle8IAK!G\| [Z=zȂbBxq@@g!)f7-h+dVIF֐Z5x[Ա=pl37?)G3[Hy8_I% پ ӟoͩtHn>E;N-Z&U?Qk*aolIuJ3{R$?)G3ancʏ/TF+9LԱ ܫf6CRalmerqL [h2o5e-YjVZՑ[ͮe^f-Y_6mue-YjVZ#6oՖAW VZվe-YjVZՐYjVB(l Sǫ-Ye-YjVZՑ>Yj,V+ڭqjVEo{ jr2F_>Ϣ?ҵSOB}cfYɫHL<ӵ{ Lŋ_ל2!Mϝ.7Yre˕+"Oe4c˕\5+.VR#J?F$hǕ+.V\YrQ\Yae˕e˕+.V\YE+.V\eO_Yre˕+.V\K+"8@ jiV\=eˉ.&\L&\Lq2eChm(2eˉ.&RF#ˉ.&\Lq2o8lq2eˉ.&Qmˉ.&\Lq2k/\Lq2eˉ)6eˉ.&\Lj)9Ҟ39 } z9䥕'G+`zvj:UF>!ա#W0;36It}gE`դs (fB}@~?Ϩ >Nu._YOC-lfB=lȯCvf[2 A19e";kF̶dYmlȎډ f[2uCjx.̶o'-lȑ@ve!2ٕ 5(Q>2ّ>?f[2E""{2ّ>l!zA[fmQٖ̊ %%%%%%aKVZEiTഊH")gZMVZEiVZEi" EiF,KW_R1+TB6[,G+oXky[.ks-?/Vbq4i'({k+f,vK[B[F$?q%iņdTG6%`wF0CC.2ſ{}wYB;¾8?0 z*RrؕM 4uupe ^fF::,&CWcN )U0E`( F.FH , Ah)I18r "Ӂ)H1c CgHdC}Ŭ x64M *єFvo)1č쫯e]{*W^ʺUײu쫡oGop p p p p p p p p p p p pHq5P_,YM|e5k)S_,?; 1!AQaR "2Bq#0P3@Sp?3eqS*+PuڸwXΞ;*ֶUHkޫᲾ5?Ĕujff$|+Uþ{_-]e< ^|p`:#ZwIJUÚƞHߗogȵr| )U=S,(Z332[9Yǟt%J+.yPZ;ӤZ*Z{ѕmsKJ*$Dkv)bzHl~N荣i.8rX"eq=N6*-h}D=#p{{3$ye|3,)( چ#j sBh9"EŜ;, Cl<Df=H98Zb8qm6as5iT,/Y#gR3v]T7V'5>kHi6x\NJm䡶O6Mk =au얿ÏD-!s5iU/Yf.F'UJZn˵#j洓Gi&1⍢sNaÙh΋Mp SzzGnתPIm ]zgUmBA`,F'UJZn˵+K1t-2Ѯ-2Ѯ(NeTF^iO?`] h@ޯޯ6!1AQRaBq 3P"0@p?} :Op|##N/0j<$4b5 H<_(Pi?^|#AyՄh5Rxb4G_#W^לqU)+qӌA:"u9}ez gʰ{!)EqN"TYV^ qSO9XȌZ'\2 P @O>xȶOW%AXEu+ABT#=Y_uIU}~clOTI(9̈[u)S"DI@,z#Yj 94*9JssF3A-b.gnn|݁ aSVA G 94Ȼ0R..C[H]"7at>nF~V) JQcX]A3w-5Ϡ[Te—3k+g&`t9$lQ Wat2vU:2ՊjVuh; v/jN*~M Tq:-BA! ;M  1!A"245Qaq#03BRrѓ $@CPbT`ES?#Д-RJUСCU!I*E?u*q)Bj\P$^ i?ZJfYM] 7X<MoV&ZiC(eZi9A[~' Kl3I.f4vtk" >sJh8R/-@5NpÒӈeǃU 'TZ0Vw/?&S&W6\&Q"*m g ~"Y񒵄% k^~Q7V| m\˅Kr1 Xm* Pt|L"Qq)DĢY{"UM6Keڥԑ^F$m9%вͤ\ڢn3%).  I4aH|LomkaP L^YAYR2'Q/Q. di&yav-e m+Q J˶A2 Bւ)Rr3Kea# i돕ڑ2RݎtDœytgR32Xc av{Ψs&ٽ٭zԼte1$Sw֖=:LIbn?ig,uKvc:֭M44ĔF*ġ!) L&mmD7n*§RW.'8–zŊ6hκ!!evSDjBV.Ëu/?.=ۘxk DSy ^*]{HÎ̦MܾBEt|5*䢂\qcGL6 /NT9ӣT|,n&fI|\NGJ[Qi(RjFGTLY:@qph-!V*TZ⹀q(N≚ڥ[lN&mSat )Em}ÏN"T()W)i0n%qYrjV6r[..r0.|3}h/`L9ҬZ1t(ZR(.N;NJ`РVf.BPT20I/[~+Cѳ4"=6$jfː) @Du2˾^5TU!ˤ>޼Pv4 JPòYmKpr.$ ]i$ʰ`*]4 c̉>]YDʖ fnE)С7,RX,V]L!Vp=p-B] WشƖX9*l]/M(;"]$֥$-pTL^Fqٖ2i*'ēvodunKGef\̢g%q U!9 u5\NKs|;}ibMd\M~ԬkyN׾1MKHRۭc^(2̮SA 'O龙^3sk./!ӋMI([zFuS.׉}N}G iÜH F!ЮJC)i!2rն\gZuj^[!]7AiAQeA0ŦNo}4ě޻_{ה&I@NW\HJ0D0TJٹasyh=9asyh=9asyh=9asyh=9asyh=9asyh=9asyh=9asyh=9asyh=9asyh=9asyh=9asyh=9asyh=9asyh=9asyh=9asyh=9asyh=9asyh=9asyh=9asyh=9asyh=9asyh=9asyh=9asyh=9asyh=9asyh=9asyh=9asyh=i]E HO|IʼՊ}5* A!'\J.SГL: kNV+ mY(z&F AR^Fbi&ilJ[d+׊GjaWEx?Rd]VM%N)ܽJoS˺!$f"^er mN8zxJ1f"W\é/ ',{ pzUavC&QZUP3- д[;ٍxin]t#A#7=ZaV9R-5FR7(x)TCM!I|8* D+K,Z K+%H*II>[KjJh>˒m .!G!an.;e?:B􆈗u#BOPue 4PmRm5$9/uQ˲,%@069u  yL:4iROR#A$RC8:ڷ$n IҵI+^Pe_Zu~QqIn,y[dת%)$sa*U~N"++l%?IJ3"h8hu㟨e_I?j LJJ(Nc(f5VSPJ4fZ0קN`q/\rMrMrMrMrMrMrMrMrM6𯮮r_{bYNkkkkkkkkk{ ʙ'#~?r9?OG'! `v\~iiiiiiiii 캼+뫲ؖS}5vG#G#G#G#G#G#G#G#G#@¥~eq?r9?OG'#~?CX6ݮi5$xG$xG$xG$xG$xG$xG$xG$xG$xClo{. 셵uOG'#~?r9?OFr9?OG'#~?r ku22pqn9XufeOzRiA\z9{3FU).Ph]Q.JPN7Fa_*Rc{4kJtV> Src%}D.*@+)*JE`-)L%7&hJ0M!%zk?Gt"aVf4,(@# Bs3$ *BѸ7jׄ&J0~SRa QeۇLm3 5Bt'Gnt·T#@&v[g 0pej% R/4̊Aj4V k^ v EnRL_"8D'j)Qa]j֙B!(Nup5 Jh7|ĶߴV]]q(T ;A QM\IEvեrOL"rn||V-ُj p *+WB) ԅSD\G@$E e"%0kS+ 4<ͭPHEIpqek*].r^sM((w?ݪZ^*LrL%Nq)XeۇLum١^Cυ .:fp 7[{%6]mI"ZeeHt5)Kk ^TE3毮*i+PLI!5WVc3.wRq{(Z}/L}7 r+RU)XjARWi"DaDh]l!A)F+#S"_~?gꡬ,I #@[%mjyiuGȤRSJ=.P^HeqR-3ɸ =~qo,baTU=Cu3#i/F tK}ХaMxQbYemQ,8H*gD!N# dUHm={d#!FB F!Y0.Ҹ]AꆈFB2d Aq!jY{Bh#!zFB2d FB2Fj -rRtA"2d#!Ad"-u)) 84*L2WH'/oe#P聺6)gZDNӻ[O}PݘJ &Ue[n;GW* I۩:k̸Kô,xizӺL%8 /Pn ҨYG(Ӻ҅\;FLï2$]oF3첐}:4t mMpI|F[$loKȯ#xuYKDpwNĸ^q4MK6Rnr+]5WYp\ٽ-HWG]q5Ƹ\kpbmH,#`5Ƹkq5Ƹ\)"+AJAl-EB5Ƹkq5ƸⱮ5Ƹ&E !2JEq+k\kq5Ƹkp-B*0RRVwA\X\kq5Ƹ\kq_UX\kq+5Ƹ\k~Ƹ\kq)\kq5Ƹkq5Ƹ⑮5Ƹ\k\֏"uJD>Cn-:,I#FD bbhafVE+M+OtJ.M! x^Vh~m<7OqwZ>JpV-P.-%/q˯u."UR{ჸڂM "0ZR0~W̼V%C&´(SHz!כTJ˹; L[_sbJڒ+MUtn%(+ 9bu騅"C}\J!Bq=p[H]DʖNwOK\;ee8DѠeӯu:2/p_\F[vML 4K۔MzHLJ"qqJN Sg Δ0녚@qG\f#1uhSzs4Fb 1L* hu@–,j5Fb3t3蔕gHr PXӬeA3էeªBTϾQuBZ) {oBʪzBPdS &WT!!!)@f ̻.Gd `2DO%BXJUqUJ1R#1[☫HIUu 1. f#13͢vaGe(;RawPe/ER7C|)ӠFb3?AZB@v=>{$}Hc#0^v=>6P-t Y=*+LqGt&"A8888:7jE{c]m !֕qGtqGtB@ I(tnn#+M4">">">">">">苖GtGtC:]{*1z 4|{JE.:T:eTkOrWĕE|)%jJ4Y.42ʵ]z\ѕ8MMUQ_T(HM Rt+ ml9},@ v Ҹm 8Ҥ{G )hџWL\@!DZx(v: R?o>K}2.azPihzƥ%gRr 80??t6h%/2I:ajZQtΐa%ph4)HJKDJKRjMZ}PՋJR)Pތ Z'eGPM*pxhQM”"^8-:Ҝ%`KA-Z4_;\RێZZN <F*k`AmڥWOfJǵJ+_qjJGpa]RFU?T)ן﹔iGitvGitvGi:;Nӣ)hv:;Nӣݥ3Ӵ:;Nӣ:;NӣzNӣ:;Nӣ:;BvGitvGi[nӣ:;Nӣ:;Nӣ:;Nӣ:;Nӣ:;Nӣ:;Nӣ:;Nӣ:;Nӣ:;NP``U*o:NZ Bo.6>O.#߂ ev!c90lϼ`a€MC!CH1F&&ۙqE#c9$gm@ iq5 R {CI'{g.I5Y:HPXJY &{rZWh$B@O0*"djE,1R cXҰ6jN -/gA` & c{cQZi\6a1mñ6FG*V$0{8b$6Dao0ya4؀kp+' oPx`ʎ ),7Eo(cT{}=9iyimpu/tRP's1h ([ZoN8Z arZf mOjem&s,0vwlɔ̦l b8jL72/iDQi(ebA0PF8-?5`gy$mDX5-> DA&/;`,M`QS,D<;G !? !u"k1æ 쁫}3-)h0SP~OXl_^F+YbFL#g`i +(W+d .kM?Rgu ;GAgcV`L tbU7Y y EY,N)QgBAxm5{At 2549^vliά N$G& UUUU_Ԅ돤۫ǯά N$G& UUUU_Ԅ돤۫ǯά N$Gɵ+M&ffffG3333nHU $ Iq*(H "F\,gA0mlo #P# 9 f6(7\PY(0yJqe A!  V.0X dF*ɂ#A!@fc5KD#\u@-Cd^b"W^J(ć/J(HKš;NA"-s. vVGcĶ.jHIHo  5(Y1 83 @鄱Hjy:CBϧ`u(BBj thi`Y*,(Tp yy1 B.T@w $VA]|^CIVG y!_EvPЂu CL?w%q.:6AK%^b&P:#/C@Uզ ң}*6(|=$ ,)ɺyϞ 2xWTǤ!؀"o!v1?pZ#AYS.3B_s/A&6!,+3t+GZNh{@A@ <.BuJ(V*&U yG(d %i!v3VI i/!eLx>P+} VO=ODg"Y[F_0nZNƽ0jg8 A#wCpx* 9Lj[Bkף$y%a` A`@H_0}um|PH&1, j KGGsD[P=^\x.McRX0``K\ ,VTצ S&d(#V p%L+7hr?ТXz"A [0ܨ GįH-/ B4`a22a:0w/IT%PL"ciCE,X,]lAuGE0!NDP & Q -L\&ۘp#ēÉ ɪk ;Z^ l}-/2?@ڭ:x㧎:x*У=':AE6d_0 @`)㧎1x㧎:xF:AX:xg A+(Y:x㾐2H)0IJP8!H)!{G`9C}Qt[ʮ4+3ƀQ%4Za (v\zoUڝE_אrɑYC7k%9SkBlV/BA/ DrM7Ūl+@!ŜwWO=<48O=<hO= L m6 BAt)89)bIeƂAt$}!X@zyX'3e  zy@FZ<!!Dh&-yq!<Ё0@@`c奌W GrA!c!<Ё0秞8,QZ@Miy砨 P O<"xDM]<"xpzcAM <>x|2,;O <>x|ӈvHpz<>x|@Dr_j5<>2@ X .svMԐ ?GOS?GORhc4P/ R?nXe s[0F䱈€%DEJ$$TT34b_p"[5ӤT XDGd@ M ND(! 8FZ( 3M3Tb=f~::*$:Xb!g߲=C+'Xm&/A0PI C ?}!QTY&Fnpde`_ʕP@̨!X*H1G(]#8h cG;аL* 6 >2,5&jp^"!&lS,.Uf&$vqQoD\"CAԩ_ ͂ IL~ wzsC܈ÄXE'9K" am0 w `hrB6 !mf,6iM}+`I[0Dfad6f@ . A@MDv0"h5Rbsqv;9XLHvt*p0  yП~'~~'~~!pAg7=jM=֋h

mHݳo%戼xu4c2Rp0H\7_ x$\ X6;Wӎh{ [_߬t=^!gG(B{cҥ3޿/Z,OJ㎑Z#KPA>NԫK4)sǨ";ѓn@GOfyT{|XnhaOFZ-/ \jijj2١c_8ƙ%ݤ֣bh T r" j59m,)l_r Y$y 8%XqBׇ隀1n45jGagI4ҦDi`CӉ#J76/s,kJ~z3kZ瞵I$mz%:P)Y (!TG{4)2[bdYfo#8B-,D\ɾ0uWW_c%0yЦvҥ3mX)4"*w7aҲ~{˦wk b6Q8::)!Z"$KlLپoL񹦥7ڍ2= r:oPJf3jS!hD=T g\7ӄ7l[ʧ|vֶ4$CLcz\3 $/i],IL`h`B,%eoc̀+:ͧj00pe@#tC{=ݶd*!X-`J!wVI!AkXAQKaܱຑB,(((( },ADMI5$aRQaܰԽ2-ȎK!d^0 %잤0G^lt$ N%~hoi"w49.KIH 9!$E$ ؒ"]t8` DMKu0[ˬogG}`y.}L4TeO>|~묬JeeaVVV YY_eea>|~(LMll#q3{y1R6k !Wz8liHpC a-!1AqQa0 @P`?(\OTd}@Lhm\ ggv{ggv{ggv{ggv{`8 `3c;=3c;=2y5Y`v{ggv{c%d:)ov{gg2VC0< R3coggv{`3c;=3c;=0"v{ggv{gggggv{ggv{ggv{ggv{ggv{ggv{ggv{ggv{ggv{cktH◵#Qb)j`w@Rp[6z^`.oԝY4u-5fcc8Urn4cB$BHW:yzC'{Re 9%O[աƲ !L]NGG :)@=c7 RU+Ms>@,qJ5Yd-CQPz hjP̒@kf 5"Rd/# gv,h I:ܢՋ&RGU#\ ٯ]ip q#Ppd"k:nA$nW djyGclj]X3Ŗ$c"BGB,9a}`6#`)& 1 2H#rH-p/!+h2R8DxZ0 f} ,Q"%wKc8t(F/Z $"M݂BkxEF=&aUѽor3f7eI7fl1)V݄+eskAaQק\g1Ub^(k5ЎEU4֖,&w/tgDuW`m+1q+6G\F T"fx"pKj艔*{0$qHPll -CP(MX `4LXT͍<ã;;;;;;;;;;;;;;;;;;]ձ"c=}fS0ir}u %6rioX] X1'8OKxrxGt$ 0Xww`t*3cZ; /EUQ zp #0'nc]A2[@jq3`si}q퐍@4 AZ'ѝӵkNzk7zD)B'=Ni CPNQNzOk*@յ=D f? J.g6Pm@ky0: Բ5-˄l5LasGG&:/-L2$Jֶ.+?`_} qy6~߻;HЛ& 0 Ꝑ0SH8hBbA-°<-硍a F#5dx|9iB69 (!f #'QN\,[ aČ`@FiD X O)sc5:Rvo BrV" pZDrGHwƘu yU5K5Y H\ d"ĮLiMs`HW\QE*HvL98BbX 1"Ԏ8#,#riaQ''FDP_`-(-Dtvld@ P5-XThkA75`[HPg%?* prS)w L8-qKRi(qa[Pl5HS[\dY!5Y0g ݘQZ:3LFӮVby^FK8K6!gSS_%,44VZ[h!rQNFi00a#vc/BbA Ѧ(Diu\VF4`3|Vå3}XHZCd8pcNt|;QdۻD@L(6 C_"(w ~b< 푡hr\E])@upo>RĘOP$!SN&M]~-ӞhkOW7^E>MvY2mOnC^z~z/ToG=_ɓn:~wwwwx߆Kt֚Is wۏ3333RI@5J)Î:~lB-:p }1PY(q0Y_7nlŽ0D 81x탧Dd5u+Sx_Q%yY(œ98x|(^x tX5 XvW̺ L <ڔQ` yu:^bKf}Cя$u<Mj @)Ѧ) NPD5ѕ7Y[^GQjXM$٭]-phhW^Z-,Z){VR<.500ӗxWI4CZ6Nˏ(! },qRP+~! 8]?59vz{`+.Z&NsmJ (C^tEo`wZrTRp7PT=PB@9AXF9V0؅B80W +e!dGR|Ay 'sQa:$;@ߡ !52x:l-zP1=1@r磔0Δٷpd8Թ[%#kNrҊ7_L~Xk~x0Qbo?UN_@OZtb7?䵱:DMKYwD:E6Vf|wTeĆ ×bo\?RG=jEVnyZBXۨK9u 2 _L/Xߦ}v%sgnq`rJF >cյ0=bҺT7x0Qbo?LLUI_Hs׉o_uN(hzzknqNM"p'Ohk՛rCla\C5*O󥰎:z7W5 4LE 2 DSIxPitL ~Z9=NI#H`@w7UF|`8Ch"(Zs~:DM+꾴4 hr56HwG|cxLBHCYKA< TYAsS`).uARX$%GQMS 'zR)׸e46z啁ld[ NCWYrr*|+.%WDM5Xn 1s&3vT%H?GQ?d̯jҤdF=WRn0OFPpL?_x:g);Aj6SJɗz;yrwla€Dʤ7=TxR8C)j '\/(bn+;H/^xxxxxxrݶ'/ CGb:"s8-.331xD/ C`:vHwv)D@h*z\7d Lj$KA,ƥ %gggggg`0h'C<<*ⳤB&!KҬtmfбuq@Un V!ok^2JF02pMop@b֮>ϡ"O6~VK iAʑXbA^Dat +?׈+v'xEpk Pf*(x5w(R h?W_?֨@nl[њ;tAںDt+Yֲ%ÃJ ` @^Xo? |>s9x4h1R9D$<ĢXe&3$& s9)i|xpVrF's9!/9/@[!P<kxpA|>px9Q:qP$cԛD|>sF/>3y<`͚)'ly<`0|g>1RrhV𞽳y<`ƑIz͚4y<`0|g>3Fݞ`0|g>3yDly<`0|g>3(N{~x`0|g>3yE ly<`0|g>3V@Y,"᮸hѣ1`3 aТI?'T²ݢc(?_gk@w9d(Uи:n싸A~ԤQaE:.K3?,6jrBt%p6w6$@\{fk`Iv鸴TP.oc)ƹr!M'ht,INi'5bO87Y%/n] #dCTA'//hp :0hE dlPdHv1W?t,*iU m_L^t@%] a0I%3<<~\ 6di`0\ t ^4d1Wߥ QW bx ե5ŪUk33?k=s31$(Eټ,BasiUIXUs31vҾ ?Msg?,`HZ΁y^~Y'>g%Oł*+0zYg?,glퟖ}϶~Y¡FY9 ,c~Y>بxjSFkÇU ] @*˦]2L.xމWVp+=ힳ>sb%UV ( Uڏ?yb7wy5xa=j+2!cYvARIwaz@G*כ Nz*szV`_x^ =VPE"J(NEūxIM5w&CHD 7 &>2y̦'[H q1zFMs% Hzz1~˥*NO'kbe)T-醖$W+ڽPA=o(`\pJc#r= Іb z7Jk"hqZiH(h1QK)n8a> stream x1 Om D< endstream endobj 25 0 obj 294 endobj 22 0 obj <> stream JFIFC     C   8" kNSǭNWUۭ?9jq9y?:Mwh]ǝvyqi6u헅to,vPWt3vZBt]?&!bb&!bb&!bb&!bb&!bb&!bb&!bb&FQ:;1\ˍK[)Ř8 V*ZY_؃c%zg.KŜ?zr,'w%}VS䰫+iev{] s={d~JEw Ew Ew Ew Ew Ew Ew Ew Ew Ew Ew Ew Ew Ew Ew Ew Ew Ew Ew Ew Ew Ew Ew Ew Ew Ew Ew Ew Ew G1t@h:;t ,5: ΀Pћ!>69ik6pHd6Uҵ_h/˹-)( /t8jeـˡU܍Zt*f b ?RUP\*8uP\.կ.WZm]g8XM[iz>[溽niZQQ喛iPnEvzߏTf̝s]*l7Ӌ:OcD>iDrO|Jߟ ʿU*y6W⨘*?Ћh@gw$m#t5w/i{/L5ty}<K+ߟUo9[>*?>\9kumuko_N.0>eUKaA}.T>SO?]S(BP+%[i}\״΁7.~YiGt >>KW ygϪ\qbɔ΁Ǖ3ֱǝG#yF4M:d:C=+z!ݲz @ 2<$ً-=m7bHd ̇:1d3*+|Ő(@=|jc%N٩Ý&2V0p'x#DQ(3 ǏF 1a 3}!!̆i1$9l3BԐ&20- _SٙygB: 쳐ǧ#!L^6mxk׍^6mxk׍^6mxk׍^6mxk׍^6mxk׍^6mxk׍^6mxk׍^6mxk׍^6mxk׍^6mxk׍^6mxk׍^6mxk׍^6mxk׍^6mxk׍&f{i -fn$y g뗖F>T {:Q 9v3k1d42q!1!DEmÜ'וC#02ŔrFwrCi.!wҲ9`◒C!W!NnND\פ2^Dxq|yH_VR2?7'%%s9,b_#xxD1fdcA%}c_o7C.1K G:M8G?;sH`9ɰ e=Cr&Io9^YRv "D[Ζ|r#3!Ċ2dZzd qީ15Cn8o7?xsxA J}BuDRs40[B'} R8_7er_S=&jQR.#`[e]J:4RV$vf<99 gl~Һ3\d)A4ޚDOf6).ŸW[{u/(a~O0RF44Ɩ[6eĬFi$p~H&3F5Y(e(o/egE61Lɹn$LƙLll>Ьr"Eܜ|AMJ>-&&ReJ[?*B*B*B.*ԅH:ڐHTU-V*B*B*A*]EHTHGX ~& !R!F*B*AwoR!R!R!R{B*BDHTH:]?}zwJDrR߳v'=g(rr籞|ELv޻淧؉9e}g_}Ih44 CAc44̞CAh'W$$THE(D$Q" qQ"HIPd7P_a8CAhh4ht$=WIM6,~|d5JrI9 ̄tO$lfbчzZ7 >F Dw-w/?-D(w&y)r{}M/Q"AKyʆd*C3H[#Q]n;:ɶ믯)FÈ5DA<ޚu_EIYZ49R=Mp(t193 9>A1Ɗ'þ}v+GՇ:JU+,iciA_gJ->$^(l2#id3hϖt8Fl˅o-ff^k3~I3,)( AhtFNhj=FVU;-*j9rSv)c|׭C_ w2Ҵ(s9#iZmiqlZYD'^Š̸V֦*ENm %a~_]cV[=RKPv7Z#gJZ˵#jm&s5ǚ6\jYu G9\Mp[%.z䍁†?s7'}.%6]Q6|p-N#g䍜H ]k_>"ٮ-2ٮ-2} TXZ@Zh_ZBxNJxǏ6 !1AQRaBPq "3@Sp?}2t<MG}ѺTny9(4mۺ? )|EIif?[>_`r fo3^~!{ח >eL Pr_^AʯB/ :c4t}(;Aw,w"{CL+9DԪYWW_ʦoV*"V+25^pPD"eY\&V@Jʿ_~*gzUz -4;]4Ր'me}}:*mʀ7#lxU쪴:)y&V;mdmyY\&DX=VUx"7ULR2<ىS\HjgeT7dld5Z$B갏x3eUffP`eyN3ZCj6"}X4ݔDXVUauASU{*v^ʮ#>YȻF$sE"Y*_Z썐{6"r2 7g9i vOSkZ<Ni>'jRrħĦ *_@PM  1!A"245Q3aq#0BR br$@PC`ETSs?UZ斻‡c;\eֱ28 /iSOԘ9k=)Exo~Ne7eUFt-͘, - 紜G ~3ўovmm,t|<Ӷ']9q, p-,-SϵA&T2L 2By?*IyX/=MrB~L@OўoaL׼1[ZHrX75>78$JK,N11!uStF#%`qt!cMaڔm{ ԏ ͔^UY -t & T>Lg~|O2BMrڧHʺlG2Wm27A1¡\ F@b. a-k_nRK#8cdMw1`Ś[uպBg?闛}LN9~7HXZHQ2L'/B4u5c0D$鯭,r,˝f~s(1X\>EM:ZVkbcL]x?Bp(A7H]zqt4Z~ F2SƖ(q6itV,tqi7?]'7AZ8?X> t~jZx&6PJgX&.:n:2OlM+xĶZ}Çk%m$.[2\gVV"8 Ɉ{_$Bk3hĆɦ$jm5,>/:]!!݉3 ]3f/}=ljuĺq]g.xY\Z&sr*+$ōggЎ/ &;o~b11ᑆK].jԙcLAV0jbt^uk=36~cllkhO VFu-ﳐ`9FO=&1}ahȴ4_Rƚ ܔCnmNL?C۵ӗ=`l@p6Iu K#A,b%/ۡVXIR2Ft81q#42<m9[ؚ߳1m2wzLWUk_c-xиmsk+a5pJ U!lCv{su̞ ZG?gк&Ǥ} }_«Hð`hfq4V(?4NQ9Qͤr L6_y,4%F}ABM!u~#+FMvos$bKKúh{.hJ6qq$nkCGRm6v ([v#khuNG̾PF)UthPZ1oq{.q8Ư>? ϑ5>C Xgφq9uMq%s\@:L0cku=X敯-ЖLƍY"؅828u|6OeӓZm]yܺ&tMr7ˢo;Dw.]yܣuyWgIK)ʾ= {C.]{ܺt=r{ˡ{h_wS5>>>>>>>>>ȴ6ݶ'iɭ6]yܺ&tMr7ˢo;Dw.Q캼Фm%_Mˡ{C.]{ܺt=r{cTԻrrrrrrrrrdZnw }֛WDw.]yܺ&tMr7ˢo;Dw(]^UOCun]]]]]]]]]\oG+rrrrrrrrrdZnw p8yxidێtUap]9wΐJ 2Pj^eņ5|Mͭrկ%E,60/`ES 1_J$[)ڱ{Z;$/<ԧGC9 sKP-mhRZ*"iM~Gh_IXBJ?<5'YJqwiuW. E@5lo#cksrkۈum)q dGLGq5Cu7ؙIn[cs'.}#&RdJ' m3"Tk^қ-嚲U^I(,?%b~x<]p;mFѷSn>do&Td+Z'lqGe!t= K@WۆDlezh[vɽML#1b?\pCqw 4n u^NJQ>RMJ;5+@Ls[UEI9.Mqڰ{7L6b`\>ʆ:l ¦KasSN+shڞ,uhR9ҙ#P=lqGe59sJ39 ,ymTQ-\vƳrh8:WGN} 1Xy!noa 8/.d.NJ[U~"q.! gS⯮eʕӄi$DCuTi؞1uu382:"uGC*EuΥ2Vk:Chơj}cZ̈́UrO,?%b~_FGPERS$ˆ\(5&5 &kr mSY2֩IT,ue?Z>A+y]vf!u%}8 DW;U@EN]("CpM`}kLh֐hܪ)ߤO#VڜdR:T)T dcZ)Z| d>pO n4Ʊh5uuܘdDZnuMY%o+gp>IsueE%BZ%M%E%hK}UL-*ZXJp2Ieh I%ZLvXI%ˮskTA jJ'֥(଑-/n~)&n.~r[DZnuMY-~'4Hm|6{n=Kr0>^Sp iqn9K$@C{]֎ ym9126KZM+L|diwerS'"Ce:% ;6R`j -sXV΃VJK$.:[AkL3s.isM,#,NYJR=a.)"ndd 4>Hgi#{>eV^e),ˬry0a"[ֱ-m<3ͥҵ[Zf &Mso,XVb&Ҷ`2A/Z#ď9_6(Ù8amq $8[\LJ Yؘys+[Og^AdAdAdCR,2JF$̑'7X+ !dAdAdAH,vXQѸ,,YYYAdAdŧEpز YYYYVAdA7O,PݩGUYC9=8Rq#Y%C58ݕm6+] 8[m~<'!D+&W\d(UBgu?V!jzԱu'}+9sr@bkX뇷~~p=7$oj"]f=s2>G+ralDr&xO|Ǭ2'_K[-?[Vյm[VյHÛ^* &PmT*-jڶj+j nѰ jEm[Vյm[VUm[W3Owm[V_ڶjڶjڶdnӃjڇ]jڶjڂڶjڶVյm[VյUm[Vյm[~jڶjڨjڶjm[Vյm[VEm[Vյm[PNdGaqh;{*%#nHշ> q4WԱG雇=榅ҾC>Giq+Տ~XwoٞK9#3ÿudeֻ4GG-.͈ ˴k)'K4bji\<І nnu+ْNVƗ74V+n3.١fm~pɇwQ.uj./=ْ_\f\'1X~36<<,->ڹ{rXLDRHw,,•t4Yֳ 0׽s{$)c=׾I \ڳ 0afdtkKMJh px״d 0Vaf⒎дyg3F3k{,}YNcZB*"0i]GF hŘYafai4hn/wڳ 0YOSJW`?2uv!>(mWgafYY7]KS"֎ 0:y۳o\{sg1\{sg1\{07PqOkO6y6{3^ILWg=/2hy&{0#o'2=$uy6{ӫR+\4{4{4{4{4{4{4{h\YAmiZ.h.h"HhmCZG44h;8KSy&{3^ILWg=|cP+oi^IlS9pZX|0{ |vj] m:SC?=r0XnmU\Ӵ( =нlaˀi0BֹRQK3b\ i ZC/#x6WVWZc i` N!ЖkwjK tGۭbqjËq{Bۀ=W,p_'0c 2eX@j+tqǺFA{i} [_j0n9ÉsJ5]]iNeCqM47~pW0'I﷔w13FshMG49c@ZHMyaB>.>c'P-sklqZ-uwm٩p"a8Ι|5:X2Pa5 b.DO0TvSYNux)"1|܏8TujN>Kr׳E":dzΡhH s׸NzQh⦈82TԡM1Se b T2ADKMjLM!eC|k?Xd荽t3n# A4T^AWO1Lr#qV (ĆJ(Hz bЄ @b:EdX̶!ԀaYF+1r$HoЄPYC Gm M+B≕k ºJyQ® 8}?6zÉ@P*`i`+M EIZp 9wQ`Z֢}A"UרН0qdu8@5ţv Iꆄb=0} PF%)7J^`edHȀ0t+ h PJ ^6zÉJhl A*D9%]T(^K6D.B D `Rr޿@YrQ˽h/偽^>E;箆 @WhCEG Â!_0g+A[*d!\cgX] kyɖ K8 ܆ҰOpxq!XWrr+h8 ҔPU11`0*ùj S(d?3@i B30Ȥ! GQ 9s$OkgIߘS$>kV: c9=> h`=1 3U(VsT:FHKԡS\&je_I.Fg2%o"G ZN/]2"*@kCFs"2MK{oV 5amW^mVyhZ ZʯO c"^d˂esʪc!(ja_T TМn?D,\ "A [0ڨ CԯaY`+VB4`Y..a:0BTqBb$ `dLv (?*+[!bSAZGEw]0AQҬ0E z B@h,S0!-0,%-Xp#ēY…Ҍa?68`'$m[DUY޺uӮt]:*;NuҼ(y:@mbiNuӮtF6:N])<]:hU>g]:NuӮtDk:NhW uӮt)"5t]:Nu]:tt련<\@S *Rk'YRkJY\p ej|>:Ҫ(o>8T3bcDSz:-ZX4r|RP0@*3P/iVu9UPzմǑ~_ƜiƜiƜi):gp4|e/Ӎ88}iƜiƜiƜ0rqf]-<ʼӍ8Ӎ8Ӎ8Ӎ8ӁiƜi_ iƜiSN4N4N4N@qqtL8ӀRt N&kƼkƼkƼ>5^5^5^ ΙMx׍x׍x׍h ΙMx׍x׍x׍x kƼkƼkƼkS^5^5^5^@ N\?W#QawKŧjLai}`dᙤ0gߒm@xxҠ+zP "J=&9χPʕ%k R2qo* a/SW涳RƀK9#ƫBAc&ES QB%)ʀ5Dfem`Z^apM*}~_ӾH4)H|ι:DN:Dc )%JN:ϩ IL>tO>to X@5>t:P@VY VT q:N:D, lȝ"ui].FTLl""м&!hH.hxO-xjyX-~%.( "Fk-72YCNSn &ELlS5伺7%d´iZ3xCYK.qV "*P!LP `0HO~4 S&!mVA$(nfB.?8CZY&`H#R5L e!q r ؅ I&E`'KT4 "$l UKŘ)@BR@yjO7pT$v:]" Ѕix#?iq!s,Yc("?4@3Pk@2(xdA:*EFd@T=FOTb`~Q$ J[zHDI$$* 0P䉇$ ]lXYvZa5v4PChNs* *&l!QKM%/40~PpVfhy"?pUo$RpjTX&0U5@@(:T7A HYTb#c*2M_ id FL"dq ':ǫ_bd&XXDXuKuHCIalL@LhCV!!h`0gm#`ϠOOOOOOOOCu@Z! _@#'[nu[nu[nu[nt4,v 7I(d cAC<<<<<<<]v߯^sߜ]z<<<<<<<?Ӎ7?2s?q<Y\00q7o4/0vq<97o7?ˏO9wp?묶mq Ͽ , 71? 0Cp????00B<0 ?0 ,/ 8?Cè|t}: # ]CXc(X?f^{Q *A=\àغX? AF 3}}H }ऴ ϡO{~C3~B[zׁ v힤{]a!\ރA <_``>D!m/{Ze%λ>O4JFƖ"Sɉ Ac-B[6VZML@֎T t@p4Yܒ՜ B`Yx f)5r"֛ `>DT5,̇kBe/T2Ǖ$1E;X 25RbI7CUK!#`x" :Jr>d+rJpCVp gx' _ְ4LXC:1ȋZl6u9PԲ ԑ\06p{f4\ b,ƱEA4`h0 Bh$2V"V.H2PXX<,z,YlK(]X 6chm⠛ÆA6B=mtIBǮl#(=H,G|2Y8P-Mj y^3V h!l4@h&Ry$ wi:}2(+ax[y(&0 w/(2Κ(  m8zdƒԂ⫐vQȇ Y%2VInZHO# ,U e\H ԴJO$hQaО`988 ax[y(&0 w1zL$7دH_iGܗLXX@e,&-UWI#K ^…F$yQ)/)F&X׫gBmcLG>?J*T^*TJJ RU*TJOOA0@37C8'zqqqqq兆&<"5ـ Ti`e`=\$p(3QEb3WS9]Ou?pl-!1aAQq 0@P`p?bPBD@Et}AP~h _D~ p7?G1ps^ ]K7o ]-Nz?O ) ÉU_b h͐=H<#zu }cx4]k s0ͼ]BPҷQEQEQEQEQEQEQEP 'MI9ZbQiZZI*^Kx# d,W5s~(Y-M~Ia`zHB $K}+++++++eeaVVV YY_Ueea#ѯGaYa26f؀/EQEQzTQEdGH55r88k좨"?8?q 9LC9LC[-!1AqQa0 @P`?,YBG'Td-zI0˰z`J8v'؜;bpNñ8v'؜;bpNñtC JbpNñ8v'؜;bpJbh;bpNñ8v'؜;bpNñ8v")] fpNñ8v'؜;bpNñ8v'؜;bpNñ8v'؜;bpNñ8v'؜;bpNñ8v'؜;bpNñ8v'؜;bpNñ8v'؜;bpNñ8v'؍ #OXd*5 bA  /El@нhCVgԛi`&[oE?~iZTɛ`!k mΛD)"+)48n3 0HajIS4ĻAv8 I%"?h0M5t x6 +fmeհ\%TX]hD2x !SJ/k@a֜P̒mXp :[Q!5EbR%\2`i\ )@ȶUorcEՋjH I*9#2mX4 nLA5lf_ $Xmа7Mg"ĵM#lW6owLsMLPqN -D3SXEgF34P8ޥnJBeޚpEF^#dËiLR+ؓJz&8 .2ޫYi\ /Z *f 5n:#!|̢fq9s9s9s9s9s9s9s9s9s9s9s9s9s9s9s.emdJHG(Zk֥`kUXP*̚rm$ R^cE.a I/6W,8:K[*`(дr: JT2VH6c QrTTPÒ;SOW8ؤ ٸ q|"KQ@ iS kaLc_!al.f"uJ yJO FtXVLBuͽFYf]C9_ת$hYo6& P[+x:=C~" FmSZX.5҅KY Sc]RX:iF6 0U\%YE HapcP(}%! 4+Vad(9QnIRPb:Z^Jg+M9U7'mSvahZWgJ5r+Bt0jT2$kZAl5jV)4+ Az3*`*PQkCav 퐬UF=t +f3\h9s9s9s9s9s9s9s9s9s9s9s9s9s9s90#oc1c1c1c1c1c1c1c1c1c1c1c1c1cHh\*c}tNdCE6QcyAasB""#iγPn..vjm8)oBC&AcDchu!Mn F%Y!6* "3u75d/ 5μ[Q:ܮױ W7J0.>ªc`+Ԡ%e QH3IVmfk RŚaX4@PF^ZдaZ4K|)t}Q -XpTos6-aL0 k7M cI9 \Z*QKy}Sb:#Bu?,je5¬qeҪ: BN&-6]~.yEch:ffffgb;,wwwwwd]C}ߪ__Vkjo7 w^kڈ;mI+{5fZ*`f6h YF8#baQa04C6Kp`)MC S[@]Ao5  29`VW]JJZ KX0U:MP/T4[)Um!堂Hpi)xqpj/4Uuk*٬fX!:KBk]&bghi,!rƮ f: њMj H)@YdR!( MPDM%2c"WhGQwmWEf"AMbqnٗE奶ƙεjNmsf3d MYeCJίms*@e&m PjJSo6O$n74J֋6bel(ZEuSP`D5X& s% MsAl] V,užJȡH:ow$Vixv!t(=uۣ ZV퍲Yc1Cʰg)iN z: u0mbs[Sxa8Uފy\( [h OY[ֶ qʭZẗޚل"? !Pa]-ʶ^/Nv=U{,)5e`btv1^KC̰#6LsչŠZ#J[~" ߱MZY=&t042ͺȀ[F tK5/4)pBך0 Ie]zJ5 X`S!J4d$(֦qAڝ qxmWLiAYx~ @򥰍:x3X,\ۙweeB^Ej$P0$ ]iI荛ȫO ᆋ&BW o+ !$/EZFiϥAZ6*f˱!Rź 8в Ъ Bqʦ8ViX(-8tZ./T4jVoCMT@ttOw1}A@n1 6asU*p/A, N*gzWl0ĦfPh[ոJXaL$ɅҌaEF2($Ldk1Yv6ExcCJ,&4 \Z$X-se\ʲeZ =uE4:EiÅz,P:QFq*{#msKCnæY]w j\4$H`*Gqb`2 P!ti.߹J{Z\d6c$MN8 9aB HKޒ]>_[UˎʕTYBg\1ˉB*ѰjDUF AH0LS&lU9J0zek+ʶVFKvGTJ+K?AKmPpV*n15IضFak1rLƕ.tsPyjw϶W p@"eU "iD i)U!u L h)|x 'On]"!:O]]vW`" ϴ&vx 'O+ 7&ڪ_Y"epax kFU/ 'O<o {3'OS/W  0A7'O<xV'O r-X1e\'On]?MaV\iPj* -%n`~_o+JTa= jV=K_~t 1jؼUq(S }? O,C0֯UZe," 7T&--bGE-f__o_Wڎ4 LFk`w{:k;kTG )~xz N{jf/?_yyyyyyyFWE/@#I뺰9BO]yxR+Z^~Hy "u`ՅwO{{\2r X5a]P8 +G ^Ax$"!k^'===Icm'HA/=======MY_d)ѤRN}[:<<<<<<E+Tɣ ```````Y4a]фA8```````"kaoN'=======Z<<<<<<<2j?h Jy o ը haI>O~W^,͗pEܺ@[~O_<.zEEs} ܑs/u*y  :ӡ-/>O0kW+ nA }ߓf-x!j[m˖{Y}!fÏNW4;&W*F@] xM`^xA<xw{-Z@Cڱz`Nx&rjݪu<aRݒE;!$XdU<xvxhL"X3 -2_ȨIZY"0<x G5ޭS'OF^VgO+]x,-ԴW~ԺXV댼jM\'D` dobx %[2Hh.`vx viyz !wV.uK]rĖZ3fҋi~䖌4CͯB!>C!OQ `v!Deڴj |>CF.=)-;}H0x>,O~?S埩g| ,,x8.s>Y,ECP2!X f<8u1*-HYG'S?|_H Ss俩[1C]z^jrAR] x5L ȵH2aPJ(a7`G-u02ֿ\;Ǚ\3KE-Bb&ҽEg-@.(N]Jl%ꍘO(9pI - #bݬM (+4Q)-BJ ȃj au [_^f,2L7Wq%@ηPmqT%疶3o MlEozSvhr9UYcXha]4ʫxtRf &*Z)BLx S 9ER4i{ 7YoGUV0؁IAi4ku/Vy K6F 6RR_ۃfZ)yDwc|@$5TdAe5դ/F(iԥاhQ%k=H1/ǏK*:8/PZjxQ,]# `)'MX:fF~CKJP[)y?e\:TuJx+ K aMwD-Nլi00pAd`.s3*?YE@MeF hO QdmTΡ UU-ÍRCXP5kEA)T_ .ޢ)OjC%n- HROWwAtAarZm:ۑV QtA1e& F>fDpU^:Z,n8hN\0XV+4ZQT/SU^NE鐂cEii* OK_RfF2 DA"Yl34U0-Cm%Ŋjpڶ]ݴ%I4MM !eB aTCP:UjKւ}"D$H2TꀿݡB (PB n@e@P>S|{>=O~ǿSߩ|{>=O~ǿP&~[@0RKB (P@M@_ endstream endobj 26 0 obj <> stream x1 Om D< endstream endobj 27 0 obj 294 endobj 29 0 obj <> stream xVK0W\HV, ho JO}Aa[v/f$y=7UJ_FΨOj:*[>Zu/m{ ;_rvqOFB߮}[Ȩ̈́f08#? Q:PGpa[ "&b2&fad=Dz-#J3 `vSi-ңFo{b'#e#%My˨:ZɵqaJN<"$-#j 2Rr{5@q#1qi)PWPKFԻoLڅ!|@aJȜ ĝ\1'8Mȥ9|'3Dxġ8?QH]8FyPҬQ kt3)MJITv@" $=ounYe<\(TsU@@(цo!L{F0 Ю*:ӳ$ɐ3:ӥ4:I$`83J!%/"uQY7w #.Ory\t>8iD`i/ɫm` endstream endobj 30 0 obj 812 endobj 32 0 obj <> stream JFIFC     C   "  ഥqr  1[ 2~'_[vβz#KH:U!Ax8C]kCzu$}رtk38/e:M!R\JFkǒ - 59ηY ;2!Pbqȥk!֙{DkSob N:]q'yuhz+<򍛀 ع9H|04 , 00 , 00 , 00 , 0ƇXT,ϛ|gO2iU 79wowUv zjXceW1o6S9zlpqpp,=ߔxajj؎ Y ?»O;(h,Y?3so46/}SqZ[\1W,p%[8eܚ!V@'tlE ֿy*c]"K&is8l"r' "r' "r+S,j/*բk1wnvY;i]`!iml@D!i`p1ʌjKj]/2N6mӧO)Q짍G5x{)Q짍G5x{)Q짍G5x{)Q짍G5xzGAyzGAyzGAyzGAyzGTw^3enu͙Rlff'>Px\8ad.ٯBdLϤg+\pa7+@o畇0XrGclMyeEea5ŬN8rQ)/+`BwJueĪB([_e:U׉Fiw\v|Y@@a{(QE ׋5XZ8nQkҵaVJΫO7M6Xmd?}W5ͣlx偻CI h&6jFxi5~lye ;%F{~Gw nЦs%Kl]}Qv6DCIAr7Li(qnEJ}iֱK.4Fi<a{(Qٰ㏆[nu^l"F{L78%6InJח5]fo9Tnq@a{(QאLkO t؍*߫{` .Gg4_rv|u.SqvAzJdyR"u44 ]f̎ ؀/+`BǰƼ7cH4#Hݍ#w7<M7cHݍ#v47cI~47cHݍ#v47`_Va%SQA?Ȥ]<1pfk_MOnAinNfnAi/+`BǰƼGmmueSbmn]co7"=s<:(t :ukQi7JDƌZbjpz'e4nt?趱v/tiݴv H7m ݴ[D7m ݴv H7m& X~y~yXs( .u-I[.6<]jۄi:\ZȴU5msiZ*wk0;\=yZ&9e9(:{wh5Ku>{(Qא{\Y*HO.RR}*^rJzmHJz _ҫ6w ַ$ /+`iVrݤ E^U MZUٽĴaݬGd5rEK˕,l/~tf`HL<jP6X*RbWJi`S >[dYѸWeլW`n~y~yXs(w77G-CL']~#rtQf|@Z8re6X7//;?fg{'F9DɝbɈ"=({ k $I%E{i4kk!Al@~y~yXs(kug}?+/cz*:|=՘6=ɫ$.Yqf]φP2Z*]Ռ6e ;TXe*B"eQuu饑[Ɖژ*K6 ط8?cltenyi2#aòF/ëBǰƼiQ(}ͼQu*;=7hQw]ưp!DvF &6u([H6aUl;otya9Ks)6<+qw: ـe{(Q>xe{юD]Vk`g-R:d+G],rb, ΀/+`ʇ盪p߇VP1&p+NuYG)6lrn%{.S3E9V,1+v jYL|OsکӆւZTܥXY6: Ѥe_VaW-_jʫ{[P| aͲ!q>N'il:Æ:"u$sW+{\O/+`~^[f_Vr\BǰƼ窖N`s'!=]WxYV_Va:.{=(w?ߕˊw/+7V;:ѵuM<_Va&Wr햁{'o/km2]iGclZNvɦrlKnm- -7Ø,9@P<:^]am* 7jVRa5^+ KI?Zz}VlaTJ^y*q F6߫KJȹ*pʥ6xϭ7EO[Q❊V-*lCTdSx.uXEݨoR_9q.CA%١ 4%gpϡ8a߆AE%HE9L!0nLcچa2(o!pj7A(H+o6 6`!"24@P1#$03%5ABp6m&]ۧlnn[un[un[un[un[un[un[un[un[un[un[un[un[un[un["if֊|6\.\͝qđl)`.Aqz`\MA(Spbam`B\Jpc&暬 ywl[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[- h4 o#ʘbC bጻ 룰ϑS?sc̎͞kjoFGD&SXa :l[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[{Og[7_Ƞڜ@|W`!,pD +͉$-_޾3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3^3p¹0HZfA"8d[": LSM30/enjA gĖpDd{fO{dYs7ڄLvLw($e8sㅄy2gAFC(>dt̆@4SK!lsOgk-"U7RMgS37&CD~EF0I5tϖY5 1ʃ(61V.^˘̜$evӟ~ 6UT&c6s: c;I%44)sd")ZL,cVbS̐\uҘ$I + G>w69ʰGnnd($igrc̐8ex>.N+l,lhc"63!..PLDHG 1j7^,sH@y1 ml⌛^+!ƍ>0| ;dfb asyLBe(iźZ'̂A6di384.r,Ჱ0Y*Ĭs2(z;I[[!+|E6^#: &}cF@Gف^)Zf av"  pA+L˃_\i/G_@lepC:a!&\q$YE^9kS[>dq;~~cL>/AĀF$8:BEm<Ѽ䝑2<&5 Dؘ@ciⒸIt?GgH؝$d70.[ -8Ql۳yXYSmZq2G[6 I$Yi͋&F[4쾁 d>ݓE%a,[6;by>bGMWly8uTXd ^Iv ?dR.kN\cYR{ 5b/; ŁtVqҔ-y;1/b#c:y8@~ߞ$Tkf:U̝F}LTsO7n,(P~rj,PoP t CJšK|vaR9%0p,X"h :stFp?ܾtLs"u`oF Dqe!/2?&@ Wc\N[;dl(K%?9e9OGIa:::w pw pw pw pw pw ӭBa:::::Nf?# ptBP,87c |dS`fFsWD},*RGi/pϨ7ac{)1(.90Zdqu VԏcIff>ێ`U0WL;+`1(KdHЏ:cD8܎ Aa:d>yɭ< ⤞S[%[Ͳ\%ͲAx3:l@,DO1V]u^#?'"@ƐhB<%|ur6 91=t\Dϴ#  ']"bj+06=\-iu=4ؐuiY2.$&\TTbW2"-q 2 KĿ}ʉ4;Lex8dpzq 2 %qO#vq.....,ш]]]]]]1< < < < < < ^; 15{Z;v?FYd:yMw,{b5+38¬+pvQޓ82!ȱ~h*@ ~tMX时C IX8{1QGKyTY&2G%tG=Ys P9V܀w+W|]w+W|]w+W|]:VZjVZjVZje/gH0rpVM&Kbܔ3u'ɆY%׼}߿2hɬ9 -OQ>(cvAn- Fxcpupu()%{?i~A灂Has#ucd&K6wxZ`_0Imf(sly!ԋ~U&wxwYOÝ ymsyޱ*|e{.8^hq'- bpT6cX{?Fy,0N¡Nf /唨1Ɯ/؃IZ`O{+ؑ Y2 pf[ `#Z[S~P?7o}6r;p+"̆@.qEK2ɾk\8dؤ9FEScȜ\5|6\y+ =HE~(,H]ޚ|cBE#d@KE61;_rw{Q>"4W6sl6sl6sl6sl6KMu'ӳdKdKdKdKdKdHkb̓dKdKdKdKdKd4ѿ;5SqoZIM&X%T4:^hROC |̕kcS/\! D6-q8Yp|A(Xa֜:`fZ⚹bG ?4j 70 D3I`Jg;ɆYi 6L*:u?8¢,|W͗a3 Ż)s֫U^'Q,7~H2򍉨%"2)'RK4Df J4,r*r5%!ueȉ PHNL$<dz)"` tY.UY!KB@0[E3GfM.6I&%HAjz4\%ʲRi?p=.ܵ3j}fq7}N1:y=VU^el^3>=#gP3"$\Ì;~̵]JqmqO\e^z_wOgHٖά6˪2;e+d7QHq${O{d}|Z ft/d_6e[W:#jŎ O U?`/%%AtLĭw n+^zU}ڹrȑt.rGѱt5b}"}cLHN`5=m3M bN{L]{(7~i.O4O)NC|tFhg,zz8D5X$e"|Č{t|NM{2ajiMz{Dˤz"פzB貺%ѵt],k1k? AFL 0'Wj+vH!8.|o~?L~cpwInf^ Ƹm1̪\9!OAp,;ŲY]:Zzfwt?'1{u_g A}n-Õ95|U5'?#逕v:Wc]v:Wc]v:P.ob<_C͕+U^[韒jw:>䙫F!NI:GIb~*a /H4ج4<,洹utK=Vbgl`8Gؗq - v7@ @ 9wxwxwxwxwxwxo#SsՏv?ϧy>K/d5yr[OX_[3 S< 'OJ)@l Ln+&*܈ΰ$,}߿ǔ{"b~^48C5==]2CV7TU"bOysQU,{?ifr*iMty29 $,6C$Rb|fM4%ʲDHxm&?~JD*XȗX,C9o6& |Al,M~Dµ:O//l6y= l1s *(lHe`dINs|6^=/zڷĈEC䂁I}TeR9朗LovԴCjڤf~'2J>tPG?λ|΢w2K%%(U$LXCCwovӪ/"dǣ?9Zo2R5L^x_vdoW%:9˺֖'7G2&*H]Kl{X2yHm\ [g$'%қ_G'A\#gHWuE%o0V8ՅSHTԐ]d$t Zgkh{('fbC#a5qE7=3gHAaT1V+c)X QM ,ڶAZ}}v:F 옿X?l"ٱ?G;@ gۍb͠eau]Ca%REaZjawLˏVgH[agHhb*5G{[#+,(ʊW S lߜ/$ڷK7Sm0%4L,/I=C@~exgNcO37>llGw:-器StLs"u`oFB;B֐$@AEG"-dya2,.l*')ޖƹE^Tq2}[?;TŜ_rIo37Vr8[eאa sS}6fkŗìsk(ͮdYfk "X.&)?h# $$z9)¦xrbKҔU>""6(/&?Xŷ?`!"wՖA0"`CCDݟiW_¬oua3_x<㛄؈]gBsX7m=܌9I 9̌Rfـe$?;f?F~@Жȫ'녊(ChXEpZ #N{;"$/._%a`袰baZMMq]r-4m3-0y'(eYugd3jv9f1ϯlaɮJZ$3^m&ZJ}=RnY-94T՗EY& 3[ Bke;&aRσ[Aǵ6GS@VSc'dMnwm]xɧ; N:$pVȔB'&caUJf:c*Z~]P ! ;j?ċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċċ;CwsuzW^ɍ^zW^zW^zW^zWcW^zW^zW^zW^zW^zW^zW^zW^zW^zW^zW^zW^za~lW^zvrū֯Zj;7:j֯NٍzW^zW^zW-vvuzW^1֯NZj֯ZclW^zWfV,X]pfcج;c4JpY%y6V}50˞:I` t1^YF-̷NF0A5s ,1?iթ1!E)Ҝ9f׶lۅS<_{LW5:W i#NjG۱H%G5ɡWц. W E5\ͪYlIR.lg-rg-rg-rg-rg-rg-rg-rg-rg-rg-rg-rg-r;9%g8SGJ6Zs6s[M 25mW(Spm )ی04r4!eFbmpuF^d168⍆S{V0L],##f/ghܥ8#0kmfdKY$jVcV,A  !@A012PQa "SqRr#3B`bp?3eqT䩕p8i\*˭CfJ:533izƨ`롏x0=~cyhߘ^s72Цx„}͛P~|iyK06cy~Tv5Nċ'pM5g hj(7n HMu0<ҧCA hm)Mi>X7 g7t˺i"ko(Fy rf +;Um /5= 4 (h;JXQqN-':޷g#ig2z{#؊@hn\wd3(Nr d=Y(괌yFr6-`Taߐo(F%p*U\wd*.p9LRRKwd,=l㋾߷g#iBSf*Ȋrk4yB)8 UDN9d`f2.v`$p Vm!5tS1)sdbB-j|IT\0GХeݴ'|P{YtKmo획Q)Y!2EQ<im 4WGysQ6FpWnWnWnWnWnWnWnWnWnWnWnWnWnWnWnWnWnWnWnWnWnWnWnWnWnWnWnWnWnWnWnWnWnWnMC AQ!12@0Paq"R 3#4Sb`p?}*j0áziMt#"a/*e,5?rb5S/6B=ݟwxg-xڍ~߄g  YBu~vƌjT*Ւߪ4VPUcJS\dTfOp%#^[;n>1E$^Ro,5b~4gd?w}_&ʮѤ CeH~~ii!h4TFQ*#EDh4TFQ*#EDh4TFQ*#EDh4TFQ*#EDhEJ_no2Rs|猔I^΂&ZZ 5ӳ8BB]7P|K#>by+.⮅YJ_LBsumyo`͟q}Hj}aDm7ךOa[SCұ4?}#D}iD W_~_?-Gq#_y{%`ɸNJu[#OsVֶW3sMJ_OjY_u,s>RGq> GW>~I5<,⡼B$Н2i1A2árxKB QQ1gevXϖSBkVTP*iU@쪏J2)m )uSYWvιxKE>t) w"Oo]yBdfUzzj`vmYQƞTZۯ̘JRDso2R kXW-YJ7)FHs9#J_ej]| x7)%])XM$ '/nm=5j'k]d+Kt:pCQJ CC<<[.;ߌ?sG c.Rrwr Qȣ ec\.wёIxrYFz[i!҃2E.@P׌i*ˌ8q&ELEwty3VH$dHQֿzm)uԚOE^ijE0ƻwє@P j3#FM2lKLwE%Eq[btϚIMA0 tIrHeg kb4Y̠$:tH9:7/Txa9<nmij-Y5zkP0ڋd/IhQx Pi6E4+m 'Ʊ'[BxjaW$w2y+xW@3H[Bd/Pӫ[}{o4^pbB$9q|_B#Ilo1(s mX ,iJڂ[dp LG"JdB`≒ܕK.r:J<}56ӫ[~mijLF%ns?-1-Ģ1 ?'<fU;[ 1#˴E•cz5 X/7[~o恫Fa bcM#QiPGv5"Aܘ^o3 f69(T`w[ >HD Pa pToJ]P)dԖVkloG>bv$3blWbL]_ +1v|&.دb9NM|&.د\ud-xDWNdp#Q?ث$Wݳ_kv|nٯ5f7lݳ_kv|nٯ5f7lݳ_kv|nٯ5f7lݳ_kv|nٯ5f7l$?Vgzw%֠W15g;*KӝAPw"x`֞֐F,qXQ sđ{奅qSɘ4!ö)h-I~8'G#U{т]ڹ;;U9$¡ӟ6(K") Bd=ϚCƹo6Uht(g1j2m4C$/]G4JJtK2ƑVK JFn$cQƖ WV8$s#9U"M,jX2q[HeIclwKYZ&^4W2E4r:*Y$dz'V]xeNi1Sc &Sc.S\ Ye2y{9a#yb[[>mcٍ0guYM24+WOEg"@NE\oDp^7T:wS̜T>*4ыYSΝ]+{/ghݣ|R"+i/_HDbHi++2&Q5~7`r+&.xTBĪ'X@#JTUZYÌ zsVQcFYdRoVѕNkQ.I>zjVk$"uiO\)6@B~ j&gh/;#L8=qpFQq}67r]E "c̫Ib?Z՗NH{ 90td@m챔`u8ϗ+8ӧ_ KՎBh25`>qQEu24# tHWYfd JcVƲ!Rwƍٝe \"oMrc|+U]2Fʭic䘱ˆ`A>jFJWmH)F^[wΤ)£~AljmWkbauy+v]JBݎVS[,dȗ>7 An2(:x e`ԏw/3MsueI 3HdNvd}ۆ1,9NN%  oH-J2zYy]Pm#gf<6InTٖoq8h#iGŐqMM27P(ItNk?|'4Ur`?XOO?M }ymZ)f`:S'ꯄI_¾' D+Ls2/Rkzfx%#= W$/_п|"OB= W$/M;#ھ h>AR$xI_¾' D+zI_¾' D(2^n\"e:yU *EsuM q )vzj"v"{#vwHQmfnV LPq0gm lҔ 13[Ddj(y0jM:ډ$~tk24jR-|A>Vk8tU]]M[\I8 1ǛnGo*rH';6ȏK|Q$]K. :yYݜyjKk\3{[i.2[ƞ0xս GndC0)FXT$0){Y6V|{ԙv5woڕ w;f7WT}&uyGvd.wZ EzqvRCkDpDt 98>df w;$$Tc$CU]N 9W;hF `o>fvI\~#EetS<2ɵVo\|kt-.^1uN*\gW'i~R΃4VqݨHoc}X*\sIe޲\ąHtr#}dya]w[KnV!Yn LJ% ?& N%;ltf$)H]ŗ'VI ՝8USD2`L)~X|ԮQK5($hQ*ALIƞw pJ*K\:ԩqߴhL4ÄER-8Ey=9 vxӺ~qy{׫^;46G68m6)c)#^j2$rZ5Z0BNqxx˙n0#ޜo=}gΝ|5f]P4 F2q&R9#nTk޷w Xg~+o 8cs$1,#bG=faEq=sr4YR19Y<Y l& Iݿ%Fn@S-$ddJ+`hӚ)1)']=mqd3];gHⶍԨ SEsO*G%nݚw:BI8!}n 657u$cX'XQ nM[)o-6E{&I;GshV <$ `yHE tMxq*em#]')`aܚߚu3,7hfbƜ]c,:1#7u aغ͝s 5H"Hsqs7]c(y5|\okF:eY$,O¢]slA$YP ?}+rh2{ۘ$~S7 H_|;*GþLW_5MllC p oF7h}Ƭ"-̅^w7* 2<*a8fmxpw2ssO|;v[B(zx}ȗR;p8 ?ƻp31^IdFFFj HĄW??+Rb74i*~kE,miycF{^di".>`OK,i؜߸'5bJuÐM릙{069?.cqewCj(v. F͠Cum![q9Ni [c릌!3/4$^UHOrnotms9,aCRyןy^~^fmԆPI0T|^rUFwŗ]zej/^Yuڋׯ]vŗ]zej/^es#; x^,E׋.Qzˮ^x^,E׋.Qzˮ^x^,E׋.Qz[ SY1dpo-G,}κhPsۯ]vŗ]zej/^Yuڋׯ]vŗ]zej/^Yuڋׯ]vŗ]zej/^巒5VĚw4X|t,LEWNVSvu:T[K]*0?඿Iom~˯%QJb73^~feDa#+;%}A?z[8]\N-ͧVntUYP>BׇFz?Qڪ$߇GYwkkpa}iL2]n ;ٱn[m~˯%_K.Em!of}F2l"ٴS}~R(i^f.,qj򶧟rUR[i;ΞcW2-պ*ܙ6nߒ;x1y~Z%̪ |؊ k]|--[uoi ُ%p%%at*}( gӬ=hbV?eB:4r?"`5vA+evc~1,@ ]o9GPbu89 k]|--[u[_{=qB y~X|ߧ?[C <Ʃ4Wԝ\xc}r c N1=ZqB8% ®䵓2|2FۍEAYNM<Ҷd0J!|J)-$3[>MFd-(8:T[,[2AB㮞Y8uK?_jTE$F@$6:r=U ^;SПVsɉfL~AQf99FKyjvl1l3IR^46@;|Iy˨iuIvj_w^K[p?ݟ?Hl>j<zrh#vSC>أ67cW3ٱ3g8[I(GV"= p=X Yl&޺BXV;j\$VTf=;odGcX˿뫖e0JT5{HUK\uE(3+G ip4Mj2; qw9#Go|9#C ڼz ?-^kZӟ>Jo % 9K#S` ?:"yAr͆9]Ϟ } yF~A'sV${tdCrS!)F!^K$PM,g ͓O2*ak,S!Upl 4Q,Aw$g OH!}ӿtgD(Jv+)]غ$.Tq'||+D=غfƬc1練^ܤj2X^ {]z7v+k)<3ˎ,cJ[ݛ6m{P:{鋭,vqlM"RQx!D{v|m`|?t.qVH?䠑BpUHb[L8Ǖؗw'xMjظiu2pRڮpEt#M nc[:,+c Mύ4wZC$cON5$݋|I:ӻ7:bTq^ݗ]Z8q܎^3W1z4M2GX* ?rݻr%\h;c wblƦk׾>Vݖ81Ƨ{c4XbJKl]; Kpuӑ-+w]JQ<u_/蝇n╴\+7P= spۼFxt eK[8m"1* d!peܝ0C$)Fґw'Xbh"4kzu[qjC ulO*]\ߋ%_.T2G[V[y*݋a?'T{"X8t$6PV'Hb{=80Sgt2ӯ}^1F':&<8ݟ%]\5/,ic<<l)h߾4nAi$vsz3駺7=ǁ o~;⨪XLXrt~5h$W1HH6ڜ]uL$D<7u|[ܗZ S|$f0w;1Bs*\A =DP1$yw"(b¨[ݛgdBt uq^4f -nWN,A51bE''pj}._Z_K֮}u}j}._Z_K֮}u}j}._Z_K֮}se$$ovz+u}j}._Z_K֮}u}j}._Z_K֮}u}j}._Z_K֮}Ce.$'~ku}j}._Z_K֮}u}j}._Z_K֮}u}j}._Z_K֥$X0s# 蝇GeQ}T:A?YY$ϭӝc4F7'f}(N>E]lDeRhF`y͌IgINg}1ۂ]&E.q}uq:022]CMEɑI'~:(4JbFO&@78 EwI@̬ڎyR オ20^Ξ5v=ͼRWIՎ=<)Iy}6pNEb"GSt[m NX#??&I$g $ ?teH9;uf`x9K-4!88h[U*ȳO^ѱd=w c}v/O5MrmRM'Hg4 p g??q#ppV7|3Xm|i%;/9VfIv*@#= ƵI݄1ۦLԂlԹ^0r}lBJZ떼akZkZ떼akZkZ떼akZiIFe] _-x^0r׌-\ _-x^0r׌-\ _-x^0rҢ_[3Q*|Oj[K1gg$c8u-.^qfIx MnVw}:<+^:r͂7֐!|;~| :QOS#iQA=?'UMT¨PxdUı-.Bε7>],7ԷA"ؑYߤ>\z~5aft|m:{tZ v2nU溆^z$]7;T) O<4t*h^8&HWNB6z97FB r#ːpWE/{D%@ߞIM2Z69WDز]] 9~_'蝇g\zAഓw6Oi0\g9ܞz=̈́m&Xr|hقE}A<۪]}hVmmny>ZMahޒ$Jӡ߾{m;8|xP)8Zdnpm;^mIT%%kuI}Z]}_VH-yOV}oulmԺdS|D=7jm-Z[cO;^}=6P^o_MKj6$b{mc7#2i6.9[vf)Lus7Vm,i5(C;0zK2̺$s./WK ʤżǫvm{I+Q^o_M+E Tq7'jil2Lm&(xo$l3hѳh9n:4;(Q|SOo$qʇ2 Ǥ1Isx#,vo''Jv2N̺$s./\˯KqEs5uI!5#x҃?^'u#c3>]'] 1D.k9W&oY6íwWR=Շ]$t'K%|--[u[_඿Iom~˯%_K. k]|-VDIR.I,n3ivy=Ckp'5[_඿Iom~˯%_K. k]|--[u[_඿Iom~˯%OVr@ k]|--[u[_඿Iom~˯%_K. k]|--[t-3"!)pI/yח0q ˙5aNFE=ʫu{V85ƎǺlZo;ko֪ZF ٖ4*>m;I@LS-u[Ʊ+TI1둳9ls~KT.i3]Uc\xRen47Ult]a>os "%O빢p>_Ƥk;`Ȅ'- &~o1 n9bMZmP*鏎᎚N?%w\*;шIҽ!GX*\$pk,~O YvY1jxO$qdml޴M>D˃=\EYq|{ H97V2mˬ]À^ȱMDgowWs'Hs>/j '2$w6&w|InI18jMZ>=pTHt. ptgiH{p4ȃ[Z4-neCaiEtKB#u@ڹ>}h[mmAa]yGwϊG­~̯Z_ 32InhF*=i9R-*Dgicj]ݏM:kơ)tmIjnwcFg&&֟q?imY;Go P?[_7+VFoW­~̧+u9z$sݷrMJk<l>o7.kR?99S?,HOʇ.rTffwʱ}n[}`4BF Cȋ`PKyf} Qϓʹy388m 0V2?-"H4!,vj^!,ˍJ:0,J]+^5JgHyg}f`JBiyuA$yw(ƓӼ)i%$*Ej?xDy6[G]4句H&;hY !&Ϊ4j:ehg$(Чw=5hv8z: amXW>c嬄l,j9LÎ=,Qp' I *Y [b~*(系XU趺Lta9|Hr:=äVQfǁ UŷG*̣=Y[446WEssksk=ՇVܭ@Vy3髅lF!*!PLռ%c;oq,6OehdFA]xxR4l'GGg`˿b¶Fhx}ܶ,a1xz*"i`V˽;UkJd&Dg 8nȊ$I#N)g!l!uYLb)ԙ7餐w'kx͂X'^ Iگj9MMWo2xr@q߾nqnPj: ׏Q!v6"6r.7{ |[[3+pHg[aW؎`lj8Z[8tk8ĔtPB/oǺL__543cx ٿ??|'UҘܩW tW~=e`IjiaA'U$7Q]3<[%2'B9#(X*ӿNqӊh| #U'ɼQa6þ/'߄yί.2=Fݿ>Oqko7ޏ6 w݃q,??o'u"WY`۲]}e' Lg}\A܌,cl%K^/J׋R_ԭx+^/J׋R_ԭx+^/J׋R_ԭk_aOmIvi=+hcϺc{54s׈jg5 IiZ&wZt&YQȜ,˩A?ڬ>osly3uM:NTWO2JtC$#l6*qO8{Xp"hKadq\|Mޜ|`rB~T?7k}ʌGڢ%ХԐBNT#ǒ'[av^[t4VQ448<7tqh\8;G)I9HU6jCY]&EXJzE%}m`(_7Fr6.2LpQg]U8vZ3M 75"Fulwm?,"*Vwco 6U\6W6TiH ,|T긽KEqXC, &gK8'Q=Lư#`ʛElw wIh*YW gDsʷ+#V? >@lT$Ả|0FN[|;"h؄9SmCg5l:a򱿦ovgW&}9c,;&Օ6ŧ] ko9o܈.]Up(@mad198M$ R5{H'Y}m7[Mۼ p$R(WmWPKj#wSCM]"fƽvqÈf>'lK)H#T '#9xzLFi Gt>Z7 s/}rHG@h(Tծe%kuI}Z]}_V_DծN3oSe%kuI}Z]}_V_Dծe%kuI}Z]}_V_Dծe%kuI}Z]}_V:j`2N]G;S]ʗ9p$Ş3?ƙs)f YqMfhzB~[@Ə<iOuv5;k {m'2:H.wj}̲H,}j[{-~ WsN%6gNs%<7i0iN [ _>*&uszh%X2V3婓4Jqh]N8_ ȶKeOXIF` +4S@8]s¬`}1̘ΐwWyG|{]:c(6tJltu7rm:$#\`qE+2q ta^Gq^q,?k+^',e#}Űݺ;gKSpw6I;R __1/?*?jgUEj:V.%O)?[ڭlԎkwᰠ0 3zƯE^%譁MqJMnf{9$XJ`5<=ǷP]Bd86EOupɁnahWqH4x'qQZ99^j֫V!#N<*BҠxNW7I}m4agw* b0{ؕ5q7t<NA ?ڬx٠). m+߈5&%tKPA]iZ]Qr7Jכֿ]&D8TT(%Tռg9Kgޒw|;G'z/7ӑv#(`9z+ }o< z(nbNQ62+~X|nPN׌⤑'6 S1^: бH+"ԏfe4qFG^rt$u $}liZ;WVsJT7aߚ(k5.p۝i_ 329.-6*m[W­~̨2He|?>Y/ 2+i߶{ҙ"GV;-W­~̯ZQHXШ/~_;b@m³`}noж}I3jݏ-[.=38.![w7]X80}k:ѡŏhog\-hdԱ,H(Z$ d\439-^s{?h4=?vn&'@A]9߼wP n8or+@d_-=i'nR )øv)+cv QxnH:H\mƭg]]иm'R"{{pYyY- *rHx^3W1z[ۙG$'h(m6B%l.4ctEv*1>gr(I!qEv+no&yWEm?.6ӫDYbY/&y$Rr(Is bdguًԯvb+w]J-nÔFϕf޷4f[cENΌ34Y tp,(y^~EܢeFиT$Xb #?;8%3Ew,LJE(7RljH:f/RguًԤʊ#?ga~|kgn)'LgJ}fYIGU%.`vA1 g봁6ɻj8-Ƹ4n?%CuŦLn&`kk&i:ˤQnB&wР%kg Ͱ* puKk +]C&86H-1};<#88be:;:*Dys$z2W;he5m22Qzjh<zH-wJ"Z#ՁXcMdpHߎUwr4 4jDq H|aGE"EAL﫝λ L3WR1CpRmb4Zճ[NH3G6NIߚ\DZT|~Ubee|ZKNy$mnt !ssYKC6ѵӜ|1]];kZ۽q჎i?lU3FnkZ{xo.vh<B 7V_l4}U25 Gu!ԳޝΉc`.#ѹOVͬ{Io|ҼNA{&y]=Yk[Ȏ1\ڶ1wʝ9褷Y=!.UHNsdIDouaH–'}\qkh.BN38>Z떼ak~}6fʏ(~S?q^1"9H^G8EM:tԚ3nc9,1Bǀku ZW\f⸊T^s#͎;7}tڌ m}b|WPWsiQCmCv7fvtKF'ٳwa&iDIr>ZvKre&^Ms3GL$I8UƛzD8+Wq;Yf}M'Iz0io{N{kOrg\-* hfim)]G?]_^4$wǐ#XunEIP,N0:M_YoI6/*=oϗf dn<OU $J@̜~Jvg]W g:νXߟpq瞜Јۭ|ԮQK5+DѤ5桌`Pa 8ffP3Qyj <1̣EF10i5Gu43!,6P8(a#W]{sH"}3RL$S.Ӄ!ˍ<>.r:B1^7㪤Mh{A^O KcMQ?3(AMD|ߣ#aUi- 1mR\qV,W>[F4,^!WM )faw&=̫P , QJ<̊!=uwēk0J<=DwqG]X=O4s ^9[iR^QB[Z'v(7P̆E-CH(l}nNu*gW"MAQᵝgv2s8h [;lƭjygY5Z6_$ޚ1 e?PHݎqZ:).A2DaiY:JzqWWaOlYgh@95s9'3(~N5o$I-q?*7 ښRO]a"\j:UWZ!Nl &xc4gBK;#RBgO^MX|O?)m˟y/5](%BcVzJwx.2(+O Z$fI<.Fy@G?[Lm\ʹӒ>1q%=6o?RcÜ;<ĬiK.[j+{~O/|ZKrpN5u:[&ߟF|ΐܷҜw  i"vKHrɯH0 SmMw@食?4Rn"Mi \I95xdEȢ#0H'L`o)D|8yiKhR7"4R(cEH9RJ"-mB8FPi%EW*`zYn.FM팲gF+ ;Sx>UAc%ϙ=Z! 0ߞH]j$=څףBO_C [Xd75rc#ϒ*ӴƊ5n㜚tid³?3?Aٗe ,v9'$eI9sWmn$D~J墍ktX@I4 ̆%H*qOeZ=7/f6,.DIX癡[":To~Zc9Xűy؅%Zܔgyb*s Evd#IVvѪ~"_.OeE s :rH>,BEG(\RaaTS/4V R m,ë4^G#MyǮx#):Wtc}&HZ<0F@rGCol9%G(phLƍ2xVRy0Lp{O&vFX5ܭŭfӝkz3iM]Ѵ[ɲĀrNV.ѤHƾ*"ջ|+r3~ﮦت4N'Bgi kjq >?՟ƙٵ11H#QĊ =hq$*s*wbѽO7A#W)$n4v#;p>In^hL=CRl̨.<'#.I#O<5(1xy('0 ՚ߧ QJzNDF<:xxc@v<2qƕNOX9ܳ[M[Xʅc^8gub['i?Vify Ŗ7#Jyj"@' 1՚൷b&YF0Zx_`hϢIIEr0?5گWgƿU_xjk?^5گWgƿU_xjk?^5گWgƿU_xjk?^5گWgƿU_xjk?^5گWgƿU_xjk?^5گWgƿU_xjk?^5گWgƿU_xjk?^5گWgƿU_xjk?^5گWgƿU_kjӜ<>Zgk{5v;^e]Xyٮ#k{5v;^qf׳\Gkٮ#k{5v;^qf׳\Gk٢KIof׳\Gkٮ#k{5v;^qf׳\Gkٮ#k{5v;^qf׳\Gkٮ#k{5v;^qf׳\Gkٮ#k{5v;^qf׳\Gkٮ#k{5v;^qf׳\Gkٮ#k{5v;^qf׳\Gk١6q⸎׳\Gkٮ#8qf׳\Gkٮ#˨-YA<9|\Gkٮ#k{4K2:K5v;^qf׳\Gkٮ#k{5v;^qf.h˓kk{5v;^qf̪:5v;^ pppqf׳\Gkٮ##k{5v;^qfm=oTRHn@ln@i)aidwfqKQT[}S:c8>|+ Ay6@=[c&Q.yic37[hb6h^ $DdXtm]GhPFOMC!"P̲VDl˧}w.DYWwCIjd̆ ŀ;V7 0C!<<eC&gs~J'bi unٌVetmT^'>-p- -T-Z)cde QUL:ݿ9u AYuq$/龎Q~u!ރh]OϟFD80q+6KҦ8JgjmKV@VH46V\$b-KvXoyΑq"!__7Yffi>M9ɮ[҇_bA8Ξe8r3Hkss #@Ie$8“ld,wථo]lV6mԽn1VHa72̃˺$᎔TRQ8|q -\#E:V3öV۸n|CH,[ܬ 7Pr8V봷7|7P߻娶Ji&=u{qrjE5K],0 uGeUn\ȭG#9ϣEXvP$N㫆Hu@dqW6lVϾ߬P3gӏ-w9 ->Q]\&o3Mmo)诞tqSݲ֑N2C6+t Ic%Czx @;@ ?(W97:wSnPɘcڸĒI X9N& Z>k2*})+ݻkgh6$SQ*x|,ZɕQ?w_NŲ4b'X9KzLhD5jd&#ye߻dy%"y61bS222xƴ,e =2 6YJn'71T&h)>3P%ØE+4XF?M@[<B (Co,zIV]y Q=HϬ® t#C^&H=AFGKB:cMѢG] j~U+?Ht~:pJ=c}K\#bWMlb=G]Az[MPFb^RUgV\ȮƢD$nd+G9뭜sGgg X9sM]0B')Yp#}D{dZj:*Wmw2+\9պ tt+@˘.`MJ &Nq& *p8fǨ:68Y-ǥہ!ThYZ&LNj1,2r Qٓ x~өqyvEyYEiҟ0ܺTV\}a(J4KO+&wy:eG|󗝌z*eG91RW $xUAnYhM $Y-ֺ7(-,!1AQaq `0P@p?!>Z2ܟ*ЉTƻiL<= {/x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oh.deи*by:hZ 71uvmw-^T[}ez:w|q.IQW-RaIՋH\8MraZRb|e2fϮ&s~/Gmwq8q ieT)Z֮/«rO3* 5i}j{θ1$8zxT,gu͓LyXϨWzJ+g]b]c5dm'b֭Fsj}<@4(`H=%N<λFV(iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiol8{J O%Tvz"zZ9% A#A}<,Nx=k7.G/D 6-:j@)R 8UwȪ u]:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӊϠX6Y"f+6pG +1`_%/+ wsfX޿|uy_ڶͫm}+)}Or>l;hA;@6 -?%x[ Z֠^,݉(DU(]8fna/ .ßrS7L&;9}a.fDm` M]p١/5/5^5PgUYl$#,\2KkQ `g5Gfp9nâ",%U;JzfRA΅3Tx3~-rǤAF[6fR+󟛉RTx= ppˆ!xSc @AYG֨Q`L@r{x,aHʖ<ʣZG(M*!ȭ1:a[t;CeQvaU8+ADˡgɟo;nJ_a} 8P[j]YjcS ,P4LmV3 2%R k-g C5[Qȥ nzS+.o00C7_ܼnՊ+Q/5 & !>w jm/%1R@I u$7F| 8ydʩGjg΀]亩jЁ"7HW~V9JQLKO*YlR ӭ?O?w/eo[7(R3C1c+h=NbqX] /u31 ~S VϠ8fdj*BSE[v/5)TSZju} У7*}TF˾"+֤z'5.`hU2tghqbPkьba^ Ǐf,6]%^gy7~}y7~}r6 @OپonF!~"1*H'9 _P|b~?g埳g~Y?,~?g埳g~Y?,~?g埳g~Y?,~?g埳g~Y?,]tgg UDv ImyU,ľnE% ͇L܈[fMtqCT-Znl{1tNkQp ݩC ƥ0κÆ%zAW JwBmfA\4`2Ըbo^«QAq#"GiN*&)fOeDCPyz M0Ĭj, >ݤN c:k-s(\+(֮͘9x8PzaH)xaۥTv#]n_ku6,R"`SK_QB7Ote ,$shYVf(X+DzuC|eFtEvJx,K@m[.E详5I%TʱDea)Y{ ͭm5XZ{<蚕n 5Bnm-l P\lGM\-:ۣ7Eq0P4u%zѦ_29֦JG020oK]6JJJJJJJJJJJJJJJJJJJJJMy=JV1I,#vY)O:a4rzR H]l(đ 0y"Wa15E6z<:KŞЗc= *mTөU.c?ߌ} ~4 ڛX*@a `qf)a[w91˳E ǏJa|L[4RnC*r>cy; sui]⡫ 9]<=uF?XYSIIVXWXM`k5A]/lq?m85T7d/} m R"<񛩬0!J(u jYZJ¼+98JuSjWLEޮf K]yVWei׹`4Y&R]rucjiWa nV0 J'h˄o]n x12a(:PKKKKKKKKKKKKKKKKKKKKLK$)pz:dd L|3=<]Y1J-# -WV:c~"ى6 յ.5>ls փ~aIsޮm^VQ%|Ne-pvy`OH_H88888888²K_/~?Kg~3_/~?Kg~3_/~?Kg~3_/~?Kg~2o,u،nlMJ=[Q9v1R1ZF-Rl*gE˜cbQ[˘}5 xjL/"l FB `ޢZrzpF LMDl%FwuW Vʄ%inJX\FG5椪 k65ŵJoCwap i Q@&X-;2 @wso.v? Qxqk 5//!I1}Aۣ*-dur4d11^!+ =$p>1-0FXSL퀮reig(\-Ο:^Yq#cXa-laLfڸAt dz^,yf.Р.3Yp+cnnve#ߘB[=! E*,Mk%`Kq TEKq1>* EMG{ 9P-јJƬewV Յ\|F˯BwA G.LY#ZV3!eIk d Ѭprj>v@Vlxvt ;mc)G1`c#Exi( ' 0#BJ*[,X( r̬ݥd\ ^^YzEE|L;sbgaz"+6Tdr>,N,՜3h' ԻnEbpX-f|# ڃvS (r =㶥%j5W,xBˑXiAQO x5mo7gt iϠ'%*oD hUk[z@[ʮzJonQTjr zVPh uo5Z ydByĽX~\KLOr ŽhHRq b54mhg74eَo b# -7uXv{#k<-zBN*PZGm.BTB4V'u, Pܢ5āG[BeN=gIqϧoFht6җάׇ jiBLv+qyxdsJmnPH*nUwfi (D1-&h ]r(l 10\ZH9c& }%]ꄺq윅_[*(uC&BfbD9X&o+X.0)"h3ɕxqsP> .WzjZ}#zy7bGe?Q>"yIAEX'S~_TXiUfoYHH# XBStBU4POH!u$7FkU Xi+6*4 #`/"[bKKMWT /{\Y. bhrn]CB#X7*iUQE.^!u$>)F^34= CF4wֶG?ȍ4hѣF7Y]Z?#F4hѣFՀDJ"jT}QjeBPz e@%q1)T5E p̷r0l2E*Fe=XrQ:ʱ:<]*>Drm$c$4m!TrjnӀ%pkOvkw]j㫃-'!9۔ΐuʓX(ʑ]W7lIm}sЄVc I*i7Xb *TBV*F^vFʀM O^ =dt8KEWEF[3Pl KqXR-Cdd(Zzb!"֍bwU1tÖycn?m@!ʺ6:XthF7/r1@$Ncwq2)#n%ltq,[r5;iR#I< #Ph>Ӵ՞;;eLn.YK^ YYm諈@ي'mکxcZ:h CTF@9YND~ 財L}x kxb/=ps2w1 7/U3dyL [#f)itٺ Ys7T Z, 2E / -tь2PX.ԬSKά8G(Rx`!H芡[CtXf2[66ك,}atTYȋ x"e)7@Pհ:le]Sp}03nݲ|zg/c_}2nc}!gwBۋz28{(u1a?kЋjӍKrX p+.kɻKЈ(k/JpUWLڜ)Ns2W aDu0 Le ϊol*ތ.4DF3miգ*C)¿ w8"O϶R r\'2_&SnÕO@Vs{--!6o󣠯B%oI Ԗ ĢcCW**TRJ#zRw nQ|G[l2G-,ʭg{`^]:RjvKx'UG7#9pf-VBsBT Z3l5X]V@vAeEZ*A}Xp)!} Z5Z5V_ZD 4r®OumS|'K8 Kvh#.׬m~T/!fY=V}r`7RW]JKrWoު*TAhUQ[ɄlX} RZ0i,&zy,N,Ҽx(/? ;7` "JV7=k"pm4݅ܮ[5G*!!fƠ4Ew%XUЙ_:ײjT;$0U}鍂`L#YEzo|^vK|u6|#enXAch;QK`Pmz_ FW >rhlf9A޿Û=3_geGM_щIg.\Wijۗ0AUCgFRnAȕWZb+^ 1Y,xn. 8h=Y8Q@bGT S>*p+Ëj$Q*'DJjj/W oh ]r )h\Ŋx0N0OzB]Vݛ gMUH\?fdvOؾ/?bOؾ/?bOؾ/Ztg5C߼2$:pY@-㯸IcԁF^/E_5)oX͸Hѵ)~ci >õ%fv?z(~TtF_mw;tQf9A޾D6)Ghzf<3w`m09`QǕj8UݒQMyUwz/QX +6RL]pE3J`(.؈֊zRذ] KĠ/邦#٦zٳf΃8+͛6lٳf͛@3:/.&_[u^Lq.`]A= G+?7VCK ܼ׷/r$N;=&e\F抭^}+M7d{ Tp ݱ0"ȿ6g:Z{De`F7l̲ knj@VX'zEk u:MZ z ftP{UG(j[f#!/ [_+M 0BOK[JNi/uLwjN^SJo2=uX^(*C{;'hBCj#I0:UpE.^eaup7b .^9tzjmwDml)n5*[oC+oX2 \I|$dq@֝ Z[a 1a`il߉{> ZYeXRjRWBd/DkBLOlA}Pl]Q X1Mc>V cP1 -p1ërZJ9ha|trTO H4ZԶh]eJ_gLadˆdв`ci\P=|s $])!~*UеNO ya1y6k7zO |~A cCeoM*kvUaeD^.%tPjrR:]H.-j)lUcTD(CZ0g{1+%UC:; M5t=>Vn8EVCдCSVX}Rv7^9'Ь[rJ)HCgquzlYĆMhu-e吨F=LFغJ0ٔQ㘺.wҮ 1`lPOa9 xК)8,ZmP< 54f!>FL>{!F*9e/h%Z_ybۆe=ѣP=Mx%y\bQ1bR@e5v~W, qHN,ګ,v~Dhި'z }InvT}W 80e8{}`YU|HѸ(wRibm@lkWiuP$1_E8#4!zXTYe6FqN;fxU:٘Fێ JS6Y3|} 6/t2]_kGWriyHWrDl ̭\twxnbpPoP)gQVf (֐:T&>-Բ؟Ԥr-f+.<Ϧ~{{KeZS\TSEm<sv{ +gzeܼMB )YQG@A{Nyh%X\Vg:vwYCZ؇C6e\>'0V^  ўkrc&ڪ=t0XGE&ј KJIv<יCOktq{<](T; ]⥫@N#B|{cf\@Yh̫YJ߾l?bNЁ›;ba>uRЪMx`ܭrb.ui1 /W`@5#Z9?Ho8+nqAD&۫zZlOar̭i-voTX-*Xg65OK06MPL(iQ) DTLKld[ ,!`]Nb˳iJ 4.kQjGlUۊ ẕrޡ o)Ħq=H5xNi6iTfqWql*9?T 8?}lVec4RB\ VuY+aH"穀ـ^a_ PinP>r`_W{ |_-Kvj8ΆUY&#TTǤC8f+t&3WN -(clezJ$AZ3\K6t<1Z1TGzj.#\땆__ !mSl{1c=f7l=+ajWYJV0?5,<@1%J6.J)L)^q)7~nױ$ѭ?4YSTGS3rUwJ(pf#c݋jsCO}efxPYj+b,j hA;T~-q/r.j%pE\: z^`컗gjkP}q+R[)eՎ3hй٨&3Ps[:dvYxs?)Q-#c.uZ>NrX߬NҘ`憒_+Eq:EiI  ?[_#p"5-h7q VVe8d lZgxm\EAJ ޢD5x SC%e\rPpy4t LCz/1hsK^r5'2Ln/6Y ƈuzLcE}&WATjEY0o:/g}*yK@ EmAt] g5 V71/]WUg.CeJwwS-xg"# rwƊ.dVQ8 ,F:ϡJ)&(:FVLuX*\#,16[t52ϘF:S+$=I$YwaI$I$I$I$n:ORI$I$I$I$I$I$I$I$I$I$I$I$Hw$ZI$ft!FI$VE㤒I$I$2OI$~|I$ft# z|I$k2=~bI$G UK_I??G#Jĥa*oln*Ă+l 9j3 }֘N O!}#Xȱ BhgF2XJV^wM*e579[A˖CɦrHRUەPi2{ud6K(68ܕaOhrӕ,Ag0T7\S0Xc],F8+ rrJ<"'"̶+$TrV/Z5vҚ5zx:m}IE]{OgPcKa ǽX\;IYĵ/5S^I踜lF8sYs\Bn¿/%XD. `U709FTkq0~sm|3IgG{R!J;`w:O6EiDy Z?<;Wt}W{-e~}'DQ!zM )FTXPx_ЛEXTR6b@YNjڵ o egR`eFwAj6Yn=+j )y eabʕvzE˗.\r˗.\r˗.\r˗.\r˗.\r˗.\rֆ0Xa̡3P0Sf]U ʺ B ^qm;*􆗡ϡD9lyADysûZ6*}5%pZ@b;Pr̴(m*q[l_4֕VUl ]f t 1| #Y(7zdЭGAz8UmĎY]bm{3ٶ&fCһL|[4x#n#N7gX, \4C6 U-nX.76[t5Bu8 @erD*`pi`t XRT 8]Y:DӭWRh=ayЄD˔RVc 2I@Vz]L#I)}4Gӎ/F۫P>ZPg[g 0,<<<<<<<<<<<<<8< 0 0 0 0 0 0 0 0 0 0 0 05n0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4M<4K4<4M<4<28<M$00$0 =O =,̰,<P5cv>ƠWϮ4c͉emow([<_K7$凘`෪SNX}TUZصr#`66ıx9Ǯmm%%[iy# mmf/<0gb==cw*] =>55T 궯7H&RM`eXtڼ߃#Ri94ӚsNi94ӚsNi94ӚsNi94ӚsNi8+!1Aa@Q0Pq `? VH#BgIB`EР.!o)e|1i}X@gq#!CB֧EQEQEQEQEQEQEQEQEQEQEQEQEP*QMI80?":D!7ZP3ߝ`% I&^(((((((((((((ұ(((((((((((((((("@ɓ< }& oEQEQEQEQzSKZ T@4aP 4+R&A&U$Н@M@CW#K}͋E)DPҎ Uÿqqqq5(gWzEW>Hi(m 49v ,9SMbLCir1]!˴9v.bLCir1]!˴9v.bLCir1]!˴9v.bLCir1]!˴9v.bA3g̝x͝2u6w<׌'^3g̝x͝2u6w<׌'^3g̝x͝2u6w<׌'^3g̝x͝y @ybab&K~T `^[|4` !DYULHTA9g*Y@ 2F@ӡUCM+@Zo"!<̡q}S3'V˄ E4PL9Z.̝x͝ gG1A f XE^ BԼg)W@KI9 dV;zNfݒWUmtf$\qTfE?7o )3?Oܤ\CTA$R¡/^< 1X}s'^3gh"E`m ×ćXKm~JYpX:e<+Se /BkK6k2u6wm/Q <46{=u?5Bldi3 @i:>u~AK(D2u6w Ax6ң 6ʨ J*S8A|qLfC* Mt5A9gBdD w2u6w 4bQ@-dH(Y `M#( ?e m2 VFB I"U%`[O:;HfD G:Y1|! ,Q rqt2 ig~n ` ׌߂꦳C:;]x#TcG:;Be>M8 ׌߂PC[ ̝x͝/+jt/2u6w%EN̝x͝'J+:;OC'h+8{2u w9r6?2MLO:HAb +g~ >M0@HV֖qę!+PR{2uB×$m[;d,P ^+g̝`C0>sw<.&`gy`gy`gy`gy`gy`gy`gy`gy`g 0I,!1AQaq P`0@p?(\Nɂ* `E@o@W^ 22"A]GM=G=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O=> 3O](G)iQf7|/ +_W2Z[9rTMz{QA6?f=5ɢ=ygR1D1rջk7=?DG@E3 (Qu7{CMJ6ʽ j•OB:.0ă"FQ X/14D}x= $sC- :6 }H>B$lE| Hk1( +ۅ$ iy{hh"˾DѮp<@ !Z %BW| =f,UptkM9@d}TޟL5E4{&Ou$ t Tj_$'M{෽vVYwu? (D4izgW, DG\ KJ6Zv15(xqF$#Ƒ=,MsK 4izc._Χ)ʗH$hkSȂMno02? ,1;lUME^k "fA#ɃPBE@R>dL3[x40""@8p}"gA /?uLQ߇2J*TVD T&'i:eJ|B b5\@?QlQ(R)dPM.> O'd2|> O'd2|> O'd2|> O'dۦK/xG@c*!҃sf \MY_c.haM{ {[\(*:Fpi QNp&FAP3E2z\"+,~ fN;d_[4N0s/IoUcm1ٜUm858pwYF"Fp:B,ȧH*p$ H D%/8%iJAQH 3H as^bQ1Px @ `dt 0.A#73Nٴ]  FNMu7 qgGh>18q Uf"F.R#Xw $"tDt m:]V%=@ m‘KNrp'68FkV93(UM@tUrI$! qô(L`;`@rc(u yh:IYtLg<&y3ɞLg<&y3ɞLg<&$P`A!dZHB&ojv.B4:čTEЕ7ZRXbNkٔ"#byPG <΋\P 0auCB!'zD4pݵVDUIéɇ((-#:䟧J^cV@GFTr o+xRi@$, ܠr{YaY-Gn=! xܹ-T@]t[)ܭ95&Ť®8.ocdrt2$ `N*r("XSMcHAGLYg"daoa:m۹g3:bΚG. :zcPD_bF$g@,ie)RR)xWH,GVbwt$EjBL⃵cEF|c@~d [hÑj1fþ=AZD2멣a7^' G#@J5I8d@ ڷcC|#XKL[^.UԩBM+:=b)tE!c)e3gӐQ ΃ wDE|{(@pGkMpp RNFtΎzk $DƂA/ T)X`j7Yz%6%5[~{ߞ=~{ߞ=~{ߞ}C+:3$,w,8T$i'O%"R91F>ELM&d34a3n<" n *%:)nݻv"!PDNT85,(w7q[#sS/_v۷>,[n,!%Ƿѧ5@@@tA:3L9 B~]u]u]u]u]u]u]u]u]ue8N?q'8N?q'8N?q'8N?q'4oܿ&~?I\ zFe4:L@۠ CXg XSW BYL&7mTɻЍ،W+W@dD Nr8DD^|P~wE-g&xuk: &-h3 B\XUC0Pan`t"BMGYp]ɋ 6Rs?OT/1QT謴YQ1S ILHm Gx f1%gl `!آ QaNloSv֭W5z)Sm : v0om>P!gW!NloSv֭Yl6b` NgSV/ځn V@bW&حS>!Jm4mȶţLB0K0fFFB֍k"Mpی6KYH %cHJ-M88gliDL24UEwz0!% ^f&T?Wz'C,NGrWMrn*ƻpڒ/L忢붭mLlkM)Ë%Az27\p 8:Z5d)ɠ8I@Ъx !s2dɓ&"rBQWs&L2dɓ&LX(4A5FRę2dɓ&L2eq^Ax/E'RTq[?2 8xG t:fI/ESyi"0`n1:)$ljhUK8m#8jZYL¥BÀ`Rw #z9c2(&! ƛV UĦ!DQ PjP"64"yU KU$oN%JRaG p(&^RL.ԍ4!/9 0U A" XTS* wA֚(U(S!%j4AG dQ6HH6R95E>S4Pxn@ϓ03";`mAOdRڷxՆv-U1/R"G* D&Thk80OˉBAꃚNCCeݞpx0a0;0${x0ׯ.&];&@( 20Gw)YUQ5 w*3Mb YkW}.I`lvN7#J ^# CHyǩA2l!`dv1ĵ)ai 4^n7T*D#8Nq>*&%u q׾JX\$GQ-z!Y<GA8&ˣfL ccH9̪p/Sy;}`G""%`txY5Vta€Ê= =1 [CilCxАH D4I9lS$ɴzI@X WE;7NJV٠L 5:SDAԈi P%d)]⽰[b H1%c Cd 4rBJEE^$;ޱȿpU8nTQq~%=-='@)"!\M]8/`Z^<CE"A*NPD:\& V,Jմ8Ɩ  P#QSebFH@H62 JmWDgXHx77מ`#GaȎqt'o8Ra8t} <9ӂtioQpJunsue`7͉EO@6r*v jL S7d")(PhUinJCtwe،vMU<9BÓG`\QQLq™ VMAx_DH&7ۖV P5:Epm G69 dWP\(' !)S9*~&s@s]EWb1֗r"ʡ$0/UpA]d9PP"SP*H3`ǐ0S8<]jy`zyP$yd,+H\^m8uLC뿇 l~SB!GD޵]6H阍:O[XP 0UQ'q C ͠T!9>D&KZp ET0 _*$՟bPWAovo|`"ZMh*ߓޖ=[PDB`{bNH; BCQ 72d|tU /JNj o dN[ \"([( scf9[r`1>,5 6"1Li0~Vz\_Yf͛6lٳest-6lٳf͛6lVbns_ijf͛6lٳfFz\%_\RAF%;"~>PD W%BӁ u`\QE>ΪZ2Rx %h9Hs@"5rq@tsQR[)G.¢KN!.ea;;ⱽ*n CN* r|b$6 "`7lKG˲R(Pq@L(X=FT\Q 0PF9.Z\%&لFYd^0N):"R" !1jybZ[I!lעF đ4$H&!#w bN*-Rp:ԩl3hJM6lٳf͛6m3hZ 6lٳf͛6P U@ c!@q~ S4h-z4j]; He6ˑJ{a3T/BюWioEe%җm&iI4 B#I!P]n\Qclw,ʣ)LyYEEEJ0PUG@tn/f!Ad&#!|If(ÂcV6KV-z<1FNP;+ ٢̹X`am%>#DޚBٌ9>"lVJLco.CJlX났N;5ŗ4ÊIVfښz!bXӁۄR S"hnl!Mr&>+!^}l : LBkވ ͦ`1e-]?"!p=UTBuQ[}MYP( ÕMJ (e6@cB sUApT!N 8V" ي=eRQ;:*ie@'%ËM@s`Z .βOB SYd$ > X4%QJ?lәn;u Fi,D95И@@](9ZFFP7UNAD@O*Z_@phbzA8~DBݓhJ6 ʞV"t+DQOǯحݽ&K[P:*t7nSq;6$SS%Cz: TvNpZ4 LTn@AwPިC1 u;8QJswt+)]U}3ZI$ZDqc{?~r̲$V]fCD 5D(njWnFKT"L1)NM¡ hDց,RIQ*K"&ؽZPR N..\b(ћ#mV*a _GJV "^(HZȉF XIIORFFLLHMVZ7@  eW[5ʶ,#u,Hx>1:)6 bNep 9G+9z`h~ c'_(crUQs+A5 Q#.֣*[9}y!nؾG ` 2N=34"{rh4 eUZ2H"3wCI0{v 1J.ۈ "<8m6j\)~F奺d@;cBo. ^x—5m #Ne+(ģY akMW҃"+pk0-P=@KNaS7 d<4|PAuf6xzd bh-d1@8˒,O1qܶ@hwp\U:`ۗрj&."xxT ؈[gg^EV_BcX#oGR .? M46ƵsEX4n@.g8eAIO/ի@` :qp_#_g@U{1DZ;r.@_GbYnÍN;u^e"v:mg%y]Q#EAu-* \T}2Y1]}N/+\?ݎ9$P+!^xcݩ/xJd]t#NRw:4 7jj"q@P:EJ e1 YFI.;A"ɨm :beUP/8\6Ab ").tsX@aa:\=RA$E84hxlǼ^ @Y6zR*f|&l"4syZ!ΣD©@b͗J7.R؞ǑQVF'"uGy4ⴚrQɮm xsIw0'At^:+ ID Dx_&rX7r޸p= ~fasOϐ. =v`t&b>ú^ iJ1]ή4}h8漀@;  RFTM* \ Z j@^JHZ3V2I &r$`,*ˮ 7u9#jk>^sҒKټ \h ࡾ8UMDZV'D34ˮM %XlZ<(V4fX !z48IEG0O ݞP9{K9.B oHn3󚰙:^ľջƤz3:#ȚO,^R=n_&&q3[u@5pc`F`< ^BM?4]Ok#ٛKGYӉ (E e]ΐJPB‹rg lg!5ЬPcq 9nIb`T3́-XJVk4-㹰iCb.O ~<7f . l! 4@V[APhN3EX8ݸ y`pMȿ'"$fu8m"UӦ 4׬ p:qZX)P7ù?NED$`{1aҀW@)6uU h{9 u$ax]V 5R%T 5URe6Ew'ur9]DgW]R$M#&"ua8{GoWz>k O*,>vvO,k{r* !<BUWjٳf͛6lٳm!JI߭fao}vAc)gJaT/:z: v!~s g?$￵A] ui1K ]ѻ[w1p/=:Gfr% ERjuSz5[ R5jolٳf͛6lK*7 ٪pb `hLs'O6h򴏍s3m14x:0t"?^4 庉Qt!٭ லHR&J[V iziQ᭰*7)ʭRzS| "VƜaVᚎFqwUm91#cfucu7a+/0U$ ]uNPEc?ߝI5`F 5\' \F,$@Ie6V [ѣ DnyQ18P3Q8aC=Iy !5TlWAAPDۅc&DL%MfSR5]lksՀMH!`w:\8Tа^P&BUT.3̲js'дG w*"Vj:GɸcwCZE T[wgYs%@58AF;&zm#s_~8PZJ@q|fu!f TRE9S t+hƝ)Efƃ* W;D>&L%-s SvF`.9wЊ~+[@[U,z:Tu>fc+O3{j &&L [wKLq2dJ\g9+o@ . # ܍a]3ET m'Y1ym:FcvD ܈Z&!V]Y3L" ]brpӻRP$ t/.?1'mRIցo4&4/,1)vrkn{Qt?L@GA8-q/5+ 48$ hXP SBD0oȡ%9^!kZA43ӀJԌnj,N㓷 遀 Wn á!""5=3lsD)U ҭcB5^j<-*^)1[jb/Y;kXO(A @罃}& 8o1$ECDu@}m1 нNs§^ä@Buy"#`KӢ05yv't*p9zr)xD~ɂ'{ ٞ)s1[qhu[NiN%UB&I`&b 5wVJ#@它۪>jи\mmtP+M`>%-HFYD& U8<.02ISop s?d 0< "8S!C5G,#po ^ gcG/2ǴRU1h` !P'$:Xȇ%es{2H6Q}3ٝ%="~ㄪHIb20"$ 1u":,oCx@pU [h8ǀN\N$.v$*2F2aoUH .0FajfQIAmaUX- -x)[Uh@-&D )(滮X%@v#W^B%pTxt;oɔZ&4tP߈m9mi4CJiwi7b.o vk( -P:!ռXg(o.tS8 ) 9> T\'mkOu^:#tΘ)W"MxCpu\UAw3I) ^10608HCraF_],C| si mkx (t:u&^)+wP:Ph:"lMs'&[ ƄN.; E\ק0. mibej#Kࡎ4ymngl4B/S9Bh:p̲0by?wx }`w@kAT ]ZvƱ["G"ݹ9cAfPb@@A|wV&4v'Rb-?hC$*}$(iWj 3tT?\: aGP͗v-67AjB&^s&IU\0 )bPveE#0S K`kpwFPQ5QETd/%GKM F Ő(c FAȉlҊT @@[u16^ 8ͬ-kݿFu<͖nr|VI  ?mՀjZzZHbཕƺbƎN4e5È[Wdbɬif+=5CP%Ƶ͜`%BX`-InNޙ ~\)xЪI`t8LH=qi"~69 (dc`_.#:60B>M?/}|шV y25c +zG p;D35֌B]48%W(BGf%EP me$EDq^DZ,E# Ȕ2%Ou[3x' j-+8e]KQjDʳuB!f[&0 DL0;*v[m',QN4"m6<&K. _S Ql07Bۭbbe0/)X (r&&#yj*f{Dx͊G V"-K &ӄn&j(Xk/AmؼSZJc̪dV,e Eqѹf_A@ SBUxQaP >Ind]4P @+~@Dy.>59WΡ (d)&E; WXtX0M |؅I:Pn.IGqB5ɚpxCxv GqXtwۜԭ WYj6 ~hѣF4hѣF4hѣF4hѣF4hѣF4hѣF4hF :oLimr(fJ8BR! :„V"@G)R( F$]gxĬPEXzo;|48h- h5$1.E"lT̝^ou϶*4c|)јh\ :$uz9\)Pn0W~JP qu϶"A-$`p0)LBp*5|)mRq㎼ endstream endobj 33 0 obj <> stream x  Om7x4e endstream endobj 34 0 obj 728 endobj 31 0 obj <> stream JFIFC     C   8" 5CJm\#yU6t2kl\mqge}kVLXYg&{L+ƺ'M+.h$ Z\ʎ@O\+JςZw>ʪ݀~o.kYB^kBբ},ݫoYYK\Z]oj1 LC11 LC11 LC11 LC11 LQ,}15q{)삯y~肍 %ⶮ((yD.ȳNgSarɿh:"N+ءzhn_Pth?WP\OT'>xQ= g7]е5g8v]Eۇv]qnEۇv]qnEۇv]qnEۇv]qnG2XVU fGO jW}UGW5{ ΀PћB}X V 6f6)*_'TWސU7;%z\essՅ\=h ՔV5sVbDFz@bj=GCW9y gh!խ\ԗ?1jXV&uW&Ϭ9'.M{>7l.WǥԜל_3.C|۾Ugw5:[ܻuzG[]jӚ~NCg]r?R O^.]A͠[cGwy^N?OrKzC[]jӚUr_Vu(u͟G|g2)#A͢[gGsk%v-rIciKh΅o ZmVd@BeOO4I,6~8E_x-|R򼯧ʯmO1 .E"/)>~䜷{Օ_ϗ}}@_O?L5Yy/v뾲ρ~ğ?Lkw>bqpӄ?C[_oYÀK[pwԜwjcIvK`} ^A1}|oR6Xr66cf, >u+ʩ-Ú-³9㡊\X\s 6/LX<'οJX$6flDaNoK*O /"3#?WjD-",\Yjsʂ+7OPfC*[mKeð=F# @<ʨ:@#?A==d6kKo&, m|m)"i"nim.Z}lKqS 9v!b2}:;qiLd=afu@UH2R(P*Ue+iR$i:O 2"DLi:oۛ[W3Mӝ+qtWDr[*H LSLZS)z)%P)E]J%WC3E7NtүMc-1)JxdȦL^\:Vꫜ2P/& .ο*B_&Wn=V77Xv2͡p2 C!d2 C!d2 C!d2 C!d2 C!#Z$2.6̍s nOM*MнVNZ_2Zey@TwPAuA%<\{zKUI]H\n5FWU2ϫs_S0n]F *OA6,G,i~Em Dr\˦yZ*z헟~[Bԉ_Km~)ѥjɕgS>$)Fߑd(z G9WlZC27RK. ~EMdP&=A^Ǝ>Bm*kKUI!MN&C!d2 C!d2 C!d2 C!d2 C!d8qx/^8qx/^8qx/^8qx/^8qx/^8qx/^8qx/^8qx/^8qx/^8qx/^8qx/^8qx/^8qx/^8qx/_xn=sOU 嫶[Cr"T zmظDv"<Ca !1elg͟=wXֻT1-z`\!br۴ٖZJf RĈvQ됧PmHdl"4WiW&KYb++dG]&VK}\)0j3adHyWΖN3=>y;w!Cy K,GMaý{;w`ͶGr;w(QܣGrW}d y;w!Cv .I=;w A܃r=Kor;w(QܣGrW}d y;w!Cv .I=;w A܃r=Kor;w(QܣGrW}d $HK)S2$˖G)BR}œ/iq.)֜MjZPN-D$/mjJO5k@RuOEnKƗ7SuIp)~OQ0q6Lr6ַjT)ӟ3sA u7ӞSCpur[K8̇} R8_7er_S=&jQR`m2(-M)I7ģgN|)LCu!run2N|uhMZ?dž/R꺧$iD~eK~kT$>in݄ktpWg`DѥhhaL(Ԧq X\o>dZsu7ͅ0d:Xc Vb'D<le2Hdt"/"? GI:lVJaFdS$iuybt-qAVKd794E1%8ot _ +&)qL(Fblɨl]#͵PTJ3p27 _ Bc?o1ķD[Ő'9Onh˴ܯY*2~g] n+ɐlb efkq$TidƭـD܈c7&iVAMJ}[fLKHLK))lĆ$1! HbA c #C4Ǫ@؍.,ăXȱĆ$1! HbA,Z/1cɏ-FR!}bCĆ$1!ؐărL_abC{~HbCĆ$1 ycȱ"Thtn~~]Ex< i AST-U$&ﺛL<39l5ßp6՟goN'$ب)߿qTNq'7cu"z {5^5r|nX~t 7Hn pŚ]> p )!Ct )-A  `Yxb+t}׈!Ct 7H$V iEeH!AGOn!Ct y+tvm丶 //!Ch DF!-Ch D6)J~[%'h D6mSd D6m!_id6m!Ch ,Ch D6mQb[D6m!Ch*!Ch D68o*R!]e'Mx7ƂO35ڭ6ޣ{>~!fP&-j;L< =Xn+5PKΪYgHa0[h鬊~ErdKihsG m hs{-LTQ4D8({ȆD2 -2dA c r:d"VdA,~fXC" 2T>RK"pd@&UGm3 ܈d_iȃC͓mPvr!Ȃj}1Ln 9 !dllr2!=Ȅfe="!Ȉԧ!OuBIǶ Cagau i^x$`F $` #0H/m'ahl46 A4hVAm K4SU!YG~QG)򊥙udQY2mzl]c7 |?NeRKm=a6)CCN)$)E-ksD6 KјR5bҤYL6&R6KvSsݱS,g&t9epjC\yROob0Gg$";$"gݐ1Е#F uTq!hTScê{GG'ˠkiQPgi%e!lD4dCgFWDT7 ;K*c|#R"1 U:]vm"4bj3Hf4vާ DFVjR|:>hU*ZkKeIy5t[ip(㢎:(㢎:(㢎:(㢎:(ᶐxycall66 call78o8裎8裎8裎8裎8裎8裎8裎8^/Ef@c!L=2c!L=2aKi= 1!AQaR "2Bq#0CPb3@Sp?3eqS*+PuڸuXΝ*ֶUHkޫٲ5?Ĕujff$\+~*_5îz ]e; ^{p`:#ZwIJUÚƞ*֌iChӾ˘P67'[0P8Faܴ1_re.~J~_=cVB R%; <6{_U-7eC~MksZA洋{֓nNJiaexe:.WNh §"R[(?Ž]No:7捝V1hK`oYj&ڵ9$sZIxhƥӭ fg5̼gEI?6|а"7ݍ?n64b΍gUmtBA`,F'UJZn˵+K1t-Ѯ-Ѯ(NetF^iO>`] h@ޯޯw8 !1AQaRB "3Pq0@Spr?[2̪E`zl6y;#g Qdp )rQΟB˺(䣂fm\# Jh~`Ͽ+Ewp\!u.2fkd,^QqPsn!ju; P򯿚j*:'VxPThUt+4kTUzD ycW/,jmVZrv@G}W6Pd g 'O6m{C-1B'Z*Π!ZV5|豫{hVZrv@G}WxQנieW5e'̓mC跻nǙ'/ ֊3 ycW,Z&U2] 갏5G(NIBF[4U,Η-t>^mDHakX=PeUJ}z#o&&K|F'd})A;̚ht>t>؉X-(:U_HĨעw9ĸ쇥.up#b'Ti`jE!%o[y-rT"v߫ (To#xXH (7P !1A"245Q3aq#0BR$r@Pb C`ST%Es?#bAYJk%*Pઐ"?bQJP? A!+G62%JBҳB,\-@r O.pM6t)8P+~u҂ N8gI.f4vrQEN}-*װĤ_ *ZIAHjÒӈeǃUꅓTZ0Vx_fM,L=hlMtD.Uɀ>6"Y񒵄% k_D?(ܛuOSǝB}W%mRؽ y6\Ah9Dy9*D:SDĢY{rB}W%mRH/CnÒYfo7WB ֆF @gAGR,>p& e+C3.0ʹ> KKvD0kvGRmNQ$*#癉6ٚCM#Mr75i]q݃Ly>;)3uSybJbja ^LabPP rL&mmD7nJ§RW.'X–zŊ6ev!evSDQ!+YPnLL[:@qph\6-"Q@aACcKl8dma]쉴!! P'/!ن\PRPˎq.ٖ2i*'ēvqdunKGo)3(r@Bz4$Y\\#8ø֖*Ek ~CR_򝯾1)Z*<2MMB:~>g}:g ͮLԾN-6$jm:a%L^')8 PWN@Z1J t1 X~("'-[eƵ;nNgM-¢UN5(.na/&hˬbW?lrZmH&]Ʌn8*D8!!nc2M2y&NNO0\ RyzIC >g}:g + %#-XnVbWKe/)=%ܺ .̔ah5 nЋP4oҐfi0Pjfa!LX;/w ]͡x884t @q*IBM*!y\0# ouJD%n.\%em(ck1Dvqdun0h2*aee6X}TδԼBb8AiAQeA0ŦNq~/4bM/^// ~P?v%m F6Ynx(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:q⎳x(=NJ:qyQB0:Sr5bMJvG.%)L: kOE+ y>ɑT#Qup &+kJ[d+ךG!2 n¯Wg="y\\UʣJsii3!/RRwSb;\\}Sxy(bE, 4i_|>Sj_NNzPVr~\-B.טv{`%BҬWvf [o.ӷOǗ-q62}%B.Su=1ů! }GC XK(H֣\}H,bbuRbXCa:e"u /Pv3-g#puBR5JR} eĩV}!\Chg"]tP S1ǜ.!]oxechI?zNF}CBjM28K1]T}zzk}\cW$Q8qɇFY'GRDHq&ӌ>ڷ$n IkI+^ߨrͲZVHta2AIgĹdBYԭ }-0.`*-=RZAb}JKx+V$(Tw*G?jåB}G2c.)4m @?Q2ͷ@,M GoIQTݰDVW85JIJ3"h8eiOuC28= l4dU,<ѵJG4*C(ZD,+~q?FeByC5 8ſEw9Ę9'18LvY'j}_e_I\52)*Ry1<V2,oK[M' RP?.aA #inyw}֛co;o;o;o;o;o;o;o;o;8W}vz~-e9Wg:::::::::+.U5GG~?tt:?BpNMi:&:&:&:&:&:&:&:&:&cuyWg :?GG~?tt:?oWH?tt:?GG~?t!nw }֛co;o;o;o;o;o;o;o;o;8W}vzm`nۣ~?tt:?GG}4GG~?tt:?BpF].X;mZ+̩ &G2Ir!Z,%rRqg4u4+`JRTLq|Puҝ8+a!¼ǢL~ )!JNaI*RWB겤 `5EIHE2Nt%x}LL!>nXL*Fdd;"M(WM!7U7h)Y}gQNoT&!7BV[ҚZ$C/:hiQ(}Њ;u˦ )7+tgp~a)4J(_zi/eZ p3ɭ{L%2R+p `D{?N]|[kRi4T+Cj.K)Ʀq)xrLP5q ' e:Fi~qZz=1(T ;A QMlIEC`;}ͻtOl"rn||֏ NC58EbjB\G`QrH%;JEa9Wۤ%GlK/0KeamWɥP,10?XS\(( kaEgD e85U"4h#A2^F:i )@'n[q1m{+˼jZPU#A/F4h#A(1ZIiځZzpHPe)(F4TcAF4FenT%@.fe B\qԳD.>FԈF4h#A4CHfeD@ un@V43{'O 6G4˫S10c(4(M6;qMQuR'_CG)`D$WӀPJ &U9?#]80x\ͥx);ɓ\A m?a zsmy$CAF<&P̯ߗVK@r^WܢwfGJ{9@/ڔ*,.lIA*H =z]be v"Ie;"z{*e3<]hI76 mMrI|VD[tH.o"NG+tYKDrxO ,˗iBeTs ˰&` r)X˜rM(,ͮ#\5$ChT.clmR6DĻo銀Fg06>6clR mۭVX0)6ZZc >A ,K6VX~clm+clQ#.ZZM`Vػ Gl{lmMclm6 @qTT=*Ӯ "BO5e"6vclm"6clWclm6`clmm6b6clm}M#lm66cl.}p@~ץ+L&NQ`92ۯ_UnvJbmssU Q()/Ҕ-s_LJ-ũŔu:1jIuoٮ.~k.m;5WIIٗy<̚u\A ZzP%2Eijtԩͅ5,Xu63zΙ}a'y@4`o|WHiV%+jH6W^oB}x+/(! usi3d+H+}.i& nF#ñV]cJ\G\l[SB 5>tsG9h@HY4{5  =tsG;8JS<>Hc#4<>Hc#4 (sNyF-Hhۭ;c#4 Ґ*I( J)9:[[k 9{VU7tb>|"9Ds#G"$iG#C =OWu?χʮy=so}'ȒKp"U%Qa a ZVyzd 4%L㧪/[s2@I3<#OU('%z V×Ma*Y.غ"+@H ZTd28E-k"*򉀋q^J, ǻq"h4/^1*FjR[!/ӝ .į1=y@5m-s)O\_ZִPEcPDmG.P(Ӯq0LVeE}p6k %;AՅ74 Z 06r)לvX[i DM() (!D:z˪q68|hSE!M=$52`N)RN͙8ႉp4ZrqɭM)!LD,ۆ+*YDhCp#.- 셭1SXJ l-"{[$"rG&n.O(vpUjr[o̢ }! BP@(e8B QfP۫l6(F]kp2qŸQE40ښ jBG!:z"ԃONpْ “g%FߚQ*7DD DBa6ZEH.#%jÏum4a/}*RP?#lUds4'40[G%E5XE.R>cᨹRyKQ\jnZԣN֎%)rTRi87J{"atXJkFM%J&VHèC&ՒI0RQi쇖9y`/'X E; )8dkrBuTM`^R0B nPAQ1!}%5h#5 |<_0#5 |<_0#5~yǛO<}6tyͧm>iGO<}6ty嶕yaGkyaGkyaGkyaGo VP>ktF?lGDctF?lGDcl-!1AQaq0 @P`?!FY1GN܉tvGitvGitvGitvGh Nӣ:;Nӣ:;NӣSItvGitv۴JbtvGiSnvtvGitvGi='GitvGitv]Nӣ:;Nӣ-itvGitvGitvGitvGitvGitvGitvGitvGitvGh( `cp*7Fh-SiSf!`7.6>O\!#߂ ev!c90lϼ`a€MC!CH1F&&ۙqE#c9$gm@ iq5 Gja S]) qK=ᓽ $D\,$Th%FA,`-U+! 'Vwnm25e"boSQ iX[ 5'}P0+$'`‚NKAkɑ% jh!q+&I K,9;sOm!Ŭ:;|8 Y;ѼyܜVA-`3<#6)VO\J=Hc=6?PIFA8GEN=L|"ANbRlFzP5WG2@- ҂H(rIwr b%GZYz`^X$+J@ F: *5u A#@fSR 2Adi.'1z> j3;Ƀaffv zb0U6ХPHa#p4;aDPc3p+l!X{@֍ nPpnj{@r."N1O1b ,,IibXζ&ho.Y{$kd4|S4RuO)*F:`3-XtgclJ?!1--UAY65 *"Ǡn7Ävt2$mMxy!HUoPF0'{`Y"`-7-L`APqE"Ԭ4٣(D塾`adH"Ad` @hY`Y⯝gD*;CY)hy_hT%%<(!PW+EGF=6"(AҜ+&\@&HtmvNU,tɻZ8V+ ߢmf5K]#h;t$)(F ZUa0v#$26i,y}a|=\h4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4M>8q8gulӫ[)RvuȐw7(TUaEi9pلs _$ƱwTjtG9[ALl;~M¢ bN-(!@ |UB "N[#*;HV,{TBS %B =uԿg!H @@b ѐA1[v$*Qp{6UvۤiZJ8+L#8)s+-,0Z;WOJ;\dUZSTY6Xu5N nl"(Ӕc21Duq @ LCa (]gpmGڎ]`36a, p  JuFD0P&d(gDIik]^w#ԟ !u"k1æ 쁫}3-)h0S!/D?Ma}yeJӳ/:a04cؠu\,5_4kH8=sCp0Z-2d&9щWsg5T (M[yfK 8E]gUj5Tցe3(ֈA{ڭެ Nג#d oUUUWBiuffffmv=6i۟Q/۫ǯV z'wwwwkDzmJl?_Wk ۏPҪM $iGeL=m3{"!*/B-/vEk3wAL)qeʕMpbwħY`,p Ek\r Jb"4 $m&6jH~%rl "|F"+u`Hrĺp yyZ_oOp+ A2B idv9E8.%sW*BLC$zax>(@N}*0QtD B2qT-0 J|!w *ֶAP=m R$&AB  ? cK%^ee9N!o/45!hT`݊>ЫvНi1t!T9$2prv5T;=>?^H\0@ 㸄`"o 9;3^Msz8|($Kh@܍ VAaR4& S}/s._D vԹ 5dCX:=@8v5Ra4 'U5T;=>?`z2 \+V8Z&N 4NjbDQ ,b EՁ D -nTWn$ !] 000AXBY;*N &A14Q0:E h8]sQl;.AS% f/$ n>TeKGS v$iaBrj~lq6']S'C%Hسv4N5P磥(c@YLG KtE6V<  ։P/qXw(ev+BUc{!( 7G V@<JWqqq[q1r63m88888 8C}0P[`,qx&L/fq{N8888 q."Y 4wAUD88888d pCi~ a0^U{g"q[`O"gçN URB@'O<"xDL̡W`* <"xD$ `b)ϭ7R@0?}>GORhc4ϣ/xP܈?HņP5n08 a;K( Z,D^BEApC1HP!`Z."# K{&:tJ+Iih!'@1B,AzK\85F#Io0R'@gEBZD!_> @l`mFja PA hI1!& )`H lTbn)j2 Z@D6I3Fa7k͈(dPP[!Aʍ@'ìo,@y:P Ā' 7\ U~Z´Pr@ADZTAra9$Ұ`&cd }LltŬ %+ɜU ʂBd4p1؟@`s"<ޅ`9Pl0EIQh9c,0i5]CS 7 bF ew0,*5kLH  23lD\"CAԩ_Њ ͂ IL~ wkyP hck"!DX ,}KPdVp&. (p 9턘c>_^"(* `gX^u`п71؉5`D$2j9:&Œ]`vr*U`9ݠ'#yПxxxxxxxxx 7yi$|CY?GYWyd6O% W| CO8_A60C7H3\3t2qBޤE'gs o~b 0-_^ wD|d|@WAF 3}k >l8_G)-*vpz`">!gTg ]^86Z :&:T$Cw m7~aqצFkl.eV>k >߽{M/'X q2G{K!Yag]zjqc'g%QcKfrdĐ@g!xad"K EvJȇ1 bnIBjX}zfd, BlF2M&~yms<*N~zUV\;0 *ƙm߷,{vdTRJ2J*T;5*TeJکR*T? i?v8q8?8FqybO(H/Ȣ(Pqx‚@Th()f֪:I?u{قE?s~+W o-!1AQaq 0@P`p?bPBD }?wAPa?< ~zOJŰ9V] C JB7m x Xa]կ#kRGPZp8Rz,`x.u!#(((((D_KQ~MI:eipzXiԽX~w,-$ǵ/L r#Y$k׻-8Bbo{'VN@;{"Y%pI{b,d7D}ێtvIpLdWO=bɮD|4츪TzIbIb1F#b1F#JBHP4414 81v_eiA6ÿAT^xV @PMv!P xZx9bloEx8 ڕ$` Tm\!I٠u Քh.oDX!;xhW49k";/B% 9Or7@""ct쁴` RwYEHX$b z5 3VQ8PtR5C [+>N@`  k.ZrDFx/11B,'8K|NJ[J69ֲǣ [`si<:xOHt04&ĭE Hٝ(U0uْͫUFzk ʖݵ3/՗XrT+~yU #O1F^Qh1L@`!%+Qyd"P: a][֢G2Yu,%既ӕFcʌGuYY_Ⲳk+++ʲ ~>? QE_Z((175[5xOq8zE=x5+=`@H6vH?iHpC a-!1AqQa0 @P`?(\OTd}Lhm| ggv{ggv{ggv{ggv{`8 `3c;=3c;=2y5Y`v{ggv{c%d:)ov{gg2VC0< R3coggv{`3c;=3c;=0"v{ggv{gggggv{ggv{ggv{ggv{ggv{ggv{ggv{ggv{ggv{cktH◵#Qb)j`w@Rp[6z^`.oԝY4u-5fcc8Urn4cB$BHW:yzC'{Re 9%O[աƲ !L]NGG :)@=c7 RU+Ms>@,qJ5Yd-CQPz hjP̒@kf 5rd/# gv,h I:ܢՋ&RGU#\ ٯ]ip q#Ppd"k:nA$nW djyGclj]X3Ŗ$c"BGB,9a}`6#`)& 1 2H#rH-p/!+h2R8DxZ0 f} ,Q"%wKc8t(F/Z $"M݂BkxEF=&aUѽor3f7eI7fl1)V݄+eskAaQק\g1Ub^(k5؎EU4֖,&w/tgDuW`m+1q+6G\F T"fx"pKj艔*{0$ΚqHPll -CP(MX `4LXT͍<ã;;;;;;;;;;;;;;;;;;]ձ"c=}fS0ir}u %6rioX] X1'8oKxrxGt$ 0Xww`t*3cZ; /EUQ zp #0'nc]A2[@jq3`si}q퐍@4 AZ'ѝӵkNzk7zD)B'=Ni CPNQNzOk*@յ=D f? J.g6Pm@ky03V@ < @l3;;;;;;;;;;;;;;;;;;1c1c1c1c1c1c1c1c1c1c1c1c1c1cВ z]UHY͖A6Qcoo0DGb#&y%k[vX]镟/n< ?g$hMD@ہD˘BuNr)4`lp!d Oe1bԖX[ư#F޲<>f4D  ГZԧ. -mxnh0bF0 B#@4uua ) ;JAq9+b QK -"9#;L:њRnfs2bW& d`&W0RФ+pA`( $;ue& F 1,qݘhjGm ZGJ`st0(u"R(/TBMj:aA2 B(Ӗ*45Y-(3Љ8DTv{p)Rh \;H u&4 8F6Ln).tFYp3TnVJ(حE@Ù&BPic+1<ܯKb%aг DLR +eU f(#t4o0P1U\`Zۅh"4[.+#U0b + ua꙾,$t-!E2 @8gvڋ'T\bw>mרm & ]UVJE~SLдh9?d.R"TWFU7t)bL'tA C'e&t. \4?5"GI&svY2mOnI.ts"""!qffff*I7_ɓn:~wwwwx߆Kt֚Is wۏ3333RIYkhL47 bSt؄Zt6\brPB %p`)M 4*8o{`@qb0NɵkJ 8 +rVlJ $(P/CMr q%A'^r5]]5X_MQy5`-aۀ=_w2c0^Qnz}H5ʁ)Volp%.?O\Q# Ћ1 b Mc]QCu5Poux"AzPmR'uurŭo|bo5#Yi#9wqCD5mZ W%ո[Ǣ PEvyu--Pm=pڗ6D.Mv\o'g IPU<*__No/FM(UT8hY]a6@V5m +A|6N Y1U԰}P2RIx-phhW^ P}cưy5af7L M(8뇍)l)0}0]2 *tR)-csV‡b"r/BU ߜ_3_qko\y%?(J[aCZ7>(Ӫ?No_Ptc=qa"˱~ΛnT?" ƺ1\ x-ĕ4⾨\[) $<@ ” 7| Q;[0<&=.N +`8)oY:d8, &$"#  Vˋ'z}UvXR&>{=FtͽۇF[%ߥs}˕R6I!}rr'w؂*juHt^80lDԊ_O-lN8~.뤇!x]>&7+QM:Uo <b!H0j45[@ TOF`Uui6N]l i:0 7dl݉g\ cAr[gNLba1%*D-RQ}#rp@p9^nl<4 4;ִ *vj*qCCЦ А'6 [pvtpn2o ?:xC^ۖ/d^6ZU%z-qѾ 꼹YrdR(^A(N* O#K Jf`T(-cnݝ8!1w`@w7-[J":(h B3iv ;9:DM+꾴4 hr56HwG|cxLBHCYKA< TYAsS`).uARX$%GQMS 'zR)׸e46z啁ld[ NCWYrr*|+.%WDM5Xn 1s&3vT%H?GQ?d̯jҤdF=WRn0OFPpL?_x:g);Aj6SJɗz;yrwla€Dʤ7=TxR8C)j '\/(bn+;H/^xxxxxxrݶ'/ C*. `d()7*͵ó(&ts1xD/ C`:vH,`w***&-ޕZӧ#gK3?K}s3333330t:dt!+Lkm- (Z ` ^xxxxxxx&t332 h~_;0vְgT=p2D[e3 hX : r* @+im}7+~1V`٧ 0Mu`3hY\IT\ jĤ >///vw T.(g_ɟ &[B\ jv GAàD?c)dĂ M*']VJVOP7/_2O2(^Ԉ L6 hƢ*Wj4 6սOTͫTGB_"!!$9 jI-&˰@OBî.X/_|>s9xs9C׾hդg>r^T%gL[%/0)S*I{́֜yb;z>s?Vݞ |>s9xE lx7k1Wd[!PZ)Ìd\>s8B {~x |>s9xDlx#LJCR}}BNx3lյaH={g>3y<"E"`0|g>34NS׶lѤg>3y<`-4y<`0|g>3 ;g>3y<`BsXDly<`0|g>3(N{g>3y<`͚a uD5PucMF~OS#VW1pK,tF G%oJ1@Sk@w9d(Uи:n싸A~ԤQaE:.gr]1S AqSHJnzl.qn- ø弶Jqt\/SC*&og_?_ ( buI@ġWS`@>WiUx=H'Gɋ)-y:Ox_ZĔ#xc[bw (KmRZm^u~|<<<XTH9Jvھ"W<ң$dl͍пnA#Eصw9%l31xL/AnKCj OQ3@zvJ "V&| )MDjUV".[Tƛ`%f e/ͧU%`U`ΎxxTK8p$JK$3i xCΠ QY\>Q}C ! H<% d}pJӬ\!ki;]\&t$M.~!DU8&MN#PH'waMd}UC9k.zxժ]KO%됥=lHr@)ځ޵|,H[e <0 Ao !FE2Zt,4P޳;ҷVAg%{Ѐ8zR,IQCr. 5Nn-[/JhEQ0B hQxUy1E}e5w:?RG('@kq6Zl0ֳy( bE P';f](AUآwJY;^^~-QJl7L4%j܍#EW(`#btBgd%*RN+`K8@"ΏL*aYL/m,%p'lkbTh2+]PW7rim}HQJ\1Ԣeq2ͻj,y QbҳA`.P@]L-Cb]$W<Wɺ)#Ԋ":=z*|yz}@ A$A fU:KkRrGh7T٤צ']BA"IHj8 `2'WUSQ3*(PB ">l{ǿ{ǿ{ǿ{ǿ{ǿ{ǿ L@t@Qz(PB (P@@LXP;tc3D$H*T7? endstream endobj 35 0 obj <> stream x1 Om D< endstream endobj 36 0 obj 294 endobj 38 0 obj <> stream xWK0W\WO;#H@-J34doR((<90>${v 8ӵGv.F.rw[rꑟ~ 3mup)Ȫ~!M%ĈS-Z'./J@HҰ2ŠXB]f#|cyGɮ'䔑W+J3:.F2E^ҹZ_IB, yǞ=L|m"gu@@pa㍃gXXKs ɜ7y$TM '/5J +=,`!V䑙iT ry/2!@ޑ[ p*=8nh¸h^~6ZC%:(pInHv?JYzĉAt뀛[SjuE:Jͪ{&g^AnO$y)l3v05ZY^HS8{FTk)B'8ɴ. wqv@S6˫@[E(TFJe/;$4X)U_ rWJ)p\ʨp)b߸UG5DO셹L't׹Ӌ=X]ޱ/χϚ7C[7';_ endstream endobj 39 0 obj 920 endobj 40 0 obj <> stream x -WYBBs癓$%I+UD@ 8£Hm݊$-x 5@Ǻ֬oΪU{UVkc֜s_}{G}▋ .8D"H$DycmO7?Ҟ~'O]'§^s1D"H$Dj"]{W_yˎ:yg?q=}ԩ#W>W~.嗞 H$D"HOx+.;qG~y]z N}'/D"H$4t%<>yK_r1D"H$4te'.vE:wk[VٵvОӧVʉ$D"H$RWҥK'_tѲtkn]|vr ^~~͕'vm;zd_ʼn׶]}7eKx)7|hDD"H$CGS)~ġTO~wso~Z^_DŽN<^r˭6śLJ?|Ӎ Nڵ-.;ϔ"H$D"6R)~ OC.޿@zoOy~ubZ5{~v}YXN8kyjwwo:ң{\jᣫi^^~` H$D"R)~₃NVlŗ_^r4/kk[o|Ko+koWd,NH__aՉיm:|=O~7?eesH$D"HR)~؁Ttp}+/s4u>s7szO=x`=KNU7:\zwigHKo=Oyŭq/ٻzѱ{̊~޶m[\xБOnHz#;wLؾcC/`+[km+]~₣6nmGN?t}ǟw}O|D"H$RR*/8?Uڵs8׭sZO^#~kw_ǤG_?s+_Yvb_|L7+Uy۬y[Ww^{~Go|{v8׽퓟Hw#GxbmWO+׷{E#_j퇞~~%޵r6?`C1;HK,$D"HT;?*n3V4}}ɓdQ'V/wt|EhO_mܶczW5&OXݵ~Χk_7^c#w_ EGN|D"H$RR*ŏ{*ܱ?Nu'NUz/>o{Ӟz>ϿY)=&=T{7Ċ=r޾]˿y͎m;+ʶսHT+Okۤp}ecnyïdWnz{8Kxgz|Ʒ>gⳐH$D"R)~ToώO鿾'W>?yS?_W1/^?wcG^gsVc#N OHRu/:&Wfn83F_ٺJ?o=wM߿ nWp(y+{طr.?c;/U@"H$D}J\=;VI.G%7|8}ǧߌ>_xcm=t8qmu˵<ϺYϼ,+ֶs??Xքyޑ_~kox紇SeϚo˟3SFj|u:ϼ3bԝ'm3cO VD"H$iCi?~8cGK{bI];W+dߵcmV'U8J-}lǞ|z9s<}yƷK&|>wC;W5/Y?x(|o}s_ /}~ 9w{ 5āpH$D"R)gɃikOSnNhF?O^'?qϮ;w^~\4]}&/TuIJeϾO:$؇-Ȟm[w፧l?nɣ#nݺesW]___۾cй[l9=:B"H$DYJ};.K{wo߾Żw/_?vt=+N\[zp} *+/OM?Xߓ~ĉHVV#S}O|u'VO6H$D"H~J;H1wm߾uueʶmKٿVNhڿ'^~/z>[njH$D"H$?R|%H$D"HsN߳{(D"H$D"9R|TH$D"H9TڹjrD"H$4J;VvZ#H$D"HsNh``r-V ]|c}7|T}n @-jߺ~-wGO]Tsؿ`# 5d{u`Ϲo~K;CUέw )ԸjjQ?O_Wx|{'}ϹwzWEɭx '^{(WVWwq߇z7^}O?E ꉔ;?dpovXs n~Tn1s5~^u}|oB}oAOo|̽=I#8x>pݭooxN}[7Ty;S9~Ϻ#?;?>!5p>.H;oyẏԞ+oػn B|[Qo`xxCy܋z# !, 584j-'q ?r;wh8@[X5~ ̝?̋!f  u}{\rsZ(TdMEiVK.{s-l.QoH4j|ŅE5>;}nswg?s#YDӨMj25~xk^sYg=a{{ߛ~|eee~9޲e[2\wu}}k{y>6TR_Ǥ߼%/Qs}|>1M7=ӧ~}~w!w{Gv`GPQQ XG.O)_\_W?S7yZh 5~m";8oR]l87}o*)|;_E}޹s;ď|#{K<7zӛTn<̷i&O}я~|~L`>s9]lN<or~8묳T~NTGnIs]g'??{^R ln$SkbcLÆ#`1sclEK٠,% |TEۢT@471fXpnX.<jRϘ5 ~hT d,xz6%z3ρtNӹ]f,901 脋xfxV>,i;o 3d4cH:ꚢg^?̥^C>Zzw4Cӏ_,ٲ cQ]Ӕгէu DxC~:jk}h= 2>,9 bǃ: -&9-`i ]гէ͠PYf>,fwU}fu1 `IPw8t_CF9rͱT "@A/:Of>Au* ],x,1}w8tiև33fC?A/47z0ZzC<嚃rOePc#̻lz]rn̓,' o|g6G<嚃 z ^ :Z:G_BVR`iǘ-(RMN]' ~:>R@G>dE`cַu3<37fn ]гA/BST"5\s 7|;߹믽7s`BVZW`X1 "g}=97R;o܇>]k K Yw}M?4GE5#-.l}h="{r|>PKf] jꫯ6^\sMٲo蚀>4AeA`30f ELY8sY1ew߸=[}^E,10$LuCOeXrP>devJLEi6=%.3BAaHkhx&lYuN\,( @D71@5jR0 aR z8h$皲hZdziPepe8f5)@o] eM٘6MWt]3 AV-pIAϤKuXQ(.hbOV5AwCCm !=4 I]SZ2Z2ølKlw`Y^:|AX0`5=[}hb*IY-2+=[}^L Z!=T[0f]tCaCm !=̙Mši=L/ &=V/x0fj!C T cVC0fLz&Ʈlz]@7c@L߳516t&:`ҳ̜և3gkz6%tcVCtzg-CPY.Y "7=[}^07P "O~j! ,3}Z z Ee/Ęl-]!C T cVC0f~ AV5 xC0f~Ӄ8|o}h=L_{֡g^Bw!z 1f5D/[ @Wgkz;˜Y-xCгէu s5Y-70[2Z2מu٠]t^BY >AeA0f5c@7lz]@7c@7>=̇և3gkz6%tcVCtzg-CPY.Y "7=[}^07P "O~j! ,3}Z z Ee/Ęl-]!C T cVC0f~ AV5 xC0f~Ӄ8|o}h=L_{֡g^Bw!z 1f5D/[ @Wgkz;˜Y-xCгէu s5Y-70[2Z2מu٠]t^BY >AeA0f5c@7lz]@7c@7>=̇և3gkz6%tcVCtzg-CPY.Y "7=[}^07P "O~j! ,3}Z z Ee/Ęl-]!C T cVC0f~ AV5 xC0f~Ӄ8|o}h=L_{֡g^Bw!z 1f5D/[ @Wgkz;˜Y-xCгէu s5Y-70[2Z2מu٠]t^BY >AeA0f5c@7lz]@7c@7>=̇և3gkz6%tcVCtzg-CPY.Y "7=[}^07P "O~j! ,3}Z z Ee/Ęl-]!C T cVC0f~ AV5 xC0f~Ӄ8|o}h=L_{֡g^Bw!z 1f5D/[ @Wgkz;˜Y-xCгէu s5Y-70[2Z2מu٠]t^BY >AeA0f5c@7lz]@7c@7>=̇և3gkz6%tcVCtz,dYvee}4yeʂvaj8@7>[lgسMTjC3:MѺo8@7>=J6wag j! ,3}I CfzF/{!Ƭek l AV=sFʂY-2qҳgzaizF05Y-2}PeX\ agH~W5zl Q٠,~waf+г5=[}hʪ<*>OLGaj,ID߸P&2CVk~3&f`h{:Ɔ.tFoQf5;HVc>de;CS =^hyA0}txCгէg-0W~* :GǬY#1 Efҳ57lت`0JV )_1 E,~i+0OJilև3gvYl9 B'(S}h-JVK+г5=[}z궆pbIAsÂF* :cVC0f27гէc1z+۸o]A& (rB0FhƬ`h/H s[ghߴI<՗I+H6m'S90P "O~ &*.CLCc{RFTG|M>V*KٸR3MW5z~l lAUuA1hDR'3$./X?LzvR8Z޾tNlszԬG{}DK]ĶrP,#Mn'޽pXˬ-ea^c"Ø"_>>~=ћ6zKsQTGȇ$2gt1hg(1*Z͑_ɴ#γʡ8^wĖs΃o,o|zu,%JYRqh* MC;,ŃѮt{W6+O;PQ,sS|o}h=LzdbE]3z>ÆL36Vk mroa3I&zyh52"Rj,y46#ԇPWҳu"z6CP{M?6Ȇ * ǽLś%k< ggkU҂rtR.7 CГN8Neq(1;ԓj^a#qDgMbC2A]LYb73T!( 700;C#A[2Z2מuzܳ*)$>["n*Hiћm%kEnAqb71u:X:(;Q㋲9Mn$5'橯3.b2e:Aw?:cVCtv{w}٪5H2ѮO:o&Ḋg1XyoeMJ$K<(*n:) A.QL c ^0π=$XG/l޶$_4GfA7D_,NЮxʼn]o{ٳU(ra NаVGJKρV6#T^ -u qڍP߁D2u 摎b,fad<#2<#鈅ljhS_I}o^Y]a~5VáQVr| oi[2VvKQ6s-GPC³ed50N5lt@l>U㲍o%cmoRM9;BAe#4}Ug`k:}٬uM;DLo3]Ja_V$qµnށyFm2lWа$k6`ǻ~sOE;% +Cf[7UT> "^3{Tn) Y+̭gtozrEꍵ&bTuf Q6 kHjfq5=Ufzٕa_FDwDpfɏ8-0+w&,2OLIs8bFgN,`t xCf" okΓ~l?ZuqqcY:UXNnŰ0^d}~i2an;4JK;KŒ^-Z,TS\9<Ɲ*q(%Q'&/tzǘQ:]-6cmRF Q$Tq }I|þxQӮT5?39>ޞ*]֝+=|\de=[tg+eӵh4>˅v Ryhd.ʃ"umO'\g dt0Sg{븡%uPYXasg#¸Pf a?[\wt/TcVCt&z6].ٛU{E8̗[feo̳h3{/5 cγwRSimorQ=TF 0t\=rM2f27R>8o xCtw7DG{j)IcFNˢ{TO]x&}k7YpL!mg 8ؑCy>vrRvg@\'h6Q`EFqQ|_!93Vͽ\Nd5n'%c@?xh֖q\P؋LFh%]?NWpȶ -5~ ŞM]OLA%9TµLjYgemƜ]/lВ$sN|2Qb]bFu=/31q6aᨓ| b{JpRUH_<~`1t xCtq ) u͆e^R ]1$ 08p!N|)!1)نJ0Ahng ‘*:'Qy+ZN볭fJuIt5Y1{f'~ث\#pJ/XTOU>de=[,W϶'Aww$7Gߵ !ZPbjW+J;R"`EB9Hś}.YٍtD 9-qSnZɍ_zg'Ύ9A#8aU] ]w,:2`zͷyܥ~fw#,KVS)-~Ub/E2vX*t Jݙ| JփFVɾQ{\z꘽,3;6}ۘ{n*/ner:ۯ3S^֓UDѬčIw-CQ)1͘d*!'`W.h`sbY, bEk\^{LxC,EV3ZQz iTrڊSݨYV}x#gLt7dt~!3b3Ñ<]LW|(1iR~&OM2k8Ci8kf|-۔fp/cy֛UfX`a5~(979$c`63_Cwև3 7ܰo߾w~Lz͙lx ֚GnBڐ y6H2w* os;{ӻĠ=[}n@s< "B8GZ5ȜL'I`)s9L~eK1T37z UN/[ 38c8G8}gSNEm[jnp`<"murj+t, ()1ws"'[smYVuIv"m6:6KUK(J!w/23}8X)|M e xCyo/{vܙ~꫍x5\374hWz\}p.̦bN( |6& dypYɾ sa.5?[9M]z4qI]y_4 $0;D/*0&Yguѯ77ggr۳mo|Y+y5NK>k'2A'J?p % 7 s#ví%yp#n2oFܭE2q&QlM [~HAyQTdQhvK%9z"$[0[2Z@H{xSDQ˞uֳbɱZnReR +E̎2d{$4\q9-ٞAo,_7VA*|w{$بaYp_+_1qO leuz(lԦHqͨLĽYރ(sd BU+OlZ{uL;Y џ1 7=&Yo DqzIRi qOx`֑=䳒b!daҧR jKg{Sj(+AH5?+5Jd]*C[ξQ\TMuT;Q1gg<Ԛ˃a^'c@7go~eVq%J7߃E,Ef6Wf 8rUJvƖRVR8R4)(Q:t-T.Nʥ`BAȐ^lOz 㨏IE>! Të-we ,U>de=[tgS?Z,?ܿtvd8Hg o]VlGs hwkDB3/{]5u̓q%jdESj{,G 8OClK4YЋt l-]!ݳUXg\qW,ՔK9}Q֣B&ᨘ$H$❌#`Q~Wg(_//I9T%)Eŭ;+,I@Jdn*ʗѪ&={RmYd:ԓl͡C0f5D,xCtg JfH)}_3R,eU+SRU9sk-c!DO5ˍ7fڨ&ϮB42NRt>(l7̲|ܬjqنfvRQwů=c]sq"T;|G5k'}+s@-Yhƭ2:v`a H&"nDTtВ 2+ Ƃ (ЯŤ sX̬i7uuUӊ K US6no}h=L_{֡g3 iLoJo5 * >_*uz{Pxy|H By| cؾG QGQ=xLq( 7at(-#EaPr)j\a4Ö]h/{!Ƭek l l=[jVvTʚ=}idT8ut7G@{:SE t|˄Ҭ2? `8,Yw_B;!Pl߮U-}l֍.4=ƾ(֚ͅZj"jJUfdY/)2L`^0f5IJY ~ l=o.dJ7X]* Ҡ^(LƙJWiWłL YXДɃYVY7BgחK:`/n 1' Ps[-]*$]DܨI7Qt X1 `o|z:KoK`ccI}0!KY9dZz6𪜏_ p|6 oBЙZ:UG +&biE>V2`{"ME,.+JlЋ*)v?'żh1[]q@T`V.#VwPY̜Hnj EloRF[x^BY mC,h'Ag%6Bَp(2i7~j!wV1]%3"+*Qm)ay%K#O8Ds#'J \ڃ1!ZgnW܊#Kp*4ĸ``Ի\^.(ņ?d`iaY6ߔ??<*rJAc(òZVdO+W)VxD#ոb3|0Kx`2jƊw^tL+aF1 E ׂ㯻K܅[:\_}zH8a9rd ÐPBg6d#( h% (\Vdyz@Kg{ƾYpwyVS[%(o .Ť-=SJjKP-0ZMYTR?lwnT+)=g%,~̜CLc4مcVCtzkV8 l:Jm+o/mUFg$"'L2u;[Ti;q KxGdZJtO7,;Xb=*^kDylrgAqzfP=n!B.ڎNIpCű)-J{ooW2C0f5D_,NxCgT[.̯*$<ڈRe!9j< {fi.E3:ZQޮ2ܢ_e,VBfny3ƃA^gt)N}PODB ij! ,3}Zgz6NPzN2?}/V#"'*z 6qܪǒRě3"mjx-JPd~Fim?{e \&IhŒް#wB7.ܙȓ*#xSR bǸ_F/{!Ƭek l =[8l(^[KLPoEǛlרG5s#{BDn)?^X g3Xud0˸6㹌';Jrڢ֫!sϓ$˾:m6͏ΚGU8;n.Uqȥ~YSl0b,A9V+,"nEylJ[JHfR@̏Rj0 co ."`ZZXYi'*ĭ](2jahQfRq.m%JӾ*mgB|Ɣ]-*^Y37&T7'+x>dٮ},@7D,owjc[&s[GAŚ;bCGJϖXF`ŔqԪ<*#,TgU 3uWuخeYYX?/Unw6֨^Ð*Λ șY NL{<q,ޠ\V* ,o}h=L_{zϦI6Uqa 9!H?mC^/-ŭVeGh*]TقD![i4V9DpSW~$L,>ˍ.i*MnÈL! .6Σ؅ u^nSoT\j짤WlP]0f5D/[ @WgklVϰ~#l F3ɚ7+6 cQ۩\Ј:ۈ) nsbRՅײ/&6~&+#IeeU|oB4mrryS{-UFbĔۻu̲s)X1!z0ft=[ J$mA!O@roCkWG3sqze }qGVe`g'kh5bP "wzK('GTނ(Z| j_g)"Uљ{y׭GfJVtbb7C5: ~KqCPl+űロ]5X4tq90[sZjD\Np,T (L ׷+ Q*JC9uF^Q/Rde=[tg.V5 }hțE,)XA r <<4Q\O>|'n>gCȬ۩RiWՖ7Y9'p*`͉eڜ+qà6CNT;hT& &4cW‘ȁtg0l-]!سd03VH_bmr )++ i+2[y]'ވ,v2cgMT52۾Q}{ac|L֥r.H+_bc醥@$I8~RPzZscVCtq 7DG{ DG)?'j@?-J tA#4:oa ]_/4+QtTq$"$˃߸*1G>R\Eܽ/齨(ױa#v"R9q F-GIȰo6~uTU&0,=~=VT:Bw{ +г5D{6c+˸kw\1[CyPjW6 tɶUbΫ)uSs).O5A_xCFXfڳNWzB: ʻ!#Oo2vvZ4.4c픫IyW{#nSF5hq59a }K.J\ /}Of6N,MEg?NEK]ߪH\JZ=QVƷ*].@2_6u8~R⯚DTtY =oʴ]żK:,Z *vAUSeG*|s4A7_Pdyk}sj**v24-&҉.2^:rAhnll̟c\<_k*$cDFAw,[[h*N)*fkf>[c@_o!ҳ) Y5`Ә|p\Ud tP}6WSv{mf(F*\Wr:3|SO{l:ѱ*x`v}_ӹyaڇuswz=PY S25.HΪdKۃ^*j9T/ʘKNKxCM+,՞Id)a^n+ 9c"]y4s|ҹ%1D 31)+fFz-Kq+48sfӰ7QEh6m)v,DQO _yW5zlŞMLaDXRqYd,I4%scvMӎ͹( ` O2&VaN{bob̛KmC]VW㱍*ȌMHUE<.U̲^*=adґNydb/4Ƭek l щ-IBh ք!5|ѕ×A]X\A]ѡ()\)CaavepE? .Ű+jw2e $=_ )/7qlOYr6# AP=s*ZL${a3 h/ՊɅ_嵢B0f5D',xC,~&2ڕ} >0(ATegR2'-a7()˱]V: C86_jWR/;[hĞ+9ήfMs[ˍq.}G 2¤AW+\"_"\:cL⓫6K_m_{OZoz ~9P'j'vBs%{,T' .4'J]dErVVE2'UqQn$իJJSyYZ(SZ)2sg;g%iu` %GIpЊZ^D`Pr-r;(}FES'bf-e9q3q2 0 \{82PHřo6Y6>`4@7Y~ qi.fGaP!/R!MA V*&^JOq){<)pҞlrNjɞA7^xg VerL[o]T&S#ߗr_1CPv!O=8N2 щk*,!Ok$ [2Z2מugeNSPD yf$8H /&LGT+ea#A|t!1!SRhFӉ$1j(6W_!Uxpa TKz Rs*KTzy32/.K̗K&% Y bz2ag`6;fjPxTl{Z&֊xzJrE`\R:hTjr KsU%ro1̀ 'eA݇2VDw~#SdV1Umڡ-57tghij Wb,X̞-կJ7/;l}cЙi:JU]_ 9 T=oR7GI^锛}]抿8vH]Qwj[yߧ _f'(_4n^$|ض³~|pVSc T(Y6@7bYK~ӳ~jeU0oZ(8}ysajW㥛[\VVI^-պIN,|/1zgm*ertܘrW\:iHp3S0רܣl'N(6➕C+Qb1MgU+BK;E=<`w[tԹZg>ڞ“:(D = 7$Uzf1`XD 7R`담GGy`(lPAfa۸2^֥>8&j1X)dT2ԡWnxe4s"R*492*-FǗC1[(zl%-ߺg 'n3AhJU&J;c'H ₑ羿9 ԥj! ,3}ZgF˾L#Eə:#S;=8nb](Ti\Nǘ4UPsz؉aE9+4G:FBR,z9O7.Ρ} S)nC4'9I8Fy a1풁|a{<$\#kc|MRIp`^ԥju1zlSֳzs]\I9FFHC@fLD-Q1=[E/l9vAn9@PDv6<蔪]Vd(S].\-s:C~2Nzd4~]eśfYhUf8$瘐VFI&N6qmrlW|$]]="ʤ[ ;y-Qd7Ok#Y6 =ƋҢw=4 mqjJ"ۃ 9p:_(BW,Uyq{[j1i$.7tܷ"P;2˜qo!=[P,Ћ(P#ʈKO.f gdc ݰЈR+}+Tf$7 feP;.E8]qhdsiu͹& k[JX!5HTMyUiiɀ6$Y^Ak^xS[ؤ˖tfc!N?/#@~So\؀Vi8$uZ\)-KW﬿:c8Apmtm<㌲3@qhٶBKehq'k#JG+sz9ޒʖ~u_6A +E8G^pGHl7 *2Ssq;N_xb ,3}Zٔi-)jB0>wm?+)hΑ~lRn%={l͖e7v c(l$19l8 f>x&v{ {//#!4brT[ If/ R3ZYHy@phQ}WnelO$s9%nE%Mo~qo!(` BxdgXYK"6[; 9FU+L`3z8 %HņAAyG5ZW0F3"tճ](9õ5 F!+iBgo&45Z4΢ЇPsę͹4wh-6]AP?dsIr6_mh~ -Ж5qo|zd8k̪{UVQej(9X*DL B~)\ !ߙŭĕTe\y=Ŗ=z:};g֩(H1< C:Q{ǎzr.wpOI*& j=I QX&dxa<ŕxA_y|t+UʇĽQZN cV&l-lH8UV\߻Bj֮4H$eM=e!ä!B>o;x}yX=35%Ұ(ԩXN<^:3&34ߏFBDW3[5̢l^P\i< kB?P̾LZ'NfyǪ?RE>(w"4/{՛ 6' 5ōRsһ7nY/5cmUuMd<, njV5^\ /O֞8MHڃށˆHigc˿)H?iu]`ifPX.J}jХ nUd^{Qt48d;VyY.kUi(led.D9J/k_}/荈955͔] ջnd{HK髳fc~>ZΥZ Oje!F[n,֭DFj6,ҙF2'gi͆(y r_wks,6%foCn4vI׳,zmX RVϭzkh#=J5@ANQ9z+NGl;rwI徵\,XSl]ws_ȉ~J] E4SLuV#ևaeb9oKjtuN! RRmtj|TDz͏r*ᆸd@fww2=uC/80Esv`_YKܦRe^G.;# {LcӮv8;z$x]>S\ -W@IJC˄Jէ!'-t'AA~ P[J":u0S{7!T=ы(;]nl|u6ٓ$fEZ.ް7>r#oBTFܠWS{q>rφ`A|$h-eY C0VxRbW&8g+Ng뗌j)"WBdMIMwEggkÞƧ`;GI5l/Fɴm+rcuDOC1#kvٺ)=wa`JEmw$^ѫ 38> Rp3t+?uYm,J)0z_0Vw 9YM2:x?iۧ|6W2Qek.g{Mێ\ vuNl m6A,\֭҉h2$LLfl.Y,^y0E"c:m#\:Xqݧ-lhb]9EƼIN׌n\N/Ic؞"vͲv,GeI)(9X$c1eϖUfh.( -/|@0fej7 gGRsOHqlIZ^Qo/ʜE1Kȩ.q :w)/[t}څ O9,6r0|{gqZj\/,녽r 5A@OlX{$OR N&o:XoBa诡nthD'-xP㙀 Oɸs0"/.J)Ɛ O䁜0jXO1,We+N$Au]F+Ğ'I:1Ii9^G։E[Yֲq%S#H#4jTN[Pi~m5fKY7/4Qƴo%݈U)bxx2lő=F.Шi-V?Kր_v̱@7!ª~y鲠eqFqSVGodӋN- [%Jztrs{X,m nG'cѪ=z6grn4tqնtZjak-W=RyܜT6Fk_'w9gMkݰRUrO9g[˜*[ wi=l¡![X+KMM2SQ2,tƝS@q0m4Zq|vG;z9`]GI̧DvM2@1ڏw"B笺-O( F#hD:}mSn8?_f+N֮.Yvm{:5Hicǀ g"Qn:MGB&iyi?oiXpUtBN7OH ݂俎-}orX`4bxx2l 3,ӵc!>6B"p;%6lױ~RzkdAu$ƚh4 ;VܠwU?Td޽ Րo[+7u\<7"ZPBwF',ݓ#2] ;[6v,RM3׽hbF2Yԧ\uI)cf|*ƬLTZ leom٬L;te*{1 z߷= DJRVz>ߧ;GD/՚YҥzyRQ}BwYnbQ]Zd:\'boeq=GX𵢧:RW:N{ː3̕L>~̹hҁ ݓHg4oc CUmrǸ/6}7Dq*5@Aτ8=/mC-,-: 9Z\Q$e;Q#93{nQD.x*Tڀ#PCғѽh!YŰ.qng7i[ILq&ZfPOKhE؜!ӜetvqoYLa>iQzҩ:sZxq1\ZIݡG~gu/]v٥``U8eeBo^/N믐s-WQzX҂rQЈhHVZ)>BXV²0nfwm7޻2N::bhf3:}w!r_z;{q xg'\tm Ƣ*ƬLTZ ul #6-y gCtHG?#e'u8KP%*t)/9O/8mӿ:snN#VURwuԖBb5YnljL`JAvP8E;uLDODflۊD %(jz\Twp: [Z .8o}r'4@<\OEv&hG^m([*c獱$8U+ݦuY)){:o" zlfPjsR."03I /9Uf}"͉nv;ϗ=.l鍈':;ջڍ9/G妕rcIT/j<P־.W.mU/Sk i̟O48jmvtQxhirH `D;U3:PIJaM$Şr4*ostR~M#Ө'^Ui[neyfAK 9GU?TNotS_w&HQZ/.'SkV7NGjKo {+{d%]M(?a7+%~l""lMU)Ί&+nN//=~t4;6J~:tZ3J~"} b≸RudZ~/[(l;a{WY&XmY iFU+Ypn׳ݥq5vT^f^J9mev!K6vTI^@.EЧ{ 7᮰ԉ@/ڝL=~%yp)"jk=o9Ў [^>;M;5* ¦Q,xJ<3v~Cb̺f~"Lߡ\36*!as8UX#V+k>[B?gY!mP]hy)W^:6;̻y#-pxhn{iw0kt fL\CxDĸ_ q2ɵ6$wqR>+pmr 2C[gi'?$ȗ;" ce"|Б/g*խDž+Sek.gҳd $Bt"W[~CI%Ɗ߂Ҵ!/s[`$TCm۶k;?S8+|b^~n!L}Ǵ;g҂օtݲ0!Oqh6Fzfję{eTۛ~.LgE W 8?A q rA߸;[#a _yň?; H>^Obver׈N猖>#pn }P_cVX.qg|SE/5b_U6V$[:7j23})͛WFLJ޼ ִ&T%{G nG"OpѰijе-bj<P䂾jog)~ح b އVQ(~HOb~p"IĦs/AV/$y^lֲXZŢcPp]fo#Ì7r>Q]9P&Ʌ8'sgE=WİqZ vq!7&oN)~"e2[$ާa6>Cbl &USPcKb2h 颞Y%3AFDbñΖ "7i"[f*)tV5D(Fw!U |Vk_*`=MB?q6,6%hE>-FS8F k5ek"3D4^quبfq慰-ٶ&d>BLP?eW9],.՜x<Ѽƽez>HPξXTh2Cp%~rfVO;k\ 4Ȧ Ykh3 >¹"-/R꫇ ]@ϖ 1@Gh$ s:CyZcf2(;}pG>2PeY9Jmh͹хYb5S4e _O5/! Tv#-{!H{ Ep#sb[ۆgzn>1+Pcj<:ݿ諒?) gƗ؝_mg]@^}ό]m (<ݑ'=$U ^gxKojܾ]_D@tzON%bTR=Oғ|xĜsאƲ_#w3A4տDx0+pHsJ"d3\B{7zݑGo-8o82%N^l97G? #s>l!Hnb^צm$ dW~[_}հn[c+L[ɿDC|tLGvK %7c4ڋv!nm* l̈7ZUKu'/JN5Hj.^L֞LV @ ]%-ʇ`$Xİ\%IlZ-g(iBK&W[Us}+66.[J"ء6&}1|͊0>(턱{Xš´{9=`Ĩ|)0}b).l.ߗ@J}U/N-*\ܬKw| Ai'tK;ya~]v.tKAnГJI:ɝ3<,j~l/io"Uti'~br6fwPfrKdXL.C575֤vw,o!n˩D|̩ :d0fej{a6j˖QPm'Otӭm ؒEr>2jC8*wW^3')g@j"W.E^9R̛J7v[1T+o|@T9vznE_*R<="s#jAeFOtS7թ2D(Z'C'g.oǚ$$0>mٙH>֬Aj.^Y=[=n-+kNDJ&:&wP*=$RtޔLx=F4*7j/ԸG QmtVY@'2WۅBO.e ~5j7DMxA|*_K1{ONքh681Ӣfu fxWP_aD-!{XrYz'|M4L.J&&޸L`BcV{gKGҚ$f:2xqW{A;bMvb2+KD :=fYmN+)J^c,dxg$gF=qsAޜ}Ũ\_c^nEFY_ 2dQ ƬL@P=[7:߆Ը!dxT葝wN$5CBi~2r(#N$lf__TTX5"|9Y5ah6H6c֛5KP!v^hKh̾[D{OHa>&Hf U}Qu|Ԟ!M"Tx&(|ƹOU`@@%Z2I O;nqͼ#cYBן2ͪ7&-IE1hÁQᴤȎxjUpdvlvbl<;b^r%A!iFfIx~IRv}?v]h``K/`5(ç2Vݔ'̙J{ai7p<Ѫ^''ieM_ҐC9B="Kni-o+(C5xN; +N!}BW2Qek.gDhj**ȳ虊xY^Kk$Ҥs%u.b_1},jq)3ԕ^d&;4";BNoUB-XqV#x1y.LJex㗵['W 7Ďd@aʃF=gƬl@/7|W}|9{WqM7"ۚݧ3!2 up/6ə'š1Z@\6b&zuw-cIY%j\.RM(Z uEsDf{렏^=2Y|D 4YSqC@gjD_S q|VB &Va%E"e/yXhNBM'{_7gOW&p.&-G_LBkLWg7Z} Vg-%fgAhح&D^.,>dhޫ5j hĤJ8vzǰ{%Dj.^~~Sj>ѳE;^Y\HciƆ| Nay0փ. C*j\Mzp/ C: ͣl3vv3j/F/j*7BL^V:+򷗻hߩ wDk$xE#emaܸE1]bxK}HXz~<4R'} ;zJ,p7C4 %~o,k$ŖoX~4>ط}ҡ| L@b;R$ظ q??ۖkQ. Jmm> J! ޣF;i7<(|+:*7"#j#9uH}}!s(*9cY(͵1Iڳ fi􃇼>v/j o^-vg@gj{xs?9շ~|N)|Is=^5kz] wǪ%xL<͹^0vjgTu}dĒNyP>0sqCHDfZTHi츼1"ߋl B3y^5RBe cf/TCI߸.fbx{߃j_)=[]\)"8x.i:' ՝iNd*+f3ъ0%lbɸL兂~ɟ+S q.l{o{fzc*d/u(o\N:8oo0'Ҧͅkj<Pn_hNzy$A2Jzdoq_Hq-WRJk?Ӷ,R$qd=s7Qŷ5'ۮd^"A}^6Ї#BC3nɚ+:A;&-,;L$+}.ƿEc<OʴWkYw1KP㙀 g7uvOuiېz6zk!@Jt-1T7.˞~LhAΘPX魌&Mb׋#LYW %PRHDҿ[.kq;7nibͣ 0, δ}V@ an.}*?Ch^Fjș=o1 q x&cAk-@t=QҾɤ+ZU~65A3DJ<;?X:t$Jn/Te8f}/:e Ry MvӌePE&XyAK5t4Zݑ+0?c5a4nLs x&(|YG#!]|٭?E]vzf6,'S#^%/wxOMܞu'eW,Y&h;<~*zZ󴂝ln`>PKg{-xt cDVW[(bxx2lűxRE -\d e-aSC$tg"6b}Ҋ.`ik .1ߧCf﹯21~"s!t>ăl]ӓ^]nX+C˥ė]<kf*ƬLTZ 2aTWPTS|٭V2˚,48U5˯ߛ್.}(#cNs ZOHYFe y:+ Q;ې,\m؊̄ v?3i>5y11<)ǵ&j-ުbyn'2TEӾW <Y gkofJRHcOAZ0|]SvY&j~/msR^gm~7_6s_1j*ƬLTZ 2lo*m*A&JЈwr _z,_ЇW D7 j1Y^γeMJZ{ x9d\L''t<uXf۱.{uy|vDf.TˤdNþz: Fz×l4ƬL@P3aTQĄ&s:@*#!>'c*2p]#i7߈t.ϑ `ӭh5g:sW!i8YkMP k{fW&e+Bl&AJ_o_ql[68o8+5hg{ NAFg]mFM2ʩGxK>O`ME*Tyq+i-+e/pw=ѬYU"Jli&r0jt[ZJ5/*ib%i+1Cj-i*;ojUpdjيcd84#HָomBy>@6F6بm<3vJN:^v]ܺ0rDA}5]F XWmTn3c>XŽ^LS?61t?x` cV&l-l}ZW>EUh$@.fҚDSN1 nH=p]2nu.2KI a>*cƎVrp:z_ ~3z=YJō1KBow\wNV6ƬL@P3a-f)qQmSݽѕi5!S啞gY3R/>eޛo L@PƏ7:DQ|\}Vk)9" ft$MXR^v;ΤKy 2:+jL+X_z;0nҰyhGKrY:YЊwT}M?H:%(ՓgHRDحw7B=q/7< F;ǠzP/.'SkVoe9xXqk+A$`Ƶ7Z }jb+/K͓Ր~ z%{ְM+p5eӐm><-dnFZRoXlWiz$,Kh,ƋNfo^Z˜*[ w=[&X&dGXթpL/4Z`vB5įcrZᡜ<J/$HsDǓک?جD7RqdJ5L~'5~g6H+: o{,b=nY%Fҷ?եaok6-/cC Jgj<P_+PO{k!ŧq[vK<4jI="99s7St8TL# J!jp.(jGKFgV΋mm~&^u#y{(|GW9NC? 2ӹBOQ|jȢþ1jSngkZxq1\^[N>;$sL/wY)I^@z̫+dMC5%|Ӱ7û$:$ a0fej7 ٳbL5>b,YBm4Otl/7J|,P;by5&*ֱ||_*"I_YWL( =^oCZihףP1SFZ5(+rSw72@gj7~u3JiDo!#NŁH YM#uJϝ̊r}NX=;mچ@N1%Sį`+`eե(-g}8z{uu J$'D{[RfG~ʫZbNe!Lpbxx2l19Un|7y$A 4u /rwΊY*eL!B5I*\xlo0gB^V4/r.2Tջx~+H{DǍgis6U+Ypгeg.nUFN.*뵃BB*45klx'K%QS+ ޖ=eB/`Zֱ~@XFr9ן9Ռrph &waūϼJzonzm@^N\NܩԙP;g1+PL=2{ Nf "ZeVAl]Ûŝ B.=Kf~qo}^k=mo:1^QoOwېU:~;s6D~~;ށhKE]Z4-nט(4sކv*S8o8IdB 3 Ҡ"YM{ɓTSmRCW)`VP/-grm&:Et_ҿ~JFd>\>1y7fm]+sA0gZxq1\ ,)Cl#MMFYG7^<>p.5tZG,4n~XYT!6L_I3W0fe]@ϖ g#zˁh'[uF09 JC,\u84[?\rbڞj46zSYKˁ_̏8BtW<Ԙd~*;ۖ='_,=Y gbE4hm" =G~T]'ѡj]$"Z<.ԋXbKJnb&9%iWk9-xon]Dj.^L=[ql)Lʣ3#bҹ}~hث˸5NNVa6fY6menm*}}H*qyMuSZMsrMػ/䲲xQF1KK+J}1+UzLZP'f+b.V !UlB5!D|hhK?Sv+t|@$$L$Z 7TWH%cky}>>i_og ed ƬLx0g"سǒo A˥$OcMr,=#0N8B9po|t5) S\+Qgq%&Tni}./bn1J]ÂMUk8HtW >c#}GT@gj7~mό=ߧPƴhDVؙEN|S 6]/0=SgcgMx%wo#WR l9ZhUcČ-g&kk_8QژO|Vݘ׊XYц 9V5^\ /O֞8v϶Wmg|ʀPd6toLJ Zh5UeWk6rÚv(B:F3N³}+3PX.itI@LK 3ηt|}5\zk_^]39{ cV&l-llܘ+NS dֈl*hc@(N"Lznˮ1LC '^IOHNk&%;~I,vn^C.08P#byrmWLݡsV`8o<zX.ks -?ԉ#~qnESxD7XJW57% }K7IC>yu"O@gj7~ S7zEMOaLUJ0M7iAyKcRYhH\KMDʼng!GfkQ-҆koUWfvW/촃4ީ{ܦIx߶<'S/.'SkVXϖ#JH^]<35 +ivO#"zK۪_D<Eg NŬ|ݟzuksyN k+΀P˜*[ w=[&V|B+zW&f ŪV!9+<0~fVlV }B핁w /L_6ubWHz0%:͢*pP~I~L \O7=NH<#J16un1+PL򍟢E\&xcOm31>~M7SOC7vc֍~"bu7SQa7veX;Nzi,K#l`I c[0wODyeL@PƏ7M@G9^rDQkŐ=ٝїx{UqEyYQGQ7ɹVȰp_qv>n k 諶M]yRC#|>}OUg ܮ۟1+!u/6j#bxx2l۳?GUJn 9OX%BT}qFȎE_~UJ]Va_~'WUڕ1TgOs`Z,_Jܹ$HMEMB|n>YN;9B^"M໪Y+W2Qek.gJvP$`9ֻ50L FITy66*%{I# +uБݰpxFn}vوX%k-L7c~m!sD[>k]}bg }lKƬL@P3Vi`1;m >]Z}tWBO ]f>k-94.LAK%IE½)ʋMȻ BuWd|l+KfY(+llE~B4xmF| t%C5 q q6?.8LevDGXh:pWZN@,@OH/" WQ**\'5aA+m_4Kݘ y*/_3PNӑ 2Cj.^L=[q2uݔZŵ]m;"s5uV97RpPjx}2M'_-T,>/*g :ZeO6i߂ƕlkޣ%*ƬLTZ 2ҳ%[t7}:v9lR")G\sbm2fj>6W^Q NCQErl"IxZA^٤7~hqEeҳBt+BךY gbg$<7:w|>#"@RkEbt`a@HSӆ";R ]}+he!H in[*~Vq8ɁEj.^L=[qVzt !cBLGb pu{:~76N $ByP#gSV&\w!.j}a{M͓8b!qײJK&&j4CQB{fN"i/R_aD-ed$*n]кv.\Z0I y}?HlOv#A[zL-}OkБXHԸ9 90e[僤n3_ ]FBK=Ŧ:`8o<I{擳8, 7d_k VLwK_{ B5CҢu;E)IL,^/.; ՍhK2xlMo ݲ]Po؛yZf31L@PƏ7"qȑI|z$8po1/{eRBN"ltoV֘LP'c<_!qb]5iWh,i-voi 잚Õ5gX|VZYo2ԪƋڳggKR5v0Xj?=Nme@vCu(L~R*?!iA:ЫMmB8pO|݋JS 8垴;(mw2oV wk$W2Qek.gDRl܌<ܤ2Y^Z7 L{)uJѢT+?3Y m&$g@.h: X|#v\E?5~iQy:'EojddoweoE~U!jr4yZXQ /){Hb/jUpdjيԳBIm~{k"*j/=,,"E"N96.:x]@NG'[3Q_f5W ̅D;ωČE? [J}1+UzLlJ߳&T"(@ZETՄ<6xx$NNOC–!tWݸJf]!@6 ?qŬNC6!>+ptyΧ:l.M7t4Slp Y g"g#d'ۉnZoWO<"T!$B3D&BM;ݲzM~eh 4'>^eliBu =]ghO[4Ii ZOj3%]/Yj:8x&(|wf-#w0*} PA惭[!:Q 9j;q_k0bɧ+цmP43;*^ cܠ6Oc'gzG^$*˲Gރ،R/.'SkVm=ێ\|1F:BhMK!cV rOeLөO҇]hdR..9čO޼~^ID ӉH?'i(7C/`׸^Xq"|PMlS5J}1+UzLlټQHx=O%ʜhvz)4&@I|ڴ<r9+9l ;:DV$3,?W\j8̌:\|`U{S r|C Ǜ~ʍn$0fej7lWɆO;y++Y{1eC7hd'bEڍ^8+W8Wb]Db֝Yd9p]L2rbO~!~eE&_jJhБz~|@gfcuqƯP0/SNª8 >b>Jv:\[AO%۴7}-NIl*v_ϰi#O ;3dJ">US< xCFdjيs)C(h$S/v Ѧ'[|-¢C"`684_+UoŞl[?9  Y&IX`L6NC^IjkPLyhR7_/ƬLTZ 2QmfGZI*Ciuѧ8[{=L$o .T q F:19Q#.fՓ*+aft\8LT;fp?N qÄh0N^:eiĸ\YpBy2D2LVfstǛy_NFdxԚzwO5:>iB߹P~Zx!xx2lŹkvc` þOOqarL׵===EرִJ<ſ ~obyf2#HA}- Յx@MRmYCFdj>@n߳Utv:ĢB)zAcxZ~:l?_=2#5>#AORoIwE y_mT"M]?{! Ypгr5JIlWFrc:*ܥw|(;EU2U:}ě`rGs1/5#0l, 9䛲c`D c唞-Gx6ҳw2d_ ٦txwMtivZך:_fX}[E%QgQ; {+LTBm)sIg->lΓfI=,zGwܟY3=EfP՘8Ek-%PWR {m~({r>,@Y˜u.2l-lxV϶}}?Cz%W9f1gqͦcM۵F?fpEA$,ti$D?E$0-{lB}XZZMcq:[?jGГǬTZ 2QmvPQ[OwY3yw%:5-K4cR'ֹ)u ;yVj̜5鲊G.L03ˁ1+ՎY܁?֓ܳu{#aGCr\w"` 3̛yui 5#=Ȗ+384{Gl:4:z;"޷#+?5,.|yo|J'MZ;ރcZٌ]@H<Ɏk9φWS# Y۵=0Du *VS/>d/O֞MОMA=۪Ĵqs`fMh.z;!̪1{xVBG"zp9zT =dADiZ8UKyo.) Fk-sxܚ u-*Q9sUx<~X~!1^܉*{[YW]@ϖ{֩Yɕ$'hU2m*A?6+3DmߴO&0^K+N;~4fe(ƬL?fpanK?-d*NفM5d[TrrUXXVkЫ ?9Eŀ#,]|g]h٩d5i M*Sx?R!k(xl?bZ./WԪƋ KvBRݳQU\r|E~ku[2AbY+]OoлP.F]ƬQek.\grVynfFR==e},Jf fZGs09ݾ37j]1~ћumޅ-"Jz_:$Ҍ߁[ WcvО-gh@*߾g-¿q]mxۥkRՅv|+~RRDO!x`:cnT x&:fp .w'7/"tކq^]itDag+S p8T׫<efZRYJ|2qQ-C7K1g4 JjQ< lW+>*٨R{ㅩ>}OTjtАg2 7o5.D\FJFΛ%į93ysq׎iMXS?9(_*{ YuPek.\gH1NM(͟iF/Y r/-)60ۿs6]M{4\;ƶER㯄JPo<5lBCARonIrLx[KU)^Nk%',8™.246CQ'5,.OYx_-s[}\݌h'үet}:߸M<]% L'[馴 ܁Zx!xx2lyVϦ-G1 ?%`ߩ>t z^ߡ݅~72Qe/1+U{B@ ƷwgL׌}MbSYg+}IѦ/yy4fp=D=s.[?YS>ŮG8s5 oKlssfiVi͞ߣDcctƯ ]'vr^3 7D7cy|bA'[s_?W2'SkV{63 y}z]K)<䁦IBBGC"*{!YplK^Hj6XϷ$F8VCYx|6̜ M})}$ b@g1 x&*h“ùd/פQw}??Ujيs*q̼'Y삺DBfA3T]"a-αe 7#˜*[ y7o?o{wO?_)Wlv-2mLۭx&;P>Z<ݯMzXЫzFL` /_>f϶c])}O- t_s[7K ɏY a`Zx!x2?:S_kVlI ­ _%"{GVH4?_{ BS3˜*[ qAӟ׿_~ϧ\l=:^^Jί:ч/wxNX͍5<g*8λwvѶ-=:QJ=Èr% o /2[0[B֜-@gc9޳7Nv";d-eո_}, (AjQ}tV*.@Y0feco<ҹг6]ߋ;dj<(|ǁoҭb ԪƋ g+za:RLMEd,'FT aD-ҹP ʅ,)/_aG1+(|@ϖ=umArP/>d/O֞8ٞE(5bWʝzUB2Qek.gztjn4p[}}wPwcV&0fP3-?:K9u<{@gco8bWyTP/>d/O֞8ـ.03UB2Qek.gzt2AƬL` g=[:wg85 Y7C~`jUŇڳ=t7^cV&l-l@ϖZe YLgK131 7~3ԪƋ g+z6P%.nDƬLTZ 2-ʂ1+(|@ϖݟ5c@gco8gU2'SkVlJ]܈*{!Ypгe=[:h7 cV&0fP3-?kǀ, qP/>d/O֞8@UB2Qek.gzto( ƬL` g=[:w5 Y7V5^|(^L=[qг*Awq#0fe]@ϖ l<,PY@Aztj<(|ǁo>CjQx&0fPƏ8|Zx!xx2lAō˜*[ w=[&гx#@Y0feco<ҹ| L` q CFdjي T Qe/1+UzLgK-Fa`, x&гsg P@A?|jUŇڳ=t7^cV&l-l@ϖZe YLgK131 7~3ԪƋ g+˻[k۶AAU+ttpг˻2:3)HYIx,hEp8g7^ut))_`va>3)H*89޳e3`?i\B 3_B&Rk g;#)j<2; e'VTͤv P=*A]qN ('25@ANroRw"\ AIA}` ;L}WӦw8jYߌZ|q^۶5*㔞-Ƿ=t8x hw'RCiFpE83}3 ặu9.W7Myps}AjvpAOn9<1,ٽ_-Z1ݠ /0}`$;x|}`<7>g4mݪ1/SInwSSK ]϶U7O? U`nstP'ڨf_~o6[!ExZWS8,< Qx?3=G'H1"gt4/7|W}tD ХmsĹ{u;.5f'>/=}ݏ?믿\7^/Ycf[*ƯVo~~~돕@)?-3Rx" 5^v?x__zE(ϥQ-u%K?xsd̺ K5~/z+tG  DW{Gp/ \DQ"?/?7tx(Ú[Au {# /2l*?7?/IEz)xp<#0..gz6tGAݚ)r$~H]M ]Dw\Afp8d;]܎Efz[:@oJLf~d֟_y`0,ݠ cV'}v?GU{_$!/ rK*Hm 0UZZo&{KiůZ)X`Q+$Lg?Ygz3X q$dg7zp=sg) YLr8EHYQ K9×#[p,S͡q/tv_ Ttt}q;i ;Ɠj;vgAS"^dn\ E9 ?%I64"ȇ aB11ܿܔ:u~„ 2XmڑmWʅ8"y.0ك@;1'W 6sHs3",-t7n]%W]]-X 3fvPd~d; E&ih:{p4}姚%%%gΜ VeCъ\HjHexd!l#! Z_/i<qQbO-Bl)Bi.THp&4%ɪU`4:4r?GGˇ1"_>!;'G:ɉɓK,)//zbSF9|8Ag"4o ԍz#Ɲ8֊>7p4dYӦt)( #dxjH%T"d u,i܉tec#QHv =O/IBO'\ȪxD&/>B =2IMAӆ3"8Q|5|Amm~_ GL&}='?'C93<-_5X_lٲOPw7K_:o޼`baw`zHnP9២ \i<~V ך5kZ[[-ZvGD~TƇ?(P W0a/{>}jaw SyAa?V_.8uT}} eO?v/HoAe 7Fk_Vsx\Cw ill\zu$hȄ^rG` 7.%'O^bG}tvd 05BV0ʠpr<9>}izIII,$Xaw Cހ̠݉'»tRx'|R^^~8@ffQCdݨ.><<|k׮]`Ajժxxlhhk(>#v/<< Q FHo+$8qҥK?'O.Yu 0 @S-hr8@ E9uT}} en(+*\r8ÔtC@CCC WWyN +4^YY9y+V|Ggݘ` JȮӧO755-^8X/))9sL,FAŧ-P)//?kn(+>k.X X_jU< 5cP'N\t~ @q?QWWg"J4vw U$)@V9oӦMd[TmdQ6)1ن6X |9`@_ 7羭4@vb*NτqS_W kHӪdNK[k3v9n,u%^㯜s;ʠ /TS k3b칍JËbi(?ҊsoK۹6ϻ,rS9|O4Wn[S7|}=hퟛT^VV6q?u0eRձeN2ej͝JءxŔ)oXGݛgM,A}@Gi<;*NrLu6vvae$Ruw};_=+ގ7z.T|vzmۦ53}Ѯmi "cVn;^-O>842Gjvc6;ߖ-[_>?4wo<_u]]їyŬ]=ucfP&jBj?زfعl{qx$2U/>Qc_g[~{24;p,K߫efK[:wsUmzt߾}x?~Ͻyhҫ4tھaثn۶[/nXwܹE"ׯDOP^֋ꁍwضe3nj3^Zْ&w{uyfƊWzWim]ӿTySNVۦ]?wxcU"/[oӯuE[3jֶGz_YYr:xGť<⎝loNuUW\+Ƞn܏P,2Wz"_&s#;߼PU!m][QZRZZ:ZG7,:0 '6G{^ϿF͔I*.@gw+߻qZٸKK'Zc=u[*ў>/f[XZ3rw{BË{&[v=LH8P.N]wI/.*Liw tm7"~Mncnx쾹eKn^xx x:2w9ql/Nj꼨T m[,UK\9c>T%~>-JJw9_=䳻4<3:dan;K#5zLƣ!Wk.gjw̨q-ܻVqF!Ĺ̗8pxJdW/7 I<Ux$i)` endstream endobj 41 0 obj 68829 endobj 42 0 obj <> stream x1 Om ?^#- endstream endobj 43 0 obj 857 endobj 45 0 obj <> stream xXɊ9W/Cs~"Ї[!I|Bpܓ18%#PmӨ/aA?UZU jӌI]6c`nh]! (#pS<z%46, =\22M%(H-s`zD,`չ6J`g]Y3%Q8{4>FEd08LC,50.512<CС\2*a;癑gXпRΆp$g)I1Fi*`e͔cT-m̲iXQ3ؖc]m0MP̳֤na);J6PISm6iݸh@c}{hZdz85Nٗ;jt#ƍzCx!?u\PglN=y ]9o7'[:X4QyM$- VUUiY!I3sqRF-60_pY53LhBvz*/Iҥ]wʴ%l6zgkC e2ue#X[džF /\60Ox~غL#5Pp$B >*˘veoG)O6@zC3TWSp*ڞfHĊީj!RtfٮեL|;;0ya<}k~Ut5r*\$j,mଥ쿚ay7:G=dSֆ4%i-omfaK-<5"3DƽےcUEW|(mn/3&L ؑghpEjoڎϦnwR4X%|T7;@-{y"F!Sn /p0e3_ endstream endobj 46 0 obj 1182 endobj 47 0 obj <> stream x_[/9sg̞gl9u&hEE `0gED&@ 3*(**[UeMMS W ֪ZU]޼q5U[6TL_rtt=8H DHpߐ \?˞1!`up*8P ?a! _.6 TPʈpٯ& QNP<4XŤ~2T|^2_c^NRduzz8M՗`H QQyirZ WNtIҫs•vR+o/g+H"#B2ҧ._hґ&WF-wpuvteȼ|].~r|vڬ$.:/VkHʹ:k-݊n\xr(M9f:uX*o䄴EʜD/J#s P$#W%n9&%=-Imp:(.BJQypw{17ݬwݘNs}=]V ;,?\W'[LvvR?g5MEsM6A^v>Hd~2{?{lST8:9X6o#\FTq4.kN]~|u\m?ae ]E/(TJO=JS[_;%VjE+J%#2͘3{1edV!/mѻ\n+UQ# lk}-8+O+]{;}*z1[zrc\mkRƑ(gV~"em_ۭ:zxt/_P𩐹Q!]:M<ɋԏ8¥ )K2Nn^H#vVYL_G|=fuyY{; (N..ØyÆ;;Z˔{;9Zwr*er0 UYruܕvtrfߋvwvvvr\\%v1co壖$hc3+$*=*P^j;9k[W>[m# {s FH5^zao( dÒDG, W){% ynHw pma"յ*l(Nr|Gxצ'Rmjϲp[J_JWF;phZW8!>#=edBf9B͢ehI~EӼ}Qy(Dp5|\5xښc站P˃}˔x-vȽj<>OLnݒ*;'չ.<`#;"mlcn)B9ulncfceuCfkOf/|EHKgFz[in[tʦʤL֣\؆L,`ɜijGGO iqj7[{]atdU^*lTw u֝'~>% 2ؾͭ:ZKo`gmfGssђ]JRH֟ѽ6Xa'p]M zpK&8\m-՞\u {Oe#qtRI=O[;mokrrsusv(e[+ݾt悶ZKl񧓠şL or["=li(koemPGM6.]m^mqqhQLQgl^D8>'F6_ȉim+EKZ[ W>m6aΌM枦$+LDcr o#Rn3=SBdo#S3%KS}hG*sq6oIo JR˘/$yZ(1'+kÜFdd#<wsÝ֦Nå^VŎrеٛKX !ut ξElk*nm~J>;WG7K.|kZ7+Bl~xhQ,v#e<)RcT|>-s3J*ί8ibBҤI E\$+䦔fVQq+7ޑBI+ߥ\%2.&vs[UL=5Bla%}$v.>v׷FY)ePhQlL/kU6v1)6PU6xޞ.|K:*(?حEfgcͯ1!qLфM.shZ_Ksd*%.q;a7FYy8Rei*ݫʚ;Q* \\Rj-dKcm$:w*fJlQV#Fx$6^_4Cc|azCbYpf`+XRVXmRO>wvsgdyJb35; pk)7'lIOlv)- {;Y`\xN1{c9canOA8Z?% bg?껒H$vƷ_Gja?<(tws(TdXoO? 2 ֳ&rl#<ܿ( 3:z(FGHVr6NvMÆI֜>qx$]w"GE.V.e:ooֱS8IR3\]qn9 o0\<]%Rgs~DV)Il<+*ε)z[Un;DogtnF%lOJ"6p?wnk7{<^Iln_iPWٜ~cL{wGCNj_o>n'UJEKho' |{[ފ$>nc,_Z!r"|l>(~R厉* l 2GXxZ]Vuaܨܻ2Q4/eGϬ ʝm%-}LT1jrא9;]:X)Ɵ#<<};*ϰ|^Q,a#u[]x*uk/.\3%ʕZ=tS5گ?YyȪ...3i;gድ\1߮,ؔ&}ݩ$n.6nհSlcN Ku7uQbGQh$J鯧zże?)m~uv$8?*2DFpA~vb$jg8{?8>L4^JmluA>L*a6U Ç{; i0KƖtqf7dߚ"aNvývssj/7'k^^&ގiOgwkZN+n6=-rvHu2JG\m\%vm p5-\]ݭ%oS݇;QX_wm+߾aV7 Z~6|8۹:`c#>ᗽeH_J';B6&VJF:jpF_J:R`Z^jc3Bb=jZQ" U|:9P`|::0 >%XvXvR ;gf^ bձ޶^A$ ֭ł󃃃?s֬Yw+++d2ٱ".?OG^Ο7o[vu U9ʨ%'N^޹uZ޲e3C+VPfҥKzYPPŋE/ާP:v̘-ol]3޿{+zu[r0s{Ne,ы754iҨQ[1999::Zngn`kN]p~ 1G& `}AİRwn5;!ecCTddzz: 4z>|{I :"##iTWA3!>w^~˘AmECBB2-T}{wB|ݡ)#Gln~E/ܯKUV^E3fZ.pUIoa^*e'K)W{Qu%ʕ+qh=饰t`}QS]ר &fD@.1硃=k?kg8q\Reu,n(T5]ܹ}K ŜtpBJSP+5lT}A*--m89 v 7N)9s&GiS̉'ұAyr>yqn: [7_.>JAO'y6n˥7lXGѣO^ U+WRƝAD7Ѿב2njCt<Щ\f5IhaZ̧g;q7oݺIJi`l5UTǼ|CzPXJNG?]DG85>JaӇL"t`]+cPPкuk[޼LbhOZَIR;4R03#tkVNL3ΙUVhBn1> :E% QAg k/Ξ-QJGU%wO<MFٱ};_Vvjz9)X">>~|%i}\ejj 9e챢"^~T :ҹN4RݣƟ}RL9WYy)|3mӧUNqP5ϜHTƆOCgpp|. ڕ gje?Dի.]$mƍy]}qbP~0'u ]hneeźk)U/tIBgѣG-Cl_&{š.nߺI'Ff),KiAV6Ҡ h[ُ\I:By ̟6mY TiF[ 8NMӋ?p.3sUѾͺ8aOgϞC{, s53ŴtS(mA:t->blFhZO/* h#D`3g6UF`m(rp: 98hdxfTWmJhTkx\j:Хرcwl=>d1܇SC֭[=$945֊&;k׮*ʕ+W_NE#Zu%BA<7Nsr"#)L=nqq?ܾM/b_  tn&n׵/ѴF̷ΟizD4az:tJ;{F:h-DoUn S}4Cѫ9p1))Iel=c',g6k/> @khtc59BUKK gеƓ')f>}[.UhZ`.Idq18pvlи 4LğMIIv;o ojU3 ϧT~ 6mCbez/ӥĮ]ߞ0as_66PDlRZT3X}H7/>B5~]kP.vU+hM\\NQ뙈??M0(#2qp`lݺ)S,_KDѣS+vݼq.hlCeh[u5:+,7Lğ332P 4ICsP %0'xpα^~*;qNsr%K( ޶-v7#֮YåSEK(B}CE/U_ܻ{vtvV7=Kg.͝T}@z=Fקp;?_<3~{}񧙭s2\ 9]y)]n_桵ukFFFkюˀ3Oc.< zۖ7 n_1ϕ+WЩx(=QZr楋 g]f?3(˧w=;ґpk Pϴgc$''sg ${*M)ņv 8Zo0:5JܷfZ_siw-ƝLhSe).\dqbx.sń!+񧙭s2h^熅u}+<:gޝ MtvG)z' {&:tŋxސ?]JǪ+MD uh~NFFw͉.(P*4\ztyBWj܃UO=m2e0ÔKݮ}]sŊmRPr23O8B<'@hΘ1cԩݮHg qSe\#,\ځNtJ[7^o1+~C9xhW^^i7;{kdd$u ___:jzI!qQ~=|hHKK3rQl!ؾݰAv0FO̙]f5kVrY[.X3xugM7OB` ֻI'!~v${?Xz)''!~v${?XzBn@I,=ғ]Ӌ7eo6k`ܛ ɯۖ7:z7YZe'[&wqbld۾m~ټe,H͜'o S.͉hTcZnBnhiyჽgv]ף v݄>nݼ1{֬ zK_',ٶo.\T1OԯKZ }&[&KM>_Qn<?_P`ˀNhM/TYL&{Ysi,qnd۶lH>lF&OΥ3۰nBn1bU_|1]$ZC ֻ wZ޼ޕVRSSN*"4gjjj=z֭[ 3fh4QQQڷo_dd$͢J5nܸ+Wc -ٶ-+g:d<4ϟ8q0}ʕf͚ϟ?}L_h<ݼn9_|O_^7JJaz 5͞5ʭ[p 4=ݻB!=y$5ϹH111K,ttVV4U̺3gdRy`m9磙'߈ӥDˢ"e&L?[t 攊CAeHP #2_5h4˗-k5?i9Å']p 0[ZZJmB %N:ϊ^f %h->*%ۖC@2wB˗UM}cL!Osm2T //462lۖm8+!>>))r ++!'̙3L¿aÇݣٛɓ'ϝ;ر Ym)7v?IN/<O s\&ws__@Ae .]frvTWS)&|ҔCNj%{{)|>|ҥ\ٳg˗/YE |q>Nlۖ g77*.\c{ ĉcǎ v8`2£@.sJE2$K󫦉'T ֗9]rČ36mr^mn~E/̙\h?#ڽ{ o>HC˖-vڒ%K(ɓ*%+~m?/WS`` Dc{ӗm7GMӡ}{__@A*杹cnj~>99dp?V-;;""BTLHL@Ԓ=N ӦMVܿpԆ4}x:3$$$\|gɶ}I7Mo\w޾жwYƴgO ޾bŊvZjg&^O?m+O޼~ׯCh g_|cA\M4 ,ٶo[4?xQܳzD=Z2v'߿}C+MhM/.T9`C4 v݄8NMn^m>@W>9`̜_nO/$z$nD?PbKOhM? '|{YzbKOhM? '[&ԟғ @.[OG[]`i/b>aTV_ӄu}xj1/b>aEZ %,aE0Z KOXwBğ#z 7H,KXw8;///]ň R9]Š u= Di1r~ݽ*-v9]Š u= Di1})-o^;vZ0a- -LJJuۚ~#eVI@Fg煫Ur\G֌iN2xep͚X%8+XL+5h^LV>j?/<"6v?o퇛==^rep95V~9? \1qOG׈z+D>f^ߏ~9+:]/r\oRil;*4|\SJv U- ELbU*ik!2Y`y9yd<׬^}l޼)wŁ< qa[T*\w~KS)hnP7SQԅi.ua?JGN20:N32L/K$ΈB^skJ&`#tJX2Z.WdJuZM)΢S-P((6PV(( =gD05 UƝg?ҥ:aC7Q/:S]/6kb:i+r49#m?#}$[`f721]G]fd@S]oGl7 ,a!t0{SZ/}?(2=6Mdy 2Jj kv%ş.mNkeB#_C_z-,,l2yLe[bŋN*{VYٿߥJKN0{yBiK;ssryYI~իWNjcccx֭u0=J.dCnwRBdzeN&U*&1ßf}Ff 1'cՋ[ro':P-wjsCe߆aGpwt~x~8J^YwH'fP"\!#U*Jlf;8B5mV^&6K gONĘج xb৫-$>zm^IWj5׉^^!V ğr0-tag.0N˓'Yi\U;N._&sPc ]lvXQ??<~礥͜99}Yf[v^ޯgv<]-Ʊ}KJ(_2pAWN >?ҋV+ěT&TI2$?sR7|CsՋ?[}3Oң-F=)bB?W9!_d̏R:16,Eٗzx w޿{K"wgвeKǏǿQh;y7\2Z?x0~*Qj䃌t#DHO6kl#[,y6c\ݺ}f2;&ȼ>Asq|dGҍ>Rj#nvGݬ2/t{sڊ.-q~Zr)dr/)n\_zQ*KXJDy[d %T*B@pu*Le9ݭۣL+W5ՆPȽQI =m?׬Y=jԨu~azm Y+x{2L 7efNǴ/ON7kb:!X>yOeH}:_?xeL'&0A8w~znrsP7 =MCthF7YpL|_6k$/= BL`)W_L*WGbW ozfry6#g!RY9U.n/[a5OfƟ/fjx|8Zq3994Ν; ͏?.Ѻe'K++SX}GFE,uXHmmKL~ط͚XbpV0:VM=hwy[q1ߓqe~-~M_+lmWPiGj'ZX>ړ7V4&z\~$mWx${X8ݮ}sw[  V(ߍh 4w~慇t?+a:/iº>a<AAA1?-F@nYpO-w___'!O=s%SA .z@bD']˳'7]4]quT_4ݠiW/~iˢa_ضΩZ^~BUScG aOޯso5ݿň<CXn޸nk?ׯmT[7bni޺Fފ3~-.}Do* o歬."y]ChblEMFfD;;UH;M_wm"wnxG(-|wϺڜ_r]U ٷQʦBmk|UA[}KRՖ^ D?-(^6xI>q>}l#c/J\ٸrjsڹ~jkkj3e㋾7EjR[}ӧOzUYX_ 3'#J JP}k.z4•}8%_.J^.lcPm8G˘t]U}TWZ6~.\\?S}umP 8ȳMQ1Ô\[O2v R>~|M74FdQZ\,7w| /ħᾣ/;$J \;,JYb6nRDn\,;wwQ\Ne.\+3wGt.?3_.<~{:;ۦM|rv9 ɔ l5xPwM74FdQZ8UGGypČI)՗j$J _,g׽f~)̤8֘_ Wo90 YD$T_E[샑?} A?}b_~f_fbҩMX<-1qs)օMZƈ,P+S v_p7}1Jmۍ5ӧ.\>x0o޼¥[[>:\TkqGsmk֬Çv.Y5kEa_t^e7ikϢ/LUE/ڿ?E\j3dRٰs[]_+QE?-(ݹIا秏)jwO;OlT7F~DA%K]\ptZ=}dn7;X۩do޼Kw dZt=VX7lϥv>r׳_vcby_]{(]@;w]l6!ϻTe46̞peܳ [ /U$yT_7:;;w;w ?G#S233 6a#R/պMgwHZ^^%t˙~gmm{IHHP&M*:zfeoEb}av>L;رcʊ &zZ/l/+/z@.''' /$ vmӴK.\P.?}Dğcrķv.z:ˮy]vk>o2wԥY;)NTPi yt&3h5S~d'=+o.I|ɛGZ~ZQܹuNlȄC+w_޼n>{;wxٳ-[<|{IoZ\|l׮]MM/{vG\]K{ә̘_Zn]ttR IOOzJ߇]޽}?]r0MtBi1.E}N{{{LLLxxQ::: 6z^hobJЦOa!_}~8q{ z S9vX.ǖzj痼];gw1ɼC9\1_М1a7VL|lθ!~AUƖ1F\3oN'ٱs֗\[TS/ZkLlŜZ[)A>'tǞkllɚ{^zu߾}q <şL,Zwg{*~b+TRt? f#T__F6Ν+--]jՅ z:tz?\~Zkw.ޝ"sr}.]^ Q\KѠJE(?_I&]׺}Vmܸ_si߻{N `g)tXY/lukMЦٶ???;:%''U$00pɒ%td}3r v={픜8BMG9I&ʢh%%&>LEK}-ѕ;+wfg.VJbّ;^vk uiTc2'P+䣃gk7c掔BZ<65cd(Ҭb>lKͬn:*=S~vTkO [7'jl= pz~۲ܷo^gOP1w>_<%dKB[4S+HS}}o3YOʔ)4ll4u"7m}alټyȑT*ǎCc" ,HiǏѰh"##ׯ_Oq8emia3g-wrK"]0C75ZGwEGʪ7oLxYn9r./^LScc#߽{vܹs+ҥK'NQKB PsPss^Zxý䟚Zhmctf]I/R:>5bX;asozA nia(A 44467ou_[[;vggO6>dגm];2r;w1ft`ҝ93H1s;3gthrT öT򼴑]:I!g'^[>iQrbx?5oR./{ ?[J7O UzyIJy5)T晐YVO!M/W\ْoO>Ktss(N5RE?-(n]O؀Z߽.{ߛsfMu^uu>wNѣ/^tEZP[>~03պM`F8" Ǐ9|Ϟ=Υܹs׮]Qyinzz:MMMpIKQ Ξ=}ʧ[̙3ݾpСV,'*t y4y|jvCt/333)C+T\OB`߾}9sq S^^N{ݾ͛7ry}SJ_RK:{^كK;eo_wR}t\)& V\\Li(snֺuk)&ǰ^B۹{u%58]ʭ[N sƍ#`ܰ07ku~‡3SB1gL6tLȞJ>oڱm*H-iW&e ew&Qδ /̽,6Ohސ"ή8??nվj%%ML4Vmi1eƖթ] ŢT_kl٪|Ty?|NOݹ}l}7)(J׳Ν;iklK-'w#TW~&ݪşT_uߛt0C#ʣ؃9WWu;Wȹs S.U'EEG)]PP@LCCpż<v)4^ޝH .2 l0#GQ,x^1Q9~m/ϟORMk(zYg C*-y&cߧ[l3 Wg횚^~nV/z@.;;OݷT$SE>=fݶChūW ۙv&N8uѣG.z]v1c >sv> mS)~ؚA|<۾ysM-S5{d0Ewl<%C|o͊S-0k~DnMɥF'FSN昐QA¤mg&2xc`r }3[L%c vnFZ~ZQ\RƟh3)`{y򆴾m֞߫ﳟ߸~?i($//ooAAƞQoԥ] Wַo)ҦRt ۷o9|1ѣ..^ٛ7o?.[_2**j֭XpşzYpQ#5 mN' P+uuu}7/ngGV\AjŜM7R\Ŀ\8srR%޽m2\eڴ ?m2̟-+ ^v$anw=Js)c>~L/}H&՗j=MWq,P1_]gݥte397#tsme7Nk.׮YK^<3zt||<]-0o\lF&{>|@s׮]C+ϟux5sۓ 8(8lIh3bv\.8+1P{9=:pL~89#6p|wMV*+i^3Q6=䛒؟7&o)Twlux ZZm5cO}B&Tky[ >)(Nq/{d~GR9o^7S;hDO罻nlx_PPG"Eu/[s4?}o31cDD7 {G*O%sJͥYSvvU*=>~iɉn gZ9Pթ_b`fqwΟ?{62.NVmܸSOP*g6:x6nr&qs(nbSe'i.gΜycE?iDLxRx0?nyzG8`khN>r ɸ{R}tǏ QvtMԀ\ϲ^?z{y0sN?lvk1z]yy's: u?2'L}{AAA-'h}/+mټ RZ;s_ \ 7ʎN6Tk0}CkRzF;0^29ݙğ%q٣(]`\&xz sGF7LcNfX4Ҽ3(yrtxF,M` ͸@0?B̑$]h}mCRWdF6ݚJZ~ZQ\\ğI|yC;] H'TUuΝWjk dgg߾u[G^Ҹ@C} VboXZ{&Z~Ƽ5վaaa{vM/z'Nŋ|iSr)͛7QP h=WYAC*6L#i>|`եK\Λ7G7|5fˡ}߆媦k}(DPJφ bbb(\(i$.M$//[%88vp.]8!1322Sw^ >Sg峽eee4w|/՗j=MW_t%tB˗\)c/p/nn6}YYYkY%谏wPs޹>5qa/ItteT"(cz헄X-(bpDm nds뗾粛,q~sxb}]͙34B/h$V톆$'%EMMMtLYa7ዟy };$9ycxL>7d__oǶJ\ojw_Ζ}z_AeŖrz/w7ҫ-;UUj~8Cǎ|s;?hu mVx6؛z7KФ^R|Z(!6:e.0uyrz073sƍg*宮۷nnGa;$$T=ubfd'*sycǎQ1)gz4je4JRh{=%Jz wzjǩSt\TF#Puuxx/F͌* y:`lߖ_lyhZxX"oXO+I5R <ɎLZeq wLS-DFb;,hd굧*ZU gfZφMb8gjE 7[g"1#憅\Oqdj}=iTC}!v7Gz@]ک2<<MdJJ*FGnj7q5'+/}6˯\7둫uxCi72̞DkpZ>"q'TAu0?&glq0kFR|>~_~Try#KB=|w=K,mSa-> ۽9w`gР+SϞ<;|fuL[RDâ endstream endobj 49 0 obj 23142 endobj 50 0 obj <> stream x1 Om  - endstream endobj 51 0 obj 259 endobj 48 0 obj <> stream JFIFC     C   " kNOyoʇߜco9Iӣg:l[ 5.cq^ r2LC11 LC11 LC11 LC11 LC11 LC11 LC11 LC11 LC11 LC ٍ=f\j]eNv=8qBE k-{Y: Ugk-SHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHwd:dy)NW@ʍQzt35>Pћ!> slF kMWJ{@q?]]oH9MA{;UW[.B?r4[ kd5Ҏc[ܪ܀+`Q'\@@^l؀2=-ҠX2>X6@] tgVjk#$,XFX٬>_9Ol,XsgV2z3IFXٮdg}3"e3%XsgVjk$,$mN7EY:W5Ґ [X%GɋG_ q;_pS[FH9+./a)9_zuqc0 @ŷ㯉dߌWV-Aj^oQ| ˋjfYi,!qbqIJ;_~p9;XQH9(@5ݯ&ىh sSne32~._KKn|k6 CKQ4\9n#{O J]35jφs]r?B:}Y%N_*\RxkTlTշji0rGc1[x(pUwǓvoϮO?E9rxRME8~kM(l-gλT;ܥ> JU[iwznKtq%\'i uր5›oQ^d^tilȿ9Q[K-FY.{+Մ Ϋ]jơ[61.6a;Cs],.W;ԷS-TQ(7먺^tilȿ9Qm-p]4}1Ṽ[:wn{tkr6#VZYt_R]LQ-KN<OGK-]tT~VwZ|%eKVa;C,8+; Bɍk5cXZ$ 3k;RϏ}ǽ6: jpϛIKQqe:s{&l0''2ϗ'{e6x~N;6|5cITyԡ@CCX!̎j}9~I&\`dyOL,< Uȳ?V&IO_JE"죉^̏ D< cb_~d5 25"$!04FP#163@`p$גe%]Z٧dfYk5fYk5fYk5fYk5fYk5fYk5fYk5fYk5fYk5fYk5fYk5fYk5fYk5fS#Ͷ|6 NtS䍳 +[6Z(EteoN%]af [:jinM`XcmqkI6umt6A=푲UýfcoOՓ]uK%dY,K%dY,K%dY,K%dY,K%dY,K%dY,K%dY,K%dY,K%dY,K%dY,4ˋd4LUSzʓ רmuq7&+74#- @GIo,۸4? [ S]k-ilu ssdY,K%dY,K%dY,K%dY,K%dY,K%dY,K%dY,K%dY,K%dY,K%dY,K%dqz/\^qz/\^qz/\^qz/\^qz/\^qz/\^qz/\^qz/\^qz/\^qz/\^qz/\^qz/\^qz/\^qz/\_|hrMy)y bDe.y@}m xe '$h\&J@-J8ZПSvoH2 .TK"6ӨeW ڟrdr|ts fD4)#["03?͵r&kt2`9i4 [dnM9i9 L'i8{ Hi!cݿG[juɖr -Hhv"Ҧ4I}l.xwϝ4FpG󾰒(C=tx\ʖئ\^4K_(bI1 Wagk%CPwqtI&OYgZXӠ@Qd6Բo!:Л, \PΫn܇']i$փ?sYC0HCL/u`o+6"!1Ǐm9 LEE-A&ѧAn52ey mGM*kCz+m{*@t=?(40ٚ47@VBD$r]Ϸ .8.R}>=>o.1.hN̞2RE+J <7[t|IU'7͍҆!ε۴cI 8=$Otayg2v|zN0֙>}%=>YSLQj-eZYk'I>CʁOkuZYk-eʞ5ZYk-eN*|$[h~-5ZYk-eN*z6Yk-eZY:L|lmYk-eZY:L$kkkk8iuo[uo[ּz{eo[eo[ GzI#!gs w0s w0s w0s w0s Fp=Cí|:í|:íx-|2 -|2 & FBaaaa| KKKKJD-|2 -|2 & 6Rkf;P;nqnGQB%G&JՄQa }WN~f%9C3g37*|tB?#.9f@^qP IY(?z>v)2tr$"(fls%v敖2DOHdC]4its "Y'|}#lsRJv#)-ӟoͧ:k%$jI|euF?>NE [ش74(_ ,$qs+oVOm]Ge-u$KUtZʺs7"}$LXto7Wmzc^#KRCGOqY'Om]I\H8!vQ#' Qh>A U~Gdԝďy#tED-kxtzjheZ3|'>ZZDc^#4nWJ PY'DaxkC^"׈D5! xh.IBT<:׏xuïxLJ^<:|te/xF^:2їtd5'4F7D5! xkC^"׈р$4%OïxLJ^<:׏xuîF^:2їte/xFCYCDaxkC^"׈D5! xh.N顮xpKÂ^8%/ xpJ;TQ[te/xF^:2їtd5'+JhDmЉ{ЉhDH2-J?1D"Z/kD"^n4"Z& &2CV W_~WU‹қVQĻB%ЉhD"Z-KB%}v-KB%ЉhD*iWrݯsV+|0}-)M VJJ1 xK2 !iQ0lF6pW_K}fUaUv.HKċ݌1d]A%\ =&QdE;}j咈hn{n & u*썸\*p%%G!ڸjII]suWH\ad©^kb->3Ƃ}W%ymv[pӈU-Q.@u$1`[t:E,@&w`UUT@.m-mcZ6WQnoB^Sڨ@-Ii1C+ N t#کRڴoCd:Cd:pR)kl[!ul[!ulL([pl vl vl vl v'ul[!ul[!u0hCCd:Cd:h; !1AQaqR"PS 2BbC`p?ZDsU_x8&f'_1r~Z)r}!;a~ջQ:FxU3VO%WBȥd>KψF0$fWU!b.VuZ.u?_ yQ\&-a-./IK9f &3zL"˒l')u-0K%QX&|B1#2 ;w2 Z.u?_ yQ\&!_)Aw|2 U,桚D(U3VO.KWC9=Kf1/Cb,M8#b@e78rFǧ)Ò.EPZ/ FC㠵msP">. ; 69+)aU.榴 |nh7* j$hNkSW}c}dlTMjCH;Aw/73n怅 `껤hRT]uu 7:8] eP2F(4 Q7qm>z.K)6ʪ­IēfX=dlV#6]{&1Çy_uNw49۰uB ~KT GTo8] eP2F(4 Q7q5D#WLnSZ>74m=K,cJH8NJtJKӭ 7tN\x&Zlhm ԁuN, `]p껤#)3Tp_#v-ܞXhݢ-$sNsk(Qk4Q!1R ABPa"S2`bp?lr* DՕ$y][9s h8Pooe/Eeogu|&<쳝v5!1&R9aŖ-'Q{e5*?-q;ucZm]k^~Uk\>Uf( v9,lěYKG3~͇Xꏊ:ƸG5'`NpkMXѫW!VNk^9%37D]gsYfd&$%-KjڵZl1ےּW'jڍk"f>ŖS[MOJs;UAT繹Z*< yRZ YB˷[NTѺ&Y)--bp7SeD`y7ZJ͇Q=Z<ejiԲlMrȘII2NYOYoYoXt.%dn%!u5<"!K(ZymkzHhPfX֨B0ǣ rPѰPrˢҝ%]W9YsKTU^uM.YiaK*#JM9W]\.~RFChN6xLCXHH]MN1j(ea*D#! RD,=שk6lGT*԰ :ǰbk:UFR ZtS]zI!f{RI"T2RÂZVM!ܤN m be(P"ɂN&umڥ 9NSoE]`h@H iTKCSUALۤP!ۤJd5]kD%I^KcCDמZ{%wli ]o"KJHaGSQZzQBMoFyOeOVIi5:^()')tKj5&U.akD[l<~XH!‹|=!Ft,RK7i!!-v'T9I4Prh듘{G)XH.%F*ZfxE)f$$-bE݉GM)Kp:zuuKTRSI&GuppRhSDJXm;*%VZOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOqOq4E.E;`p(tW2BEe9;{"!V$X9{8K̬-`)"Ʌ /NF"^L̵OilKJ[d+;T}?(T]!`\rFHu5RlТpÿ ]p St=(hUP<)(P9<f,Rvp~rץfҟ5"e\HFCӐ A 5QɗTPTc}vP]ʒR)!a ;;[uЊj_ E&%[Jưb$%뚒U2r;\Vpp 辱qڒzs9BθGu"܊'Pue 2Rj8]rM~'Z~еa.Q2iE:.I?vձiԮq$JCmPGX6MRM^HOH_Ô-EjwU?J}k  U&}rɚԭD.HRۣ*W]7݇ RRd*/rݢ렁iMQۯ_M2I| [qRz@g\4h*%4jH *+iљ xɩ3HOH^9ǤU/G[[6). fМ38,F[eQIf(G RR%JNHSjQEJ3xTLK"7JlJ BEئ}KB mo?Mk\)R)l0&`|95#k^ rd3 yN*0sX(!Icr()*+~õ*)"cOj=$ خ=)KTUYt2dįZrRzOR]KMjF,x)q6gRQ5&g4`\SUCU| B6hiתJD3qo:8ꪡ:Ʌ82`j*)™Je//4=ANZ) 9Txz_}4FA)*}m @4ETJ(VJZ哨|z+ԦXbJ%: -QEH?ic#0#0"RzmN.a0#0#0KqaFaFJ^I Wx{#՜aFaFa<0#0#^G80#0#%/m+qA FB:0G[cl|m#u>`:0@CtRLUSJ >،[b3mϵ#>،[b3mϵ#>؋KDS:[b:[b:[b:[b:[b:[b:[b:[b:[b*)]\n(!#(G[cl|m#u>`:0G[cn V y WaTgklF}gklF}gih?jwGXklGXklGXklGXklGXklGXklGXklGXklGXklET++$~%l|m#u>`:0G[cl| ZJ0P!,b,m#u>`:0G[cl|m":׷Z m m m m m m m m mvsu6$X%8Ṵ̆d!ԡJ ca 25J%|W&E*s3sm,Mk)s(4ѩVٚ)Ʈ0UW))-tjȊZ] UVUQڤXyJe6d}zX*rz)[c\aSrBz5(w=dE꫑) +Ro =;4 ʽ=j?&}9z MQ2"JBŲԬ2{ŚZTԢ.$$%?QYN*WJsTC"Wt<ϷU#VS7*ռ|>HqP K"s]SU δܻ)-1g~9QGCxKT!2~uPLh-9gNf^TL6l+ i( 4k}JCJ˲}оՂedvW 6Mc}z+jIiSRmЗS| im\mLt‰S,}Imk=JZ fy9kQ*-&bG;YEP 8q&C NYb_H\RRS\\5W WCUF""\?F)nUI)x|!RR\RV Kf.J[YK ˬLԥgVÄ3(?<8#3(?<8#3(CҤDK uJXR e<-n(wyF[#2Q|Gex2Q|Ge<-n(wyF[#2PR' WgQyFqGgQyFqGgP[IꔰvwyF[#2Q|Ge<-n(wyGV}e<-n(wyF[#2Q|Ge<.K*Oi[g?<8#3(?<8#3(?<J/-ZDhyFqgQhyFqgQhy@.˷^3|Ge<-n(wyF[#2Q|G%ԩeI?V8^#4ݭZj6c4 UEMzuFi1F5V-HGG'?|#Dߝ RB9li1FXn֭zuFi1FXUEZj6c4ḋRQԔ"sUMRH ($OjD4SiԣFfli1Ffli1F-IHJ'09&@6#f3Hٌ6c4#f3HقTZ)#f.@OBDw)槢-GeY 2w!hDTR&aҲJJ7]ZQ>rop3 KG 5 y VR*kӒK֍ ;G$?G>2)D9"%TT\%(I7CS[32{MZEMꉚcTÎTE m ݪd$/3 Itdi+MHy,9U lb+hړؙLv/Cߐ9GAl)݊lU]J= N U8 'CzZkeȒ.iRFvSPX+-&WHN$ؗ-0Zj9{ѩnK=\U{::MMVJSVs Ъ;UBT5V* (O(-%DS{gJA$RhrCfV8wB,4&k*|~QdZ6H-*)T!mB0fMӜ6g3Dfߌ!4T 5ߌ']5tIhXL@u(->#3/7(wO1JBH0\uW([Xn"2ԞJ`9JЅWJ*TMӞmAJ$ gemOHc5ZmęudnORKrI)!9B&/!5:)M̩5OBJ-sJLBPB1U gD(Pi|?Q!wFl8ڱJѦ]J*2ÔbY*Rfq&pF jIH~ʒOL^;G0q֍wMfUMN]FKnKKa bj3) 4M@@˲` ~R0j"'S-afk`Fafk`D =I,afk`Fafk`FaM !'ԙOQ#1uF>Xb:,GTc|H i@7#0[3 l5#0[3 l5"hi=>7#0[3 l5#0[3 l5"ieПW.!1AQa q0P`@p?!w:TdzB@401{4m4m4m4m4m4mm4m4m4m4m4m4m4m4m4m4m4m%ym4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m4m89IqAjDOȼB709" }¢6t^RnJ9b7d<8D!bHha0T>!F)L5$X4<2.a3|F>a*q T!pX1|A( {yZCNݥ,I+F1#6(\:'[": ƒp "+ 1SgO2Ś:aff:enZhX Ld)# bBjufB`>.*c j "AJn|bq 08Һ#s!d*;a z( ̃2&UnFȊJ !:)@Q^%. Mf|V#"[*[ Pa[;Ԁ D4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD4MD72Ԗoa^;ŊGJ U8\H`;~@L  ΰ,| f+P$w#46F!g+r>1I`:mC ZN,VN@ |U@!&쑕+A)+Y7A%n!ڰ8:)( (D.6 V͉ h}'k њRc]6i'SZiUb`H+XUԨ`h Oocrٓ)UUyje`BTyZAP#-qM B" FQ˰A0BPgpFrXUܑ  `S`Vİ-EyL^*9`,MD\(gIac]@^9ybOs-= !jE#Uɇa@ŞJZ >"rPr` w2uE>apUe7KYu4wM?qmGAgQQd @=p%.gakȨޠjd{j*gT1A^ l4> ̺zM`dԫQ04H@c Xz$|2? C' !VT, )@@/N/T B)W$+@d[t6Zh(XP<3 ?tQ!``hVaF&J"!ADQB145 %>EmXP$* hdLu&(h EAc|"/Mej <  +"'Ia@`|L.RJ&W@Ip292hh0f !qA D,*( 'T#(_Dtl3$@}pKD\(DHS#ڠ%hMfbŒG6"Џt LQY8HC.s}r|@>!}A/}˕pc (F44-ރr'a%}W!,Xvy 2FF`6 Jk{H[ ABE+Juk0'$!4P8͘PZFQ;%AFVjM@J'!fI0#G#HZM!fƸ/0TǬ3 mhS"*vi7&jqB2.%WB\AthT0#K3ƲAb 0OzaNJS#z)A_Xr`{Ĵ?Đ$(@{aG"B2,HS%^D"} fai!P.SN]YdV 0l0׷Dk@! pvQszt)$`P( $b C1 qL`ɀYA( $)8`XKd#+@{Ĵ?Đ$(@{0kI k@l|zAH N4<8 I*@&q!gn4gx]1@ſ4Jus\W:ήuq })vZo G:ήus\W:ֆQήus\W:ή5%&1Jus\W:ήuq }R\W:ήus\kC_jJMb=W:ήus\Zxk:3:3:3:3:2 hpZ'XugXugXugXugX~x@tg@tg@tg@tg@9'΃<΃<΃<΃<΃̶8\@9c9c9c9c9^%y99999kI:3:3:3:3-b[4&vgovgovgovgovg j΁΁΁΁$g,^I>2%f@Y~q(Eep}h}cLʇXd^ EdE=Fb(,>P `Ag!(LB!Bq2p%=Xw|} ́+\H\;D?;#&u>zvje+ԁsX5^q)MP``-\!t|D &bK ,~6Bhf rk0t4LaD%6YPr9~v؉P ́+:@# 1bH BQ/( 0)R (EQ@n7BY.OэjSbQV-@4"^/v g,\`HAHY9\0E9"QwEְH#"&68'u!4+skr>ÑF :H[xM 7O+WUHkzN3I_pRE*hmKrd' ơh\%xwEְH%{YMP(@5񄒑 IPxaj82@>0Y|nlqqiV0RuXbˈm"X[3 jd.D,5N3I_pRE*hmP@rl ^]A B(n* 00O 5Ry\ Q ;i<|~0QjBA΄=/ \meK_L vTn,5NIdZjիV.?hp 0`\C|8@Je$=jիVZHTT / 0`p}*TH <~*իVZj"P#5 |?v۷nݳP2>*r0"L"#X SRO'lȟO' ^[InSϓjO'"??Zv?L>|I.! ~I:?t)S?:?t`٨(5#(+&# bD &b1d&Ɵ&P@y4cRDeF'_F^XYZ lH'HrTfLT4s s<)S+Xpn-e@2A£֖ TwTǦ9(Y'J#)EOĠ$8h%ϫ?&DKLA_pI3q;q;q;q;8XeⰢgxw'xw'xw'xw'xw'*Iixxxx>A  NNNNO*$re8888>! o 8D<<<<<<<<<<l7ӿy<<<<<<<<<<?Ӎ7?2s?q?????;=Áx 308*%zb9)wK]\p^˗._H{Z.\~7.\U˗._˗._r˗J*WFҶ+:*TPRJTRRJ^,RCyv~"X1ӭB,>nkZ`9] eÒuR*2A.vJ3)nɎ#q-UeryAT,O6"5]{"T5#wg.0=" `^!nkCS^ݹsPˇ%)q1]T `"4\Ud%\7xKUY\FP@y6ya̧)ۜv kG/4 ]^,azAp`ۏ]kC^8sPԒj+f]5u0W^eZunL<:b ImAT,Ng!j>l@BǑ,Sb&oT9}u+̺՜oW,G!F3B@sT&LTBk*(IK7Ҵfż(rk?;d/ U˔t ]w-*,Sag;̓Mԭt"bowƌ"[rY0b k5ƴ6JѹkPU.7P-rOAmgp9CIÂD/f^7 }Zr-hgbUὉX/F  'wӛ TF,ۭ淛 m眥 wEV4pb%.5\Pڪv(ra5* f)ɗN&ӠT41KÝ_~6%XZ/AEzYxV+H /يE@=k,1 O9#G"Χl ߤThLji C",!1AaQq @P0p?L !#h$CY}7Ξ};еٻēg~cizD]|꯲R='j,KEB!B!B!B!BFho1zgS/?oߝDuw~xN~E MK/Adik~r$ڮJEV~푑B-Sq&5!OBB'BJRũS=w%&)GJR)J_RtCHn b<n)]_߶/SH69$XcCu7OVZ2oDAk~5jױ80]E $46L 6r&`WiaYlHx]F##R8ۅlI!廎?j#H$ ϓ4JA\d+l !廈:JzpՊSH4^EI\C{ݘ+ofj'.w;|w;|w;|w;|w;|w;|w;|w;|w;|w;|:_1c1c1c1c1c1c1c1c1c1c1c1c1c1X/R ouEtD'Rl.װ}3yv"";輶93+ZػL}P5{qQ?~#BoĚ$ &\8vCM c j) oWמ514h1 $48d`4OםF9pHlkƃsA/$c"4J'P:JxK'P&㴫xX/p]#r}UMt}Q^_L̕wsO@ .{+ c&P"3hU&}WlYt<*4BW" cT`<*7y27ÀtH<} FYkɬZ:P;zґq~}ņ+"a@gER ،Úf1v 6qf0 鄀<Ą*AX1i"% >~uDrCi`H|.3+"`AA7E^S8^'JvЊDv(rf\ TYL(мW9/P;lדXtUnw#t/* ϙ*(=H.1!,CZᐦgx-\ӂp |N.(3 i/X}vX^ ;2uq4)yV2CiD} &f맊O+uh³FQLbbS!(XCnRH&JƼTS_/:&0+Nք_s0E ~&:%?g}ѽ1h>ʁ!rsS%N,_:Sx9Pz . *  DRlX"S>Pz=8ϲ}[}YAi[5E\ɢ1p1f6t2DWRLDӈ>~в*GUxS~mͩ"  C"`()#fTieAh#9J0Ń%Fe:ˮѨG"ۡX}~M,\dE/ 򆌃I! .w ܋WBdfb: ŘBGF? `-4f}eU:s! }ѽ1h>ʁ!rsS%N,_:Sx\BTڻ\#2|yOAG/1IOpw5ܬ >/ _| ~3g8{yK'C1*O@]Y>_| ~3gϗ/>_y_ _| ~3g8{y 788@M| ~3gϗ/>_| 7_Vxϗ/>_| ~3gϗp&oTppWug~3gϗ/>_| ~oo=,kt U$H"DF5X^J-4M4hѣF>syL2dɓ&LqX t/ōn Q"D$H(Ƶp+X ђ=;^)EF4hѧu:)&L2dɗ:.+X TT;$H"D%֮`Kz2Qa DzORJ*T%ۅ_gsοdɓ&L2eNkզ\% jɵǦHHjuq"ydE6;[FمjEojbSdckCo°|d'1c <'<`)GIDʑ}F1 (oa'«hrɒE <"&ZXPEb4`pY;&.(Y+ ~.w[@NLIL8W.%E8n\`Ӳͧ$-Ck>)m>ٲ .nG+zB@G-Jxה83T@Sw [UmU91/)!#!ZPR bD>D0% y^tA+9~,Q[,e͈(G /Z](lZT+,ɻS;I' @"""-6A\רcɑ.Aʑ.ڏK(6Z )VքϾ4ךC5aQtMcȥP%9QDb' #ƾ(VS Z(p}=7ZK@k#l98X,QyCzj)Q4,#E ]ٰ 0 0р'?\,?lw45y X-Y½OiXSb[06 qrm@<#$Q9mC!DDi8ъwKi)]~)f#(H $BM* ICϝi0Bӊ]`LhP-R¨!́^ى=EE>id(U׳+J |-:>h}?ks2 D0 >&8K Ɉ '2R(%lpǮ6T(aͭJϚh›xڄoM*s[qb3/17h8DyϮAbl.)cSb=a1 @?PNG?(PB .Vx^y ?ѣF4hOӏr˗.\t 6" (PB e~UsBAo4hѣF4S6s\r˗.o*yu Dx (PB_h|i"oPB (P){˗.\rWE@/?Y=DٽU3!|_ ?|nKzH9ϐ7& .$2BW?Q N X|ϐ/y>C`BV3zHNn|ϐΠa懳ht,g#">Jp& QE#Le.vԐQ.pa%H&@ :B(tQn!3?|ϐht,g#">Mr) |ڕϒ>Cg!0uN,'-6ɣ=PxxKƢ-9Z.E]Š n0,2X5TM$l@ e3$Ђ=yIRy\9qq}[_ 2h ї$uBs"Aqi#h<@x4"ʹcv@!p\&+;&x}X#y~cG%b6\5W5TM"EqƥF|thRkqWhB3z6uMG3[s,_oOy;.(q*今%z @83aeIp>$[I.%#8BQJI:lPmMRxS$(C:(g1ϩ? 6o?O\ &j3nxfR5_ɗLQm<b_TSeC"`hp DMcYj>8E=]) jNe"$~}A[J;p <4pN5u0W B@ Ԥ+d45`_31EX^"B:tWhfZ6ȫ9 mklR[󠨺h"crԞ͛z|~Hj1HX]x8?}^g Iʵخ]+ C-#IN5xGI $s7皪\uZD>L|Fe!+p(?hR?DAbS9mP 3P4;y Usz|~eʏ? uz?v6́uw)0]>O\5g6H>eC@5¦?,Hݫ T+ԅe?> stream x1 Om  - endstream endobj 53 0 obj 259 endobj 55 0 obj <> stream xWɊ1+t 4 mC)&aߏT-ԋ'`hZ^UzUR˓B_h{-^>?iTvpͩ''n_۫aA* zN680A/0&'4uA€r;> stream x[۾.a}sZkW4@79M`Ds sQs99sUeMcK[Qk1ƷB/?ɚK,蝙g6HNwנc 1-i)DDDDD-%59291<.Ơ7_n횴ԸxsrRx|96m$""""j1Ƅ8sJRD\184~͋6 2cRbxTJ3EGꉈZOL>>"G"tDDDDD*.h6C"uaDC}͵AF554w Wj7n13~ȑ#r+~SoU:/k Mv!Vt{KmBu7޻n`'^fPIi0u#o [>{z=c"*RV<'ۯ{s˭<bר~,E 4Z a}g~1  :'Ƒ"cf/yp#Ŗ4 DG%+ |j1 s}Poy!Oӵp;s_op{POo_.!pCNoRXQ&;K"ݠ{yz(nYX8MZX3x @_ .yk,b>.$̬뻷.6s?N)]'TH#Gu%)mvF===i/‰ %pJHo,)/OM{ ݾ<ȟ壉9Z˜ /U"""jk|гZ,JwGE;թ^,,%b~#G >,mLi 3{CrsjfzŹ}й;Cdžy6qw ʘPx𴰑k6,͵{ _ǟnzޣ%Maa>9s)FnWHrus L\?pxLYX8rʍ5'n ;oq7Vv|ƂPO>DžczCtZE݋ܷT,&#e=Sl9wF\m[<́1)&?_:Z}k]ܽSl' )674GWC<씰w͇Oֈr1ksѰP/T9|BZj\I+st}YXJ/_!9CrMiD&`C^cʇDf,=5a> w{obG /D7WNIaX͆:_9rDGSW1R9ʠJYpꂨA/ _?y]bݼ*,5Pw96~Щgt3i"Q!&mP>qkFzB|CCB5F]oۗ?ynoMqwո+OEY}?=(.Mrwm\ھM.պ9RWJTOKƁS gX/P+.=JI/Ϣ4](-Iw,,E^qЁrHm)Ç WlDXhV73۳\ܢ[}\ A 1mMn;u պ5囧9`_]{W_\Qӄ;ӱYD҈LNɒWlOQ^pƥLo.S 괮>~:bf4`oc޾. H>,dZU|=a a;C\=fW çkW'Dl tCxYv`B7 G'4n!?=eI!yҫuzj<}C}z_^X:j!*S?;gnӾ^K((wcP^c~ݼR׸|""""C׊{Gi]0U})2;+B+4 `IyFh=K !UN*Q53nU䉲0MN:x7XeStgW`kX@#f]mdIeXw \txtx^Y ǟ~H(x,ӧsN]\8n$ܴے<=t9}L>=圠.;'P* N3q]]#h |ݡv*Sg`W0PkU{ィ?>H0"zwkP.)={2xp[HԔ8E]>=W 浲]sgMCx7[Pߒcu7EBdr{G_?}3WȷRO__]aj#3\5 .P`4kiY};7ۉZ/񧛋2RҸ߱gsWΝylF,"@muazc˻yuH^tAPebP+ Gy˱'.mq@7W=q }mM _5_GJMUSO2=my1Ow5 ?1רZCtOwՊxŭ-}5]szRB%ykzO]YyJ+$vuu >qyʼnSb sBY):!^Wy(]В!_xڠVTtU]CşZe58 Îy {%{u w|hB3&,/;rVG _d\>mvPW?o?etQޙjn\]872@%cO\݂~m͑*ONDDDC(A^^cQG?mpEwMlLdJr<$'Iؘ WW7]\:{ʉnwtͽ7;k5 &%z.]B}:K Ƽ%B\ ŏiݻ}g.!V kߋWPF>N(O@W,W+] n߻{7@q_UX]U/1lKWƪp誑cy끰:udyZݑ)U"""jk|z{2/Sa_xnt p&%/ȊDDDDԁ!qlԺuոvqu錿nZ6VsV$""" E"""""'@%| |zyj9OOWDDDDDDNSKDDDD>k:Q{1wΜx {.]Q0a|ll䔹sDDDܸ~ U:nVk=r۷o&ǏW|yi%7mGW)+*{oMsټt9[3g/ߩSZიɓ'ڽk>zCeb^nn=x[fճnݼlYEEH,w5r4`ݻZ1'''55UWݪb̀CH;z~qS'm(>>^kTL]r:O$'1B|;仗UP2Yz2\F9Z@ v}xHWR;wnל8.5k&|҃Ϟ*HvCk55rׯKatAp\9lތ?%%eQ+kEGG[VNܳgde\J7|p;lPjhq3x+/C̉Љ'bRz{8Vyyyݺe ,-]ui TWwnAGb>}@:OAʽw-\(-&4""ݲpƍ0 qc-Z./\ ##GG+*T`B; =kjS 7!!A#{DhZĹs%XW3@.]ĩR>[qɚcI*)=D/G]v!EtRb"(W7aGa򅌌9=:S)b)hcbbϟK$ݭ+&R\ѨGjGD 3_N;gN߾}JV͔[ZtgB:iDi19ą \;zd]8+JqP%ljUߟ{j4iŲr 9brzaTJgΜvt%q3Gݹc:3Oݾu'զUik@ m)O'AWW<ohi߻R"5 ҨA!ybVwWگm[72_ټi#1FpG]+vHđE ` a;gĴhFЄb 籕RZ~ĈሱUzV|R!F1fhd|۰k0G q>urUqqbb"A@ψx NXm EjT*BP{l%hgpիW/pK*Sz81,[dǀk.4kv6%PB\*5pY8kLEtzjN7 88(oEE)S+?޽{Kً?\I\쫞CEn׵/h_HvՑr:z'c =@s"US[Ah[gHbvv8XzO VTXzZ{hu*c/G7Gp?{9TywA f8_Z*YYYX&)) XQ$%wکz[+- DKsܸV=w-Bj7:ҷou&$~q#c./7WŋpJL6M(ϥ Wr]Hils$#lR >}4Ďkŷ/V.x<.\h'b-8U:FҠ ϡPVf`5N.(el>Uh'-h1ȔD7o\G0SϝEt)'w^L2VparnKCݺe̛;WGGXf54;ąX[6WqU˗_>hqGS =?w &p}*t%7mDw볍?,=Ɵm:Ǐ MkW넗ϛ,-<җsn7oR=-ׯ23Qn_q~Y3w0<~,G3#DR:\PwL8a9&KgjVWϞ999R+@B 2VǏ&0c{|ѐ!yUJ{v5S+1G),&5&8(,t#şSL۷q׷ouc~h 8rmbynBBt4r Ԥ3N?9rj/hW)۶n8.EW6,Ih̙3m" S0{!8Ç:dH+^_!ĉQh(phgN¤[L +nm`1llsI22]|Yrr2*Exx8N;?!q*1(^?WXa[Dr'9`s0(/ߋ,IIIsΩYŗ['N~u""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""߳ P88{`5mPDm#>XnMT?Q R9{P[O6BִAP88{P)'_xٓGXǽU[wビO=zUQjqYl~0/a}ia}ܜ=8lQܺ~M̛5uuqǽ[wSˋ/;? íL~c.Mm֗VZ Ν;h&OaҤI~K3E+,֮ۇ~]U㈳s8iӦI~#'LsVn////͞[Ѭ/m,^Hן8q\8o\S'kmgO QQQ111#GdݧOcǎY]۷Cig46ei-)3f`Ǐ1H=ۡ" ,[+.tdXnp|6n܈KQe3ݻwK;v@-{ſFN mX_X_X_X_l!4v~KBBQrׯq`dԹsb$##kӧO6[j?YO ;WX]?p/ `x)&qѷw4sZ- &;j;t}ypگӧNbꐼOyj $O<)ݭ[]7;v,F\RN/**jsf٢?]rٷb}x/eUPVV,ܼys#G n#f;ȓ/X_d?-Hygt,K/RYTXh;+++6ܼy_|!/Slۇ˓VٳgYXNou%Ek_N^|Wѷw4sH//$7ϝKg8igﭾcǬ?{u$Oyh|Dz%C#rf̘N?-[S}B=ZSܾڇ7o^7n(|>KiRz]G~>ѷw4sH//$7g;Җˢ ?L^{gΙӧO_^gm 6`V߾}qY{ߺu bڴiΝ2e ޽R̲Emϟ{lX!ۇ_0h;~#'Ls6[n//֗˝/Ͽ)\3?חW/_*.ճ999ʕ ̘1=**k._d4@ԙǷ͞'áCT7nܨeYK 555hN:Ղ̲}Iu律~`8Zuch۷ogΜ%gƤ V6FGEU+ewpRh9wH_nk-!n }JZX[pY*>:Š]n'ՔlmoXޤJFElta #uLǤZCI;0"ܴ:&)ɢ'&ڮ/- U uvxx۬/z1/4*,4<{T_|YogF-+)&R=_O]*~(=rD>\7óSwY&qu)Ť7qP}YpzhcQo[zYJME6)S)~q|wcI|4Z_oK3ùmFc7ިbWLz}/^N`4ڶ[]NL#z0L?^ٴF]Sř3P%X+rxdځV_^|3p "Ϲs+/%K\8P֎ϭ))#"0=%jDmNNe"EO'DGYT>[Pzzb)KN^a0jNд^f/*VdeO/\\KCt2kXuUv*>77??tmڞ7}@E6Dtz!n~;-6qU)dL2ZMu6$#J4Ή?ӿU,ǢOīqp5uq94nSm,[^]uvU@8t1cFcׯƍjX]SdJH1wW@V*=nfvFq_<'{No޶x 2Sc1"/BOٞ/%42r03!@Zd^dȦ->H}Awk0̌i?mN$z6DOY,zRe97..Q}i'ԗ??pEb=u,,B}?ū1_NwpԗoօrkY]`UX&$Q#P%#F{FO}g[Hy'%E 8S);lXş^Ϝ9رW<!lܸ{xW}swJOO̔?K-FnDZDE7_=@\\wVA \#eɘܜ ]ҟitח48%c FItA_zۅp~UwH V&l4>"%_:Yc1jdrtΕS 1:*F׳h1RM>H}WFꔴ.l鱱 Uk4L# ԣ]sV_G0sKjO iTAr/~nIڥu/|{gGԁ&NSF=01(,NՇ*EoY,X6?]KXNvr0- kQB?׋r=H֗ğz2ק5aNs*Mpnb'476WǵSxڭԗ'ᣗ-]ϝ;vȳrJ^^^~~<9lQm׭Wׯ~\kb>J ]G^?IbBtt^oTm⃌T\lfY,U;UXA+ҩ#C$tvB_yK_?;?V@A?ckmda!qX2$6TGTU?k7 Iks3L1Df$4#z9<+8F!3%Fn>T}5ҟ~NƟ.gs{یƕZT"tʴtA3,VGC#4Eu$F_y ӳ/YN ߠ?*ά/4d8cx4G}d%yqED-jt4& ƨ(\%,4Y̴K^D6X(nx}tAtI4t !B\ywGmWb4"G+qڊg/ Ɵu_:h3رۜ3&&`0geeٮ{eL8~ZwsrRH_f>z܊4\~N_7*J%ؾqxX3Ϝ?%ե*o-hV5*gH"vZ{zhU_d-XU~K}ٙ*\KZ}bBtb.nBӟ"Dxu𕢈NZz ;HeKIM-OMEk:'.v1138 ٝetTB=Sٟ=OTw;=C}Ϻvd@@Vp?랏,1i}[- _$|@)dokZx?wQL3_+&+;TLm~ pQuWHlvwmBnͺ`pZyZBQU@7+])!H\/N՛uЙ73#zEtW-6Z|Wvh+nWZ=c{#eɢ #V__ϳLq;o%N耾dDۨ/f'kjlg-_ "FiO,ܣGxmFϹst޽˶VP߸_űrD|;`lƴX*h%؄iQ:PIߧnz[_nhW:z do D}c(eG:QV-B/ } s`8}h"k?f?_Ӑ71hs'בbjGH}A 4!Ҥt_:?x84^ ~vM5PAV~ TGE Rz|;/&zjFllE)&S?{*⸽55"U։ _<'ވ.m{#u3L.M՗Ǐva6-ZTy]fEQf]tرc+DxYs81bDžCݿwv]ek*lڣGؾځh$'\'朸p~x {0- ⼸tΔĜ%h,z}NCv{͑I\ /k4?K׹~=]PZx m`\ ƺ%8]x5Zug\?ɶFDȿ!}=fDs٘hTo+MNM(LHZ([6#y3IkW%JaRUSe}Wd ~Hf#f0!4)ٜf2}oqN}] j|B{t/;3.vz;'󴻊^]P֗=39)Ǝ}%:›'1sR$oJ~ W GSxR};/U—ۼڲ9gKBw*1\.>z9s"CfPh!z}Nw;-/ şQA$1(}M՗O#0XX,z,*,fUTC`Xz辺ֱU߿6ٻwW.ۮ?xj֬␗R@+/+ۇe EGU|pSrr/=V9&tp0 <;_i}hZOf#' on7IZӿ_P%,δku  QocfY,rh$4`S*<M#4tqsw?[}pNjNɾ+<ϊ4P52Ω/Br\J#|ExfԅćzM=a?p E&+־UԪ/ e}CdeARQSn3uwj'Mf/#|e*(,픖/͉?aјQ(9~#{zNпdğf5hCD^_<{"C;{vۇviX_X_X_{3v>4Nw;8N4///S]ӨȵSj7gS]Ӱ4 yW= d4rٔyWc4/M8eU:A?Ν9]s;są3nݸM ?t{K'Z2Z2h+ wҭGnSE޼~wϟ߾}^xEC˗5&\(NU3{f/~:窲%jʖ ~' {&QtsƕK l>7`2#W.]s&UJ~(~Dz)`TA8Rڲea,eÔ둵鿕8bUoH[aASc2_VYVRϊ=R'kPy|Z?3R&DuF6NB ;S89~olg)G'KyToH[aASƏΧOFIqya@ÇXt5Rϥ>#<+,ƜCoB&rIw^"oi,&ƟS>ş駣/z@D {&ZW<" `Sf}נJ ۑMMnB > N5osY粺6s/)r>l\,*:FɃ*Eu!_0}+W7q8qbWuqjxm'eG\R\[tߥʹp͚5M^rܹRu\έW{g\UqOk&|\;ZiqݤOB#FJ >m&I'm]u1$fm5Uk՛"RӺ:@܂޼y3eʔsgϪ[tj9t`|a~{3227s;:իWVN.ku*3g6y fffJr9j P駵l͸OЏNkaR1e8wk'1YhˬM$Bi8VY "U8[Qe=r ?#zo.٪E{# 1)߾ynݺ,9`۷ce˄H\9ζ[qvܩLb/?'[]vI E[芊PtNbc{vni\]'NU& g^)[wO}\=COc? 8Ú7 l4`o}jthdhHl\H'%cſȧq/rz@DpGnfG I'޸qC{5A;yR|ѣSRR>xдjU):SSSQ,qqq#F8{LuEe~_gl8q1k}hv޾}ؽ{Ub,KqySwgUN?;R?wzA?ݻw|F٫W/iܪ[wNyO%?Di|l限ya栳3rv#."bzdףCz@ˬȇ1F}*FF/"ȵ=[i€=!!>m۶>}?v옴7.99޽{P=44RtGClb)//={ѣG[0 ?ǟ.JEY}\]x(*B8~\TB\hd2%%%NR 8sKr h"i;B|%яvU%2 pb/?'|Z#׭ZtEB\ إׯ 2e ./{g͚#J}𡼝{ȫ萒=`30Kڑ#Gfee)?W ݋u?{&/\@?ʼ5KxԡW~,8CqGч%]8=)M6hLyyb;k_y9 =rllEP,R%KutثgO*7'NHztHs6]łx~?"ׯAuN-wԿ2Ab݀+E8"555K,A1:tHU\\ VgϞ|l+R I8.ϟoUb9}ѢojQӥXWؚ4k?.Z[ r?^C늆}( _>y_8Dڥy7feJuWg @д̕+M a2X8Xչ?YVAkEy0/EQSu}*0CbB"ȵͬU]ɢ:}U.8zG'~&VCT/:XjjN|3  ǎOϟ!>ٱc;KKK<~XbII ]Fڵk咷8Բ{Drc${\V(zW[K,A"O?N`hph`Lyvpc%K7A0mÇ˓YYs̑++={eF$Q{"׭Zt˗/GMaͩ?r0 3BzlǬ7I򯪪΀qK׶ 2fjiG8(WHh{,sTV--ܹag}7wĿvyy -N|<#rxB4+Fx7`w|ҟܟ--<{]0^/tnʤvn=;X*eeCXt\,*/ ގ8xSNZ|) r_!uݚ5kpӏb E'ńݿO.0)y˖-Xٳr|u1G߄AoTcQ. o z0N̴¾ ;9Hv%¸wKCPVY "U4#Kܭ|kׄg륥VǏ ٳ/^ѣG._~W^>hg~ +/:)^޺X['O~ y ӦMLIIYlF&Nj;'N޽ڟ+*[tjپeǏ߽.s-\d֬NSd-B\$ON\00;#?:""bƌ :$++ nƏrZ7aϝx$1A8qI]vG/rݪEWuRkVvU{wHo߆IZc[ΧO ܹVB}:={|ƎEd+2C9ߺus͛UZRYέWupS 8s \4-,lctFHiΌfbNirXjtX/ 뢁#ӣG`|TAf_e^ :F~M9wr.BFT /q\ZfT{HHHPv_пLL0yQ|ȑG''P%UzƋ={LJJDh5xGr盗O%sR*.F=vV*6wn߶> /߻GݢS˶Me?~;^⑪?nfiѢ??BB9.LOKÈ`Rvi|"-؇UHW^~P宝;AD˄Ϟʛ>}z7N#O N㠿xI?٬?Ƿo~E~V-o>++/Y8rioߺ)Mn*|7Ƕq-OY]RR]]g.iyn0̟/HׯTo\fҤIā^ްAcTKL()Yqlu弪ŋW ieN1T{pۅ٢oR2:#Z?'nzuۣ,7HsȌ z#36|xj_g1L)m3#?![vmJUZ~0@"1]"ת7 DohWf-~Kbc׬Y|M=z@˿gBC&Y`!LuS{M,S'k[4_z{q:ɓ'k>{!CrssUTsɒAC@cUR Ŏf(ۭ[68~ >o8!2813^JQqUT%-`tJ YI\tC_[_}9k&sY̹io}ﹽsNO/LkN_^rb_ @:@8K+++!looB:D09BGqРTOi& ,[7o쐽u~}`0 ¨cQ  w'N~8_\x9\^X&EZ'OfkyWoy9rJ5~"?FO&oOϟ=Fg t^aӧa]=ĪX(YQDԄ1UgŚޝX=d/rM:3z3\o127ز*߲wsG՞ TU+[_Q}ߎ~ݛ?XWVSgoW}[6TW{˶n|w=x+m-qnK}f'-tPB#FDx!D oBZL?zYG%dd褪?7n 5E3?JZHEx\x5OA*t| DݭSSSZZp}<3Б4=hldD=y8<b!4#B"S).G: SbQc^Y ċ)h YޑB˄Ų(;E!A/O3TgF^(Aw! U˿zrE/ -TZaF.t1t\6Ғ,O DIh L89j8 ċ)oޞ?$ge|k\[k[An%X ګe %JЕQk,BPJiXO1a$b5”+Չt:Pj݉:ǜG}%yntW8z7wIOcw^]4Z.TgvOV!鹳[ d\2>KeH+%.&~97˴ &;`e޼nXbT8N2صwnS:+|Jx[}wO&"3 }D+-z\Ϛ2(d)H"5މ!H+%.WckVWVy'p0Xs4~ +SPk՘ ʝ d^u{y5c3-{mCگ ]XZaF. ?M܊;S;N%&bѡ;Zœ۳-ztݱگO 3#B!B!B!B!B!B!B!B!B!4 endstream endobj 60 0 obj 23158 endobj 61 0 obj <> stream x1 Om  - endstream endobj 62 0 obj 259 endobj 58 0 obj <> stream xWݺ/sw3{֫Bs M`( b" "1gPQP OդˢMַg0g9g9nnݼqK5ׯ[=*91*B!:\/_ŋ&__0ud*6Z)蠈0B/=wl/NEkC*G6eXϰ%@PD;zdT:T=a!p?7 0FuAk}}]]8Ȇ_7?_ M! Hr >j`m9Nv3'sF<]0tK3Uv6tt3qy˗-4dqCh5 _/pE;6Y( ?5L)q@ S($o OӆZP_xwYE }@_C"Fv.βC}SSIU_+h|u+[DКSr2&OJ'mΛ!F)lCk Zrr+N!ʹgSTb*Ύ^+B;.t7(srMn'ZsW*;Ɵ Ԛ@ggݓy1> 3W\J)lhVM۲Cn7,xwyn΍ p*ͽ]`ZUL;ȴry/TT_Z _ oʎyGg%i\\ﭹ~_2./qh=E/?o>W]K[ݭʭCّh|M( ]+?hS l?yِqv/"?Ck5=!ֹg ,l8vLʸ)c$6ٌ@HI6[{ۅnJ-<*˔;ػ{ʝ i6&8ۅxyZJ4piY(qrr ogs" qv*=(~Ps(4RTG{e>w2 )6|@\,[qx~BIz))? a 4C~q{<( 18s3ʣR.2'J5T_v=(u4d@7yĿ?{KE>Ջ)ͿE~ gG{aĄi#R6ٌ@HuhMaC =Z|yz:%mlF'/4]aAn5^Oށf#{Owjz̩=>0nӺ9<ӎ|,σOO\> S|ULwuzzz{xy\<G^Z?m* ;Ɵ*73C־E 말ǟ+a'G7Tdv=(ÀS"Y$^~0oU6ol3w'aCyOa#￳E)lhKl .dztE5 fIbJbizl strXoօ {w?}96-gšKXn.8m]OOٷ9'kT>hެ?[UŽ!=(v(ԐlPߟi4>+7{RC|}~m 9bxaqcQ:-ppmh-\yt`6 $ [q}h'sM+:~SeLJ4葼Yz@tGA9K?-^mrMK1gO':?đgƧ_{Rp|=p Z/7 vͿɿY2>ȧPf'S G|q{Y*G{#q0?n<ܝ47!_hﴦ.βĄk89lhN+Q<ܗ'J)NrGK\rnSsSਅ[*/߿Pe4!~T\0WoV\&wL݃S+BN2Sku <(vup6os%kHul0\ ?y|;:0W/+~lQ@//πc@Ҝҳկ9tń8/*1ȩO\j[g,`).;A3?ƍ;%k3u}:李}=(?֔87w^*S9_k^ ſ`rCg'Yx..6D0exX0&74ޞMr;A^.BG饧79ȸM&z'}:{x?c>HN5=f=eNN~`od{9ѕqcO`cgϰ Qz G^^>27)=),4{{)߿q 6}J >==\qwut o;ˁ4ohaC/ ΘpK^nnaC(pwtquqpt"lo7::Q! T|9R`|r >]]d.N.6@g0$'͕0˗'&&~x$ e!!!oߒHG̙3;<9:tܹ4OCAC(,)AuU: j_M:UXT锘*$3 J˗j(!!!kְ x,n޸NMf]4O'=ufT'&&5kV'%%Q{Z>| 3}t^Q? Zt)6SrD7**J#%1|%XzZet'v3[tVq"屨hPP֔E/4Oq,9rtM(nh0L2N{LYőÇbc{ [jeӻHC mfaR_JW4FCNB=BRR)4D.[yՄ.dK=r %$]8}F!(/^xɓԢ)j[6o+*SNϞ=C9%K$''o^LC&TgTgffЙ.c:D+=jݒ7P A y8HwJORɘ0Lu'zgOqJ=BrҨ2(jjzܼq#|L1-u#ԅ$c/FA99)f7B;UzpRQ@><=bƌMeۆQI˗FGGS7H}*]qH-TScZ_XJhTNBlgRkȑ;VA)42razUC]sQvpW 1KYJ_R.]B):-~̙3iP8zK PvЉAByٱ۷n;vT4 պBt,ğqqq&hq9  0a„ŋN:xjbŶ[7oPECt Ť7S ҳͥ@3}oӨJG҃fΘA2~A-^|5jwBcNK9a~gH7n,44th+<5yjSۨ?:H ]5Opq?oGݣ.(ЬFP󧤤ySW>~*d#YȢ{vsD3?,=ğqOrA .=488dڪ+cccþ 8y$:7m=ǽozLf+K!\t ucTΟ3^dMtq̈PN-4=j;ҙ0ws ]Pgg^b鬗@B SBhJmVgCKq8,,L_4qbJ#[3;s4g߻K΄j.KٗnX`TqQ_1:-mʺqie!쿌s{.Y5˭ҨHo濚G}׮tzWv:x' @JtO_g0vNEo /^~NeK,jucLt4UAw.(PT4\TC؃9 .92cq"4O[Ln>Kb;\d E )4O)'@ˎ!IT'OΞ8Kpc`0w\*(Z;o]r^bjV*^񧕥2Ѷ"cP[P!66EPP|B,!/ze;Q,nټٸA?TsdOY]᧘+7nVֹs$$$@?& l=ܺ6I~R׃'[&O>Bz$ySRכ' l=ܺ6I~R׃'[&O>Bz$ySRכ'M'KM|4в7? ޫʭkOM4646V^zuBumB{As-ƦY~z;? ܉˽>K5L}uWw.T{iBume{QúŅ皳rm_<ٖ&u=zBumB{A۷nN6UCɥΟ?gyҵɖeHA}V̀o[eķOs G{iBume{yذ{U wlkmm٬Ȅrڄz '**rM.Ewe|Æ ɓ'kڸ={ڵ+66 BKRRRjQ^ڃGh˲mZ2b_VmmiyiF.:8>fۉmomcƌ'~eҥbŊ_ վS,71O׭ZfQj`4}4*7_V@iyjmmݹs'JR?9VSS}v*2ȑ#Q?0to߾QǏg?> ͣˁgϞqڲl8F̉ufˬSUxZD,ܗ],[hgOc?jߠz!!alzZN!ˣE߿yK)˖Yugd˲*/_Vm^yf筳>5O $g~ޱے_{x?;~cbbvMѣCyk ӝڋ1O7_cQjzž>ad)!!A|D@N^^^?|aBi㬰rYɜ1i'x \G^WWw[nh/Olh5^:;/L{yq̘jz͚ՕN;6yd:εk W^E[ ߼yM/g̘銋;{?d?|:g\sNѷvE貴>|HhѢׯ/XBz$ySRכ' l=ܺ6I~R׃'[&O>Bz$yIITyS.}MVO3"aI~K^[C{'y"y&lڋ$?%l/R׃t XOܭM©i#˭ۋ_pX F$8txO[NL'QwViZ-iB>:_K)!!UOj)=B.ךZ^E{:J:3w\t]^T.S0LR(.ذ{&}x֤4_c ?Q*&UT `JTnS*7)oҵkUNapeR?ͭC*JEqf谅#D zIJm"Z.[#VGմm4:Kylil׎ڋ%EmRǨ)ipf0-\I}nb{ѐGKi q˧; 6KpzGU13ʭۋh8aSLNNQ:5vanOKccicAx<RoъK-{CTH4~;sNLR4J sKvkF{l~(:Ɵ4Q6bLOPR(TGT* ML]R{ٰa=CuiEavvi}ӻYfkڐfeeEGGꨨW ZSFvСJKNhIcb]حMh n8n_o8۲X8* an5LҐP<&O8F)nYl)ZGjZ6a;,ݮ۵sX? Nn-h/}X^bP>"UϻjvUU.AǷ+h/j/QQQ'M2\:%K?䉊ϞV>Kw]8W *ʵkk.^ZXP(** ._V8zHbbbJrv޾} vkF{:7jnǟ[O2-D.Q(㺣V=%^} k_r~ɥÇ X#!%++kԩIMjmrz6sItœJH Q+Da R( 6J?_hǏjSZ&NlS7ECyouV,4rRQS*rMrh t 1F}mfJdIbqo~~:A#"vӣFiՆj%IΛdvV/<4<{j-B&:'Tmty6K31ſ{ šrb䯳LS(2;Ij}xx;+bb5vm㟈FRXݎ )WZح LU(fO K+U|,A>IX *wz/_лl,(0;gXXR'OII1;ot9M)S(TPd]gD N2@&rC?Q_ ϓ=RN!z=eU5cK]gbU"GGTq&-Ҧj5ź|O 6 _g>o~Dg)!!r$)ڋۋuT:>]*E-ԡʩ7X(;-Z:jSgttty<{`-%rvi}6 CdFw|̉cFP(pbNMvM:>f/e$fG ϗ\9R֭3gxf>1yʧ 5I{. '`rRn9ly$>uDכ+DŘ&}Z?WX>lذje{ UA\ ]zJS(dTQ_|Ь).&4_>b]o}Sgxz=!94s}fcJ3ߧ0[6֯->Z^~{1gT*R._T^W[Tj;=>|)vb/Mh4Nś7mEwnߢt̙NQl}YsmSqtɎϞ>5V9|w~CT?`IJ}tnOdH\PJHCPqq@EGo *EDh}zz4UOEZ AX`yC>?f3\ģL=F(w%cL T*9TT|ݽz b`_#1X'*:ptE/>d:'4zKtS1j o;5© FJgx\zԯ nox^8&QC÷񓷶l/N~`S\^lf*dߜnj[t4u}4UK/9_KQJt˹BniP:qE;vkHTZ#6mUTt%Zö5[ӕƌM;tFw}ӻKSVffOti0eYmtqIgY˓=#PbvhCt1"8 X_JA}%J-QFAc7H2VS*tԅӎRW W/i%ӉAc!*C>h= vA.&/+imVdz;&|k*cyGHj>}y۝?| x<[<RzJD Z8Rğh/]h/&1T&QDQ7Q۷6ϓ~ǚqa"yF$,7O` ۋ`kh/]֓$'{#޾矔xON&)ܽ}:]&IJގUW6-YQc ~] 9/--/|J3-/Iq|aG}Qkk+ׯW /_tcDdʕ---|dY_ׅ%#g)+ϣ:5IRkf}:?vINqQh{8G3q!.GSP|\',N޼Tn$amg.lN.l[(GtV!Fs#o?1/|((Q[-??~ YV*+-Ώ? f􃟄yaƸhgN:5IRW%~RxV29C߸ȍrHoO:EcfwD炟g.ǹ'Vϕ[I<o,0k|rvP'N>l[[[]%jrˎF~ypĊ8-)r$I =?wcϝ(ͥ8EkRܟ}O_)psA1I(kɻD[Z>CЖeK{"&>Nos+Kg>/b/E70FdIZTWh@ૄ2ja+WllnN<9w.on֬Y?onU},;,__ʵE)=UΛ6n,..敕+V`ÇB9^ -9?|>v r~p14So4wa<>ksϦE[Iܿ{ⱖϟg~E綅9;$9R_t6{ ,~ɥh R9}pС\^PP޽3.;%d\K[tzǍdɒ.of͚d6/sѥS.}b9v2{ s.e4nnvcc>_H1ų(kɻĽ;7}kl[H?6crss3|@#ieeoաV**ʄ /;vHIIh4:nرE6pqZwXSe>|XX]UBoO/rݫEiӦ*: =ګcY{iŚsU(O<'rȑUνYG4hQZ wuShgvo<>?YߋfE(kɻ74v 3v~Oh7{ۋ nZVVsaׯ_a{Iozm۶56K~YX޼~jժxJs}x=4P+GqM?KFFEW䧍Ǹh}d~|=lذ6!޾}k0/LBvobIբc ѣG{|~;=yO*g)9r$7(^*Ë'_Ku:{߮~Rm̡'Eӭt'LeZ)߹pnr-y}RǙO?rQ D?}Y{F!ݻw^>ϺZJ?rEH8BhY.ڏG~{Ҕ_uxD61a//HF duCtJ5rMhܜ;gE2XO.%>~]Vzj)}S:e:͡V*G.]P(.Ynӧ":);p]^͟?§JwU̙3… mf;F1QKB3s̡IHgЛ7o ;Wn#~]?ǟ4y?tХKSV3W>9++k_x1---55޽{)ia]]+< ݻw@躯-[VAj X0g[|ߔmsw[o&6eodx 6|sNl&~&ɡR~)גw 7~kJ'Eq|b;Dw||ۼTs A-ߺyC?߽{W/FzǩӀ&>ln}T{)t!޹}^ 7Nq?ϭ[(PK~Ff}(R_t V\^sR1 gΜ)O׍:tKJJ(oXTTD.Fš4B,saH3R9zpEѬXN_sQ͛L~mݺu/gϞM4C!uJj(zEgkC*nZsߧ[hɓ))˗/g󕕧_uogkKբ+((SEͩC4[wRV6W>&˙BzڏF4'IXWUUg1t"gL2Qq6%Uʉ%#G95^-}2^Ka>ض!,o&R -2iI3sRآ0vFَGESUÃhڱqϖc+֯L[ޚ'<[1{@e(s~xsBXe=Nۆ|,:K[Iܼv>BEhܝ>r臏m.{`k>O|YYş{󺱳8f,r)`~ӿ@֘\zⅬp~t,a<]x[l3g) FjX/Pa7A֌^frH/Ķ?pB֮]KrǏ:Hw*/x-Zf~bbb]ZߨTy)44-e'OCC=" \)rݫEW\\L'/ c1ɓ'h9?{.%l|L}hk׮˙j~ƌ3qÇ'$$.c6aKW\9bNu(^*=sҿXmD6d_M`|]um:w ޢԻRi&9"cx(&X`düтgưEצOV*ON)#"i+u/şswlAN|Yb7]ѶQm̦R%@7^O֚)`{|hzG7QbsϞ??yP铢%%:F"߿{./7O6reܹs;KGR8|goݺOxh"a͸ 6ܹs(4ܹs & ]\PlCo]Qq@/ R9o߿E=;t ._?Q̬ϧHx9~qh༼̞޹m]ׯݺyKv: ~f١?QԤR{Nf Qm{4_]-fc/;enGR )޾-[Ј@/}ESΰj͓P 5qXOӟ͢?55u&77`Itͬ,ѡA}{קwo[*fj,cmyIC{])S86ŢRb NDIi4FnTrKJݳBfaBif.ANjkgA0*ן&LYWG H""!ċ7ZjzwG4]a?tŇ_6Nj>|vD`YCQSR?]5e3MП氃-{2Ʉ،П!{YfTf1 j3~i; EtM #^D@.<\DˊS.ӟzF)ak ,/-/xҪ"ooՋQv>W- DŽ|TG5g ަ8\J&P)`~?ċ7t\jfoaAX- sQcZGXKAY܎l~5ԥ~{O[/XM?hR,({\yڹvU~vZXߙCO A؈nK"$bOY?GŕGd$r%bE͹(-*.V/FGwa%^ 9yn{|]h}%ۄ=GR9Ï:ՕG/ ;xHK 8i<_61u                  ?9F endstream endobj 63 0 obj 23961 endobj 64 0 obj <> stream x1 Om  - endstream endobj 65 0 obj 259 endobj 57 0 obj <> stream x_/9s白gl& s sPDsDTŜŜ@ ([,:4=L+Ԫk׮^Pu~##ԡ .M^!Pet&.F Ubёa! O b"Q`Uhk(#1Qa!J?׭]3$H=QAZv^cF Sk'$HA2nT|Ծ6ߘ@^j#_'KV@O?_wmFĔaO}4HĆR訰S&,^8o#CLnh-)Tfu&*'@#tQ[=i(T+) 59$50+՜re'oZІn2-ɚ`y Sʬ7J=*}ޢ過2WgILy)zIȕj'ɠl]NIv Ruaw wz@WS֨|yi_d$~J#iiΆ^a9tZO5( Tth[JcݫXj؅;$_yU+s Txu>J :ںJTNh|ܝ"Wʎ Cԛ~e ]\%(TʽJJ͕FM^ SgeL9c1gM:%^O5ߡPڍ/ImqU{$?mġ{yJis:,>zCnWnTo?,^kJu*Gayڑ67eSE!z[@S!(Tۛt1H l ֪ 3`a#> yT{]R.8.XŖ7CU+AedMJWNv!rZŎ/Z͞,))@o[ՙ^,H4j_-uO>o't({BE=枽Zj{iɉUO":TE9iZ" +$G˼o»Je0-v*6|id(9<ػnG]+)u4ҁ/W:EI 8uJI)y3(-uhMaC1W)D9{UlR#t⊃gP֩ËC %J䜼n i';}i/ wt HYWr}>r}"++.cC˔R}nW/{;5v⊪^;xJ.KJ3l5rOcojڔNI%C \WȽc..>Q >jO'g廫^W8n߲TC\9jOwKik7VTVv$)Fo OOCe4H ;}B/Νժ90 >e~J^;XR`G^t?er{.57ֹ=M*68!eB1M6#Rh 9vƔL&N3W;I]^aDT:J\W/]°=enRȁ^?[\C3e'ޫ5*(rjQuP7v~BlV}npPT@I/_;Sn~~-rc[]8ۚʅ!vq*',veC7[;2mo*w7MNO4 >}( !lÝrdknP"ZvᤉƧd(S&O'lhTqǺvz(b &`#sLI壯s5lҊے%iRq#dvRoZ)±>v%);PV~h偮C opRI ynR;So'lzРARspд5!|5DmS~~~ہ6ROoI[o'Ta0<i?`|BW;F+;ˉ(μ`;pNO-y|.jTC ElF : R{uc->\TJ<pwîou vTۖ_*+cHT;GɿHtd'JΕw8F{;=1WI}}(4?{tK}OW:Yj+rvwtp8˻ݽǸb Ed'v3?{f%oH W;9:˂RF\%#A:Dn*H)0)2zvmH$^[Ffհ}槇< ><}( X_'A}7{OJ)^jcCdPlLCĤG46&L*mh\yh 6 ( Yq}no+Nίl)z"E&$ۺJtK$JT? =Q*JO3[$ה:o0m}<$Rϸtsf^tGio $>3~ ~ +B)z!wvC?\<1zu=|%rķ _sVnɉK}.|H̩a{i<tr꠶Fc4 ?iR/ZSgC'GI|\dB|ҝ:\|"{FH{:S^͍rwu:X"sG(HK27E>|bs`\$9H >s[*2oSkH/2#K:X)ǟv6>ߎ27lޓW]la~tH]d¾~ٽE+xPyOpBݨ8oh?;wwq ߙt eo%k* ץi=ڗrngmpneǟ 7{sjX*? OwGB rv({+(EimLt8 cА@ P nh䶶PJ5=}*f<=%Z~ _O'===${8ޙqeG }G@n]ۦܶRѩhmmmDy* 7G uZK_Fͩa/ZކOw7GOgc\%;*K)οP /ΨPK.nbC(tsupw2qvHR[Jqw3Ug6OW{ z.zNBzNv >윝=φKJLil+/9JJݼy9.1ggO)+V, W}+*e2t:{ ߿=kVNvMv*gNW)KKGz,suZaz!Zvɒ%4k*… uzYHH:|>Ækfl2e^=:-]*NܔK3fȐ!tlk[ݖz UnuuBݹ}51SLa/++wfHQU؄ͤDJߚoiSFEEQ]gO2ͳz:c۶BqAR+zy^55.7Y|O<ꙷqQu w/+OQՍR O_.$}v7)=VB3ou=:Nt#Ej{)"v߃VXg\JT~ThhQ st̘>]XiS.Z۷o9RtU,.K 4i"jt ۷nZG1']Ν;~^Z=WAm>xpŜ۩^щ?WӦM5:%9RH=z4N/TQʓǏ֮YVSh```eqU2;iz:aΚѡ[vm||<_fuBBjC/-z;+:7-]J;}QoHÆ D:WXNgZet'v3tBy*c~̩=^hX?t u:":*2Ʃ~Q];eʔ~S.*ԩS鰧]:x &:N!!!Vi >ywL:ѐiS|!}#9S8lR^͛VOS]U=s!:*,auSʨhRq9?~D=iFy&!̙TR!(]dIbbm4 k(I }tEZZ*924(\ttP*.zIts)=Bz(;yYּt:CCCģGPS:=L?WI'I(; #fϞmv""…ǟ}(tVtwV/ZWK::Dlr4Ϙ]ܺyNtRTB |A'6-ѹ:,C=jA1̙=qDjY Tv6iF[ 8'LHgğN(¹̜Gڷw/.AEݻi~ƌ A)ԚԲtkWi?'iNPk)S&SnvtOᠢ}av3gP1inuKD$.^ؒIA:/Ѣ UU:T a@q5/[݄3t5|pp3kO'>lpظqUC]siAK 1KI\zET.]Rtj2ŪΟS(tA|ßvݔMadU+GV3޾u^ž y4(й_껨}DܷΜi`?ip͚t:={@#4 bǷn S>{Ջ}؀8vXq1şܜK<}$((e}sUKZJ NҵǏ(8qI*IIINTTiS.Kc1C^yӦqg"5+#55џ}CCgDDW͌.Ξ9O҄=[ŋY`1cccMzEغsFb"E HᛉqE9we*Z}{]њ62n\FF t)A :0Pr׏9,?AXj]!:T{&ϘOu4ʈ~@D ƍ?~ŋR '?}bk( XV:TǹŠ׮ ggQNg"IV/`QM*4.1s&JYs9׮^~'t=4/s.X@{*8''[ +W` Oelg>Kb;\d E )4O)}'@9"B'@'Oq7FGGS^R%D\ccc_:͛6W0E,934)**jŊ 4+r9sng?d %==,~nPoMV?P{ kCOOV>ӓ+v;zlKXzzBY6Y@%==Y(k[OO=\^ԟ(_?D]F[;~գݻuBY6aں& KN7&;816dݾSW!&no7#Vԯwb~{$FW/o+GwӄzlBw-[iʥ <'fv fلv7o\1}K7VVqbԓuPvM߳4/i7OdOLv fԓu]mmgLsg+vnmnnɼX fلvKKe2ً .E;{}qX6d֯NOSf5ehf2@ۜؼ iI.U} MͲ'K]u+}thZӧ[vPoM/hwԿ}5??)1QV8^*,EiTWW7tЍ7kjj&Ohbbbv%^shZD+ .\HJJRT#F|r'~ɰῶ4]4]<3qi5gF6-?.zne٣G'~iҥbŊϟ? wW-3'WZ?O_::cYBC̘>ʍ6wAǀB蘉ONNڶmUѣG٢CBZD=zDgәa޼yt9ӮgO۬ag~lxӜO[2f6NS~5NU6 o*\'YXQn5OvgQVVFCڑ Zz YhK7&L_xv9x uωWXAٻtB^Y 2uTğ#GԹ>=vUKt$ y:jt|VV-+_HY̬?Q>|*=YoڔʙC(,߰eև3>ds]]+q{A_lwab@KN4|^ҵ7vt2{_xΜ\j}z<7K'R[򐓓()1qر K/|E)a5ğ¤s~6mㅗ.pUwҌٛqedd 7osss*{=Yo=OFWL&}4jzBmttZIP7777͈uEEE---r&Z zӟ_4nݼyi5tՄ"ş99ݻw7 !55Uxs~r劰H|~!ٴ(^B{^Ҹjt dGvV·:?l}cL'3KMBx=?ל\j}k$|@dyX3B=#UMsS=ǹ~_έ>l9?r9^{޻}O;t`ZOw3S7v'MӞxoM//~_޾y%/oaSRRJWZt{N훜쨨(R6*9ўl^{>'NH]\\,^D!-h_$*11 III/^d>uX#]Ӽ?L% &Q )}dUvgӧOK, e˖Ka7qX5Ã)fv'&Wx/av fdv;zlKXzzzQn^ὄۡ'ԛe^Ͳj/av@*TeK͟ 3v_S_,p~0;efgA /՛Bğ=2__cp~@XVoV`|AbYY= Dc_,OW}{;\bYY= Dc_,2oo>|Z 5j]=;vl5 UUrM&PbĪU aqL&?/Dm$ij"XWՙqUQhZW~y,k .>Q~j ^<鿝q)vmv9{i+E祝PƶKl&ԲvgbM RF)9L ]jqWanXARX"UqQ*5O{ )rLF(Z砒~L#r /TryL[i7|8;ONjBCCJ\##cU*\Rm'k|+ɧOw{06vFCaJPsvk,'I'jcc9c/%Gl4p8.v wˣ@47̏=}u±Oo߼N7"˗+)!ׯۼ@A?V(4rK$u^O`PT28$(.9R } @:X7k[|`Bf,ۙ2irD߿_Ihv 8(;\(:mN==RhS۝.;O 3uW:AMD*|Mx۽+لtRtSJi'hYzh) y4OnMlbN/3gQ\%UE&)V*d2l *A.ɄJuBLe+p(:F1n7 vQhHҽ114~Hcb&PlNcV">>BLP)}aw 7[9_,;K;Zo9‡Jω-rKhO+xG.H0iZ_)}oO6n@y(?H' 8q̙3h]Y2bcc5MPPPEE;###U*UDDZ#FPd;xEEl'I>(55 ^]u0` v1\ߠUN;ǙӃV!Lr)Qǫպ=ΞI>*s4q;ynk;Ry1˝Vs1[sE?Vkݦ6\G+ Ji͘m@:wݻQ*Bj'kiXy:Z$2\IW[쀧)`&61ޙsB%/Ŋ By2TT\PZOw9ل(jzP ~4yh~Ն+|+%PF6?iuXۄvݮ m@ZLXNsƠhk gyR(}[ 8uvK(ZuUbLDDI .2}dɒʳ'>{)J).yl#\+=P(֭[[uܖ<\^ZzLveCx]u0`\[QGdhg *"49l$=1E NQ;c'>1tNctW}\uqQ'ǟڴpUpȹ5? 1Nq`wq EǿxJ٠F:K%2&fbq;F!r\+XgB1X.?TVyB1\.xX9F.O1v';7FDӣ( kjPD*ED6Z( l݊niʬv,.nA|mb0g r\ܐ_Xۙ߶EϿگv )D1.6\j:))I۷n*߮;?gH:9 Y"jLYi`C1'us ;oot'X_ Aǟg~EW0%E ?{xڡ-Nj}&efğ׹RR*d2~\`9Y.OsjPm؇EX;̌?S ğl^}W.AoǷ_y(휃c76^'9>G"3oѢ#G^v(̜7[oK\]?~/?[?t(ȍ+=WC)dp}H)ȉ ӌ C+ T{VnrۚT*LQQik?O`)?O,1DF?F]OE~18Nb;'C.LnQ5sOvp y#= {Jr 5_owڏOuZz+#t"H<ې)[ǂ@_{l _z$}aV}͜Z[Ӌe,cfQr9{zYlTp-~J`B)=#Z,ͳxjxZɏtm94"ھ@*kCX) O nA|,mal b|vm AQ6pIkYbNPk׮-;yl+(ؔKnݼAiFFFɓ"&&&V?GN2XIE۟>y8G:tCDmP7ǟlwv!dAHtqvAXr+iÌjδ{ rZEDNDDv"P!|sjBE1QX.=ݚX/6Vo?VR9rh6!{?ol*bRyP\ǯf辨Y Ew2dߜ VV/mQQ} WPPM῜nmy|< jJg?(|nM䤲~_,;K;㋱1 _=}h[|iJDy n^8h3;;8,wMl-9Kg3!!ARi4ÆE(.4Clo0/$N=vjGq-mK. :tSo<*jFj~\v.Q_M}" {'7.HS RxzǝϿ7ԏNa~s=vǟ$=eA'vB1:̔JZ xJbiS~zK_ꌧ&{zLI(Lhmyאu.T)r=R&[P4s\>Jg%#ۡ S#5;3[~ŚGF  ?|-OM`LC%DN*baNI~O~|ao{mDw6rL=ڄA- >;9:J.9?Qhw['>G\vkCOC /՛Bğ=2__cp~@XVoV`|AbYY= Dc_,ׯ^Tu|ܫL zpsZ?+_?lcXч^>}~{Oޯ}uUcׯܯzO7o\Cu5\jKOQ_n|TgQo﷋?_ZUmXk?mM|T'$#rw3V7uҟm[?DqAn߼}T]&..^mǂE 9E<^Ԛқ}/jۊZ -dMYg[Vų'[>|nnS3͓FZ_~XX>[S>6.jnnfE竮VjRUeSS\:EI7wQY JT]&pŚYosS𹃅m\L".I}땾_.l-?-Jc>++6-iуF.lN.l(Gtf!Fuf-_ JDT&eSc՝1"[ee[۷o:3pO¼0v4QzSMVc+gQ{cNv7.rc(]Ay.ZԖgvGt.fRI}|\*ݽ5s[gXZ>1B&|4ӹ/Vkܯ#UZk9]~G"ߌLypČI'۔KFuhJ Ǚ]߷}f~+ʤ8Ek"E6?Rv.}PyV?-UT߹?65/Z/7tjngyGv.E|YT^*u竮Vjx$77#._y&cKwĉsZg͚ÏNJïER[:97'VXz>l|+uo(H$_Vl_ 3Laֿ<=>_gRs__ fSIR[Vq ǚ? ϦF_av綉9%6%R;_u=6"w! ,XpKujԉc%Cf~'czvvvBBBNB&}Vg턗쒁JmݪUos>+.wueo|)T fQyV?-Uܹu˗ֻ|DnیOa>nfy./Ryԝpz=eee?][Zk9YzTh0j!qM/6kiܼ}$Zjnjs`~Zq#uٷw/5ʼn\,W`k-]APyZuAAATuB&)K#wkܹsrǏʼn\9|8;$~sw𡬩O|:֌/߶ΦyC6o1 jrTc%>];bTũ?] e{ /7-JmXih#v~Ϗ h'}ԩS lrϟ K?{aÆܺuk]Kގ߰mdgeK|ՙ̘^zjժXR6eʔ+/w~5w񧡥2ܝC(9,\//m]Rz2~?ƥEK'3ӧ!C|,yazw*uV?ř|ǟϟc >uVsl^.o3n5mˌ/˙s +9,"1\{}+Y)gQծdlzn".gt-?f'l- ?oKi֍\lh9>64 4W[[C1'œ.Tݽ{ʕ+;w@-ņ4f/_v]ş Xۊ+W.Rw~̈Gdӧ4Qls钒e˖={lGwmwPk-G6Y[\xZlͥ9{)FqQ@@E*****??gf cbbƌs%a[7oGmHHڵk~_Jj޹vǎ )jXk/&\GomwZu99\P!^uuJJ ]aQA,X@AX=ӆK. 3fx\#GM(e1KhOSNMJJvVr(mNH_z5ywl7k޴/yӾlZSKfH ^>YN9/4<9zddZ!wjbn7Oۜ:8ZQ*qǰG+ӦTZzLǜ)|hb0|>if,[XA6M?_T^*O `7_O 8.a;j׮b?߼R>/..{ |1ş)Ag~#!|nY)Zs?Ͻlji*/U`F<"H?ЗP$C#rhh躵kɆLJra4& и9wd(\J|! &::zSz.R;w222*ܹs-m'9rbڛ8ޅf̙Cbׯ_̋/vK *uV]~mz|ee%8p… rhdc>?ϟ?ԩn} &|?uԯSl3fnKdB7OG,3r>/-˳S.mLV>w$5B)xkɸSsF>[FoOP~qY#B5cb)4'9te)-&\B%R~Zy|Rl ,fkx]ts慪:USׄ/((xY‚7jӚ϶=_ QƏ}Rw~̈Gd#:o߾R=O>}UU)>![H(N2Ѻ:JohhMJJ:v-3f̠Ai@g~=Lhə[(ݐCRiaCc3G;Yc(eb\-֜^MR^*ִ?7Ѷj%<[BUQl,˞؊D>ܢ/qXKiK-|zRw҅}&{:o߼th)nٲFښNDd7B7O*/Ug:Jg`Pë\*FdFFx ۶ ="X{(fJJm$x6m5S"={6] @=JuDMC -?S^Wi>ݢE 'O,LJJ\|9/+;IoWW| j-T^*uV]vv6şm^PoN3gNgO٢ݻw) 1X~Ԣ=I꿼\g?cƌcy`%S 55/Q/e/)>|Ωuk : ;B -?oLOitf >9='- >`h~Dx \244tXrƐ09vʴ%~+ra,s2J/nXtJmX+i<#\|Z>n餿y?+hG4Q?U]G߅g5a~{ T^*ut0`_ ? .=\zzz(?:aB0.^XX͛7LFdP"EAHM+Ɣ0ܷ?bhl=X~AǺu(\^.X0ܸq4C7UΝEw/^JQE#o/I3a֎cU ?%xl4[Ը.d@B~lPJ6$D#^4lh.h,\޸L|Zϗb=7ݐڲQLR~ZvRVߚHۻz}[:>{J'kׄO(,{Y7j︲4gox"m*/Ug:~ J?u&w(>|@iqz:? E5cbb6nH3sΡSg?s2dx&tqA ui1n~4Z=;~k^]D߳#K.P*̺k).^Ο9nXy>000++K'$%%5>̞joߺU"ᵫWn\% w{~ZJAqg:?ƏTF:4TnSCB6 n{+ʹ٢K4_ϗ.]~abr t,\XCWgY L S=?xp\bWgw9wۓ89RZQ v'f#f$ҲGYjŜa5FmZ;nj|@:>V?]c*Mbl'r.ymʧT>:ꖍ]Ai*m Pw(0bGR )o߼͛7ӈ@/?ϻwhEAAAaaWuzq,?Ji0~`6,**GE';Z4x*ş6̖< Y=}"ϖ-yJÇ:=b0c:o}~^|Kfp؝Ϟ NHPUk׮9y)ɡg5qq!9o1l~\[(x1ڄWUiӦ+);&-y +B+z+*] A1PA J-yx)y{Ι #xW<&Y'd3g#hߥ^ÄߖԝY>WRC4FGѱع$%pǓ"^D^钒j|[]U3N6AO58÷4ٺ=::l<cNNev{jr"99TOlhvqukڸvҷ 7 і|X޽ M&vr,FN^2(Hb3 MYQ}VÙ'g-~ٹ 3~.`/RO%0;p49z:SYbIAQDٱMTb2qKК|ċ5Є!?YJ"K5!>.7,D/z~ |}t뿌7ΰ~})^.@vtt{?[E#ŵ['~Tl #SSSn5@lSSЍ.]Bi| ?Ѐ=tm׽{}(60LIwźHV龁ZhmB6mӦA~:H 蓸8$b9r Rcnhc7III&Ekvpz&'&`7Lj~ (AsS|q_ubb"v8n\p[cEzSey -- !!մxO;Ϧ!Y_osqq1֌-?m*zE8Ͽ^o{ە$o T79 |/IyԖ#+z3sSD};?u LGB́h||cĘ̤n?w4+J<濅}Q܃alC HhDWH/|X BÃtD:8(OTcxÆZ:;3mqbAQ ߢB0!pu*W=j ^D}2%.\b)~gfcf(8QSgUhOOO)*JHHGٹYQQ4cccQFVÇioWho y[~sxȝrB{î)3kic E~eIQH3Cy*N/U} T䖈Wq Q[_n_FԚ Ah賧\z=X#-F]y3 nC%"zAz_yOKV675C^ZIpb2N*>N E~CU_+? ~3d 9X3?}>NX"N$Ir(PDWi\%EԚ AhГ@`EOq]]VrZ}(5bCM cXz/EOݾ{[j`=wxv<,|'[:pҹVs9.k_8x5.3Xx,xgT)|sz>^LXF*{:$B3OJ&A/|X BC %[yWkNOxpr+ 8 dAzOyKVmkYX*6&EaQ#922}Li y#[\|)uᶳkFe$O$.e-WT$=<˹o1 ͇ 4aldpx iK$cCWq]y$Jꌼ(q+t,/G"kRjbxiߏ?%|_?nu.Qڒeف=Eru6[< qƖ"!Tʓ--4o ?ύ 0359r[CnTH6<䞋( xnLȚZ@={]s3끀܏|Ԟo kiiwR> [~|"TWdy#-Uk>&+/~奷|\B`$rA|i~huU,6:> stream x1 Om  - endstream endobj 68 0 obj 259 endobj 70 0 obj <> stream xW˪8Wh=Ў޲n0L!O={ tTuԩKH!j?l~r~EprppS߿}:$M:Iڤ''SH`1&;y+ڻ9)/-nhP2uxZOD6]@JaSuF~82(m*1GAejF9w5'9;l.Ⱦ+Уx/A 7k\ݔ$kI)e_$J$,`w%z,ATE2l%Z-j:t22Q,fl7Xղ1lrmصN\+ȘjZ2rzyCAG,ͭ7Z8NY Jeg M:s-c_&VZn>O(n5SH7wH=Ո-+"#]z 쑙@ aX+'7ޡ$(gKTFZgHAVpx.`vӶs0BXX`9Kd'%ĉLZv M~"G}aC1x֖O vh#n_*@A dťbOZjoZtGvXp=QHf_^3%L6{Lvqst\(Xƞ6xߚyQGn/rCyqfЏ^&s̛{u);;$~ tZ "WtßA_XD; Dn ?w!-y) endstream endobj 71 0 obj 1044 endobj 73 0 obj <> stream x} Xֶ,"(eWqG""(,* "* AQADQٕ%F7&hLQ}35L@ys#=NUuԩwm @YN |H Tĥ5=*&JA_.Fӓ .tYZ㝂" #KNޗZ"rJ:HcMAJFr&.K^0AD*&( & B%Qq " %q " *'q `ٱ܂ +3W.\?=jz`^"bM^zW UTR.31%):6z>~>>gΙEiYyEՖ^TE .A @Vk3[YmVQAOOeky[+xx[AN7NvBB7dy"Wힺ켵KSA9m\# ׻o>N69zޜ VحA*'%5c#)ZG 814ڊqK.^_{ H\[|aD.͠۷o^F 2 uJ˹|3#?RՉ$Lax7Зq sxՔq^5 Fͭ;ddT#'"8vaj8PڹqFF6g >ĵ0oBȄΦ"hii^wN TLK`ѽ[RJuǑV$Ciih uםsQfZwފlh":w>E6:3:6|ӡgV@%k9q8zzz0V-T(!qݱpI[۷?a|Iy)-Ru(]Z[[Ĥ&%%EDDxyyԔev`ƻ5%nB M 06ٰaCQQQjj* ,vҲ!*vl\WJ.]hЗ%-Jl1ثW%K;vN+o߾=$$SN0Ny\ (IФ ̒C<1,Yt\ϻY8!RnM xo2呬,v|_{$ś7o˄pdn`NJ:]].n.2Kkllp#G@ۧaƍÆ :6L8EKBB+GoSfw鼦&V{Ox9+uuu50׵()/ARGGgٲeO>>9&m۶;Rx'.́ .0.3 FfWmmxo;e(ss󲲲orUq޽yihtt׬87"/uDEݺuK,--}gX{ᗸ``]!|;Ω_|?]cykN8gϞpƯxxnb |+y|DQoپq%y177?ydcᑸf^!!:+ݻw4.Pܷ~ L -o,j!nLv bhhxQ>>@]RQ}O{DyQܘN233* K.G1+>)A :wg_7^\\뫮NT0%X{;*F "6\Bf:t?j{jD:q@i>a`zOj1.kkk۴~@x!)Bttuu^xpႏ~4''H)-Uݭ,CZ~iHHSSSډ'`\*_3m4tt+Á UL:b WZ1FΝ{=#G_NO5';;8QU?""n~xx867nիU c̕}`-,,~'o߾]hqz:]{%$.' &\|jkkqΌ{I'SUT6_5˜0ggd]T~g8yEy`><T=>FKF\![NNN7Slஸ-0Lbc&l"`$|B)Pw}8/"۷A-x͛7ϟ?`F[ccénl{(>f@ҥK662 E|Y2ޛ[F~ڵkLL‰Xsuu5o\I5k0(oVPP0qD0B sss իW3~޿jѷ?{(pVfm<|pԨQ2֒߬D.?alϤ$ 555 ^)))j-̹ayU|xx8b6zQm /罿a+;o-N&>n[+MMMsQzzz[ yo\NWob6Ƅ9€...lÔj̏8oqvv3&&&9_~RUVVsD-&."tٳ{((**B6ޮ"LL!X˻fnqww'U7߰@(оfwܹsEF^^R*bWlr%ݵ=zi&o8z4|>$?Y̭HƞX5o-(&J999H ?5r'9gS ws|o|KH7\:vVe/ 999)&[8-QE ʌ0ZbŐ!Cv nӧR~}N:vY‹/⌀h߾ٳg*'SG\gϐig諷of1ye^ AfO%LHscBFFidc{JkU*R͋/,[ccc߾}e~#txΝ:Hkp{ߓ)r"2"Of+G^^̎@Z`@[ ::o˗/{ZY"!Ga }<\Z|*SpwӝEN5Iy55]vʂY:X+)IruƶA}Ytҵdi]&%%aʕ+2w9ٳӧOp>220!SyWVXf*]Q݉*Y *Gr| ua Gf'N,V8KIu}VWQQiZZRضm[$gΜ!9T-qU!#L+LN@:r13Rݺucc룏>V8;Hwӈ͘A%= /Sx:M53z&ŒmS7je#c#UYEғgc t{[m9l,$JOO=ezgWWWZU;uh8Jf$l+_%zu W?ROf+FX[G] _YY8<; P8iYҪhll9{W_}&ӧϘ.¹!7de`r_!q.0`H7oޠt{AC1EzYj4ƉOJV5qDe MpWメΈy4fPJ'UnS3 Y旸Xu.\8GB0@pmYYBN`H!|μCpE\E3nj?NV?F)A !_⊉! rzt *##$hbprBW… ?40"+BGAK#G Ladl)*vl.)]2iꀭ[,Lar?n#FΝ;?ہ qqAŋ>$==}оO`x";߾}{E%&&nڴiϞ=gΜ{liEaBkfaa&Bi@A 3pO?Vyqp/xl,SAz2ᄸHϞ=c"`O///R}}ٳg?~cǎM64ZhJOOϪ*KsP<]kIgϞt ~4e+8qV 5IOq)Ïj(F8!}Da;v@H3ܹsh"E(RR^*]fCCCHĵ~zi^ F~P__ϠT"bZ{!ֽ{w`F#öm*'6dY i5īW&MPH55K /# bĈ`H2SG%~+11QZgȔP%6mĬ`GeS/ӧvsrrAwEpB\Tv{ȁxIs"ƲlffcXXB_QO7ooGiK\a8M=djJ} {Ta7`!&xA01~xiTWVBΝܹSZIA7=B,w.SRIf,ΙOY1}:u$kb̜9S{?D{L.hS"T͛݋s#ƌ^"!7=BH_"W``V1)25lٲE#{?D{"$ ?i@***6nc6ZZZ.\i~1"QuHسf*(((++[h)I" :xNd23^y7PK.$5*8krUag~|,:tOjjj@LO'9}9.`! *He4###L}ҩnݺE@CC#**~=lll|a /_\p!9|W8z)Mj@KpVK0#ZDggg^w5WTmg`m%0أ%ݔ XDH6,r*!쉋ttO>>`FND{!=z ֜.]J|(Pv6,M$oHN~#c#fٱ47o{MjNNN@fļC'.S:N"9޽{fԩS\"W4%ܹO }*[JՖ.ٳH`?k4cxxzH+,Uݻ./545*w*o炸HwD D~tTJ->gi<ݷt1ai".IO^7:x x/8EATh`˗7`$$jࠁ@'.Ҙ0/?p* dmذf8w^$D̊.Lǁ"ܣ`7B:¼qѣGN:ell6q Cm1#z&=-,*Zuᘎt B 6qO>e߲q-ۗ,KBu~fuI\) B_1aRfK&bȐ!;w&_^TTug%Գ&.0"BCa"믿"wEƚqԄ`u6c)FH<0`6H\0"=(QH |O2&/aaa1yW%ĕr v9sPi033@'O"6q86G -󀪪*fQq&>m4(%جL#0S J%ĵ4u†=CBB4:*l4cW^TQ@6-ZZZLA:[|8Ǖ4%V^iCj]{ߓ),10dEDDPioHqQDX~}8֍r˕YMI  ''YC\j{N~.i",|ϟ?'Ń`)**4]ř^#, BcEQid?"w5ƚ18~Er+! O>Tm~}8Ϩ1$iŘ"XZbG]٦xaI\%B(as#i2m1;X3={x{ܒ>ÃVrE"M\7nܠ:mšqE={`ƴ4 6C&Y潿a 8.D^+V(@#f/2\ǑʝUTfϞ2r%OM~SPۇҟ62}ts ,潿a {"O̎}MD`F YT1hݻ\'N@+'? T"0 qBzM<(h#ƩNH‚966>}$''Cg=qz,iB?RtޝJ̳$ݺu+//C_c'3ǵk`.бcTW~!^]V!ﭏ)u{wP_ nɓ5l'ef4e4i`` EZ֭5kVLL L'KJJwu 0y9,i9;vz= _3Gz`Jk"~{m F|| Bspp0_ML=all nׯ1+UZJr+MҲ4_WO.zzzΝôr"L*m@r=:ܕ՘SyիUo\>FKX87pEBzJvvv?>|ӧE'@Si͍AAAϛS teef*UNW]]]%8'7o NӃzSΖ AÈNgw2| Ю]Ǐcږ*Y BÔ֭[ǎ*>1q?q1;p;w0طo_ǎQmJ I0*La.]*]haO\Հ7NeN:JD8~8Ֆ &`0ݱcfE ŀ؅7=c)@dlӼ\UgR?*A#xDU[[;113w/_!a0ת]ռۇpB\FCu֍AC5N4}} /O:%sѦyXۿ?|ή<Ǻ&L(//%ӧOW[4ݓ!wjnXarw2λ۲ǣ#E=dJj":tW:ry-"Кk֬ݻ7'&&f۶m^b004h0$ hvĈᙙ/! FXcM*XI*  $vç')N8{hZ[*vv[x7Q*vlw_ss9s@`{޽ 0@8Gtx7W qH$Œ3148A>|qi[+xowneSnz4`033C߯Uû)<tOOѩS'SSS ̲n +J_ a7ZG@k\hۻ#:6<\ѻonCr6 zq 3U4X !\ +&MQ7oWc';B&ᬷpKkK:d2s-]'ZC"zDb0YUHCݦ ̅}LmdiTq8=ʳXgKV++?ᐸJKY f+eLыdS@t*]p^Z+Fs8vDo3u`pH\Tԉ* GMۿcke);ubJҨ1:wbf.V{u&WBt紶fs2:1TIEqㇻgLv}O{<}<" Bf_6iNmmM%0[w0rĀ%x/[{_DQƞ|"{U[ Sˊy/ɶ y6RU$|J|3 ǘ2e 8཭4$q(۷?s dRSS"/_71Ǐd֮UE "D~RXVVd|W^ٳzYth16"o+qdDGGGhpubhikm(hq7qO/3o|uCCߴ$+ꈘ?D*ą555d%0+ͻc&'r455͝;,ahl)g|6Ma\\pijjRx葇AƎ+=ܙ[\\`Bje}%ǏMWŗ_~ikkYFH͛Wqѷi3qpqC, GGÇ+=zxbyX(gE>Ep_L'v)pnٲeJe`hhjW5y 3o[ e񚚚t_?ؓUccΝ۵kW rIy)&W3050`+N>S|jjj?CBB\B6 (̚N EPxb~ %6vorС@hk׮]hQXXѣcll~ 4L:Q]5jm`h_Uk3pNk:եE|XOO'''eddDEEyzz"# xh3hpiYi|mѺС`kaNg*3W^]vm'N$2y=$MFʹ4ԏt`޽=?^n]nn=zDmbb z^=݋jv׆MLЫwshUCn|\A_c\|feezm*? \ ڊ"+,YDYG*K.) 0>Eco?k866ЭEaYQ.|]qE\Ǐ:HQ~H毿">ѼAz ҿrr:,>1c}ir}{/gP܂ lvKU͛7!رc/^ra$9%4Hj㏉* ѩS&qeeecǎݻz;wJ9iBE֨@]]}vl4Vaa]-Y18e_ͬk3uh #_8x Ŀ7NW[%K,yJJ0;&2Err4kk>A묇|LQ پ} &N.N҃K[gI\`ck֬!^b>0i455\B*a#;;XrJ?rJ3>>>F矉? s=Z/|qb#~z}i }ڶmk> (`i2ϋ ٗJC\&>/@ff&ׯ?_zUFRbnj#C܈zƍg}&^Kl.g"ٱ~ů 4k`&T ۷ne??:6:uuWY?zT!X3wN͛7177C9s&?9]=[n+ї{vҊ6-m-K^|CAL06o+CHUB-y=#Fg`zHTիCe KCzENNN8^:P:1@AA:_mپU%o]?1@Ubwx%zA?G'JSٰxbAuu%1 bM>ДpW>u(0;hjjF(l1C]-si=z}߾}̙3eKEk=yX^C\mck,%2ZI.dΞ=sMK".|}}uڵ[n3IF$21lTu @ gHF`jj*+JIJJ***6NLL rttŤA~ejѾ @ @ @ @ o?Vn3 endstream endobj 76 0 obj 12528 endobj 77 0 obj <> stream xA 0E 0C0P0`6=D 3FD^ٶG|-[;E ~Myfpk1Fkvr,d0)+;?x\o endstream endobj 78 0 obj 159 endobj 72 0 obj <> stream JFIFC     C   "  ഥqy} a{(Q].xHWxbpv0L]ʺ(/6ϋ(>y} a{(QE `kkYX,-mB7(Zh|%gU,6WUcfm< P=(l ;i.ïFy Q[DZսn— EKPeF8< P=(n*0gfaj8щV\p;\;EsˤvJรI7畇0XrGc~!-l©,yOcɰq*,⓵2q˩64yKG=7xcv--M4iˋK4 Sc K2Mh*y3C]i5~lye ;%DrVYnЦs%KjPvEƑCIUye 6;Nݍ$[(kob):˻F '~ P=(lq-:B/G6#je6M s[ҵ/˶0Ղn9>y} a{(QאLke'ĭL+NKvcMt5P'jkXɜhi4D2ك`7J#Su A ؀_C~yXs(v=5F瑤nyFi&h2nƑFinƑL{nƑFinƑ7畇0XrGc`l"H9GEqIffݴ!inAinAinAi97畇0XrGcc^@V8@JY6{*iv!Pt7F-1u=#D7tfR}2mZi㮔Kϭ.m"#]+`_C~yXs(v=זpI`y</<9Ôla,55>a=n:F5\VZҵڌ2h:O|S]b>趱vFݴnAinMt"nAiB>y} a}rð;KMVߧ5,.uZJ8)Kj{6v=56W.z}&icr2[jӍHO.RR~6<_-Q\V6CwHk d-v)\5R5HX|Ø,9@î#b[dYѸWeլW`n|Ø,9@M_>p?;co'fk3ŗLZf%ZVX9qi5^\L$:pe ;*Ke'iv1IH7MV(6VR)oGZֶ7畇0XrcvTKZa36iź7]_V-7;&]%rtu5=yut, s7 Elx=({ k |K hYF@X;>nDv/<9ÔePwk-5&[qqrub<頋˟YyֳqolvHyuHZj~Jv=˲UE)c饑[Ɖژ*K6 ط8>y} aխ<;*ûX |-${NdQݯVܖT6>ɯ-+t*8PeLO;7+M _n+䬒Qq4L{D͈m v3j,FI-؀_C~yXs(rαpn4:cXlajEsUM5-+UW`w=({ k bv@/⡴ݍ#v-*4uucCI%ob")x" ƑCIҐ@/<9Ôӵͷg&kl{%.-S\|u%7s5~bdzIMTX÷XGclhǯNAZP'LtVXY2A< PX3Uz>[P1&p+U~y_ e ;>[L9(RMZZV< P?nu{ic~>j~0P4e ;KW 29`s+y`)N¡v$(R}1KþDo+`~HՍ렲Wt*E bhwt{(Q+ 2`,\HoR4q"a6"ߞVa4׭yTcEپ;@=(m8vU^NI+,s3סV\MSv{< PW?gx/gɗ?6.t>ۘYq_Taeoٷܧ^6þǒߞVat8y;#t>z=(mkWIzNմ}[y4]<[-w6míEcyE|Ø,9@P<:^]am* 7jVRa5^TXh< P=(lQ8}* z}PہRti*ȡ E܀>y} a{(Qݬ~xe¯iו3rR2s܏W`]w+&]y|xz٣C<=<8C<=<8C<=<8C<=<8C<=<8C<=<8C<=<8C<=<8CX]tuTu=QحN:r[(eזsx/MȢ \hC+2YaafYaafYaafYaafYaafYaafxI2*&N z-zֻ.<)cіv!mSKg.|x\"!جu2ا;JYEϝZxju[ԗ\rCkm0hnu M aY!dc@YQCy!b p6<5<$8E 5%` pD?A6 6P`014!#@"$23%5ABp6m&]ۧlnn[un[un[un[un[un[un[un[un[un[un[un[un[un[un["if֊|6\.\͝qđl)`.Aqz`\MA(Spbam`B\Jpc&暬 ywl[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-eꆴnedss`NkLG1lq1p]uAgȩ9GRmf557|##~"vx0ℝl[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-el[-򧳭 yYCN`IJdPKZDmNT >+y0Hp8"pmCĒF_ٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯٯ8a\[$-K3 cR2{-JO׋CKD)SenjA gĖpDd{fO{dYs7ڄLvLw~&=}QH(M&p E"d! ΂Q|% 8h1C#k/0׺[_,D&.onΦg5nL,Ჱ0Y*Ĭs2(zр->P/ٝ>C D#C -G3lE|\Pc (GldU؃0,E m tF@Cʸ"c6&<+'`Gd3u. a`c^Ւ𳷮ἐfX?) 11 2S;Xy4ƅnJwAq4Fxw?w3ឡl@Iaԉ5M˃bVKw=cfpERGYSlAd[-el[-el[-êpfnHd;VfV1e,QӐ׶F;AY{4/`DPWYf3H!Fd#ٛ:iʣ@jNx'E椖hPI H%ҶA$Lpsg t? v\!MlSPa$H 8X) ʗ_̭BܴFL?<&X C##v'6ơ`.ӑZً>ڪ, {5%FE%2%\RI ,f vBRBzUW^zUW^zUW=Ŷ]+`tdČ ceYYhz?z=F%\C!z&е2r?\C!z?(ZM6'IYDM"KƢ1Ku['vCsiiVL͸AhI~ibɑ;;/|HdIhKͦNk9UA ei0a$8!ׇRf|'mT˚ST(C{9X# vCq`zt6 A'NDثHŵCe鎦tEqq=#I4p1#=E3'byQ|S2v{"1MK%J%Ĕ>lߜ/$ڷK7.AwC$C; ҰoԱ{:IlvAnL)(.Nk6p/]LjXlokHv ̏=vɤ;8V4% OyN~|XlNNN+u']u']u']u']u']tGĠ{NNNNNS\ Yʚ̙᛹^لsH]0:},*9hD#'y6q܉&Ēe0y@`9gmS0} .i"U{?ZSa&y 1FK'nn_gHlPRhTaRVEGTj#2,}Vfl&k$Q uL"y氬hm/*<Ѭ ~t {{M'*> V*'^;8vxgvxgvxgvxgvxgvxhr......]]]]]]/gHva=v}#k,9fC 8ԉd5xƲlR"ArNx}.[ZW ^ }cNcAKs䈞 I~"4W6sl6sl6sl6sl6KMu'ӳdKdKdKdKdKdHkb̓dKdKdKdKdKd4Ѿa)+EOc0ҹa=(ٹ WSFBiu?#s?4UE DWVMC\Ɏ[l}ϴ[Pg= K+:CD\Bg99&"{jSeC-Pbt)IεL`<;ޟqO;;,eh)#t2z3Ѯ$_.s 0 pbj HS{HCstXX1*r5%!ueȉ PHlڂZViqdiqG9bcdVKd,9 !m=/>+,Xى; 3s8sh.w6e1Golucꌎe) R9F3piYq8x%a.KY$-&Yc3:θc.}Q-^[-Q>;Dt`S錭V&vdý3eGM%ct,W>&?k }D[ٚ6ϦqMdI'=fh.Îؔ?4tϗF`ͧN!>:bd3=P=f Y p5ChTQI&O ab<{ZkZry\fH.b' vMat]6Mat˦]*OV~~F (bRuwlZS!6' #0\>w_߂w+@Fq&37*2]⩤c 6%xb\-#> _Wc]v:Wc]v:Wc]Z o]3c :oL]뙕X!`Yo?G|/|̕N$S)`(dxa/Eˏkrq+ϗ+,M2~xm\=QO,DyF Dr!uVG=CU!%?7H,i2i;Udd0Ztp "I617LMCQtYu =QvTQ_{?G#Gfa:w $<ka&2q`̿;] btCd$,Yڸck7b\El,4H3Q.,^ ] ] ] ] ] ]}:;j;A]}VW>|U([cs 2?[3 S< 'OJ)@l ;" elK++|_>yuIN(hKW.-ʱ3M6؂Ymɖ .op^;p&`ҫ_f-o @_^^qMHkt??GB!#q R`>f\.::agTL40D>wY͔L;X4p::a`si2&a:e*!mO# y2`",Cci>fƕl3)X39,YI:[ʲ\%ʲAyK@riMz/\rcIK4YʲDHxm`AV}?%n8.Hx[ I=-x]ê3]Q&X\uňkPKEq{"C tPV 븓3I&t {sG]`OpPpٯWe(I6)#v| D;I#u&f#AovԴCY,gѵR17~˜%T7λ|΢E6K`]3KRL*䙒UI+H5Rs]tVtao?GX#͜#o?F̩TSݙ䷕{ru ̉,J,y(V,&^gW1tQ0n=$z?s-aA%Ja8.+,p!IA# J !>I9G;'KPO%ć] lL:鱰sކs7ؿ>D>q*7Vmt!34-i@[p$ Zr"@",23ã0:hʎ&CݤѾد~f OG"?nq,˯!Y" #m /Yorb1>Qmzxf~ Y~q6LA(W!#Mv!O 3Õ@Xp Ǯk#Q79R)N+  F haS v$|z[n?Tx<㛄؈]gBsX7m=܌9I 9̌Rf؁ d)X#YϮ~F~@Жȫ'녊(ChXDgA$MGag&X'wٽ#lK IhAX6(1h@up֓ikt0ŗa lG ;[`+& LI-z_ zfN$23Z@%D@dY{G $gY&"Ʊ+1ν#z춵F^XI\КYhek$H0W:y, f[爧q#QGtSչSR#)Z 3QhnAXe3$uMԊ c)"#HbkRL62ܣ[2!k<%Z0B{bi__xNdfa[??rc[pc`gZCr[*^opaڳ%%wl}\E9WNts[/kk#pf>XTeBŹM"mBwI 䥗yxrOyxrOyxrOyxHY̼r|9c'Eٷ;9c'G9ε^sx4IG_qoРe5a3)+z2Bڮ)_NIHWlOs¡ w_3|%~vLC-FR/[N_Β_BGψqO:}g<ٟ ?͕wvy>~"dAA9YI]QcMYRRPfŽR+(VPYBe +(VPYBe +(VPYBe +(VPYBe +(VP+yǴ4,tpq: 8K8%{NN=A gӠӄiiYǴ4,tpq: 8K8%{NN=IPYIPˉdi<%6#PHMI0dex%{MɉX), 诌YǴX!L%*ÎҌ-q~@xK"(=pq7^I (5lZO ri|ey {S"_pDe:}|m%{1Kp}:X?skd9 aUոN=ޣfv{% r&悗eq9f9D?)a²0 P/^mGqv8K8~A*O#q/e mr,c79iJ" 7$/*oSi#2-OIZj ڰ%,#.8GSi첞&!JZ+IYǴDLK^7䓄iz۴˂~IYǴĻEX5cN=keB ؖI8K8$Bا$%{Me&LBޱ쓒N==luv Xk٧$$yL8ښUq=mejf6~ %Nha'9A]tԂ4Far`6/9kM8HlJF&O4$!`%6r7If9Ӡӄ1KeATk{NN8WMB5 j)SPMB5 j)SPMB5 j)SPMB5 j)SPMBn/EAQ!12@0Paq "3RBSr#ₒ$`bp?}*j0áz>}H:9!RsSSU#l_XBi4*-xv~1<ݟw8K*ycyri]Qn;ǟ2Pf;?{cFe5*Q`}G۫Ǻ5$`4”uSPi"L1Q<2;Yqk=&= !33>g= !36VUv&d Tl[ ؁z*G%J04TFQ*#EDh4TFQ*#EDh4TFQ*#EDh4TFQ*#EDh(8KJ_no2Rcs|猔pzB] qIǫd4YFeI*NM]uec5_̟Y$lu IO@zu;V<i{?_*C9_g(%/e&g @h 6C-$M/R Ч3j Oe=$'S``O'ϗ(%/ZqL.#h }*Ἰm79#;.G&dJ4sE.߽s= oﳭbmmՊFOu_w W󁖔"Ylo2RRqʼng'X>05B _XVZ/ F65R{ J;zi0o7N탖xYZ(ADziވ˽wG$6p97))7?*3M%[h<̘۶'.Ɨ|M6k^s'҄fI 8ro2RQs-'_QMp'C 7 'ޛ'SMy0¶jE&Ie9xKI4MPE=:+ P= °* ƾƊFU1<ª~\I6|Иni}~=_H\猔q:3\pthp1}=b!O\icWO8NQ(1Zz3T*="~^LIgE3=ʜ猔**a7I!N8FA9xK [{# gs|9zœ^p2o)-7$3~"efW\P16Ui@Dl*Rcs|%P&ӝJ_noqaӚ8KKW:----------------------------------jGT !1"AQ235aq#4B PRS`$0@brCst%cp?G \֖ReuceQugث1?m J58ay+IVd8c4꬈py~;y$2 4 ғq"1,I'vmLU;"wqS(!k%C{|cXN7j!5km-Uʐ [Ny _EUL iyǝWS+ur='miQF9=`~u(^ ю!N1GMn5[6tGu5e2VGM]%պ^ApG8o!訥[!n!|Rw-qc&O˻iTr(>!,{B~]G:9g+m$:Pc;W=c3E Ԑ:"BqGy$ȩ9N&j sj:qP.>ړRؖu;(pe{e uO&eezkMUǁ;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mׇ^Ӷޭx{Nz;mг\E mv}0D'6ZH#4JkϺq{BgvqƦg\AK|}ߓ5z>IbqROt7 ƒF2X}ɝi"=1(I$p3Y\%{)sθq`2?&6+}`ΕII# d<$' t(1vF`cO0妐F$7G 'lyL & 9x]ʗ1lHQurk%E}#r>M˼]Z_wmNwuURTK J'H׏X%NIUp5R"HьvpuU7iB#~s$'Iゝq?=r D!u9.$0FܮO$rk\O"ᵉJ9=?s#4 LWt(LiՍ0%@3Rvs$?E68ӳ7j1 HGyM 9j3#FM2lKLwE%Eq[btϚIMA tIrH2E3qlo5kx,fPiS CIU$ym4 ^%+7 ^!6 x2Eor,9`mQj m6AU$TKhVo5GNo;̨2zu(iKh]V@BXE:UGN HD.4E.4SmW}4Km 4*+j `oʼ>☎Dk Fh$1+\uPyGdkim1V7ێ@香`J?1-Ģ1 ?7Z ]AtF֑B"J 1=u,} om)jјCH~:}ůЀfH6{ 0ccF sŰIB`AM,v$'FAIenֱ(1x4j'Hd/I*!f{.د|blWb]^+1vf7Wjspk1v{.جNkSU$׾b]@x.FbĒOQʜI y1Sc &Sc.S\ Ye2y{9a#yb[[>mcٍ0guYM24+WOEg"@NE\J8@q@; N* (@Gw@䶝9o5Z7h6i#a 33Q+xỉz -CZ496zߣ壤rap] \WP,*ݎ ,W칽dp5N8yuVfY=KͲlɾmev.i?sߺe683Zce+|¦xA#N xVko!eG^44R'xfau;w92Mmm^&}ZԝW*)#H- 84<}D41T$_[`;H%G5#]jjYi%C*1%xtShṌjK?Ӻc 1iSfZcoUk3+lǒ-J2ґ@QGbmm(2?M))[F* mx]8Ӛ?|h\Hc5{^GB_^[V1fY3wJd= W$/^пeifEU@>{OBҳ2d'~Ğ+z|I_'~Ğ)gt=_{Wف>*|Y$xN?{OB= W$/^пe{OB= W$'E!Rs xN+>􏲌q \h]W/} 4׿Re{/WRe{/WRe{/WRe{/WRe{/WRe{/WRe{/WRe{/WRe{/WRe{/WRe{/WRe{/WRe{/WRe{/WRe{/WRe{/WRe{/WReВ]^1]^1]^1]^1]^1]^1]^1]^1]^1]^1]^1]^1]^1]^1]^1]^1վ)>tOVamv:<歭0!Y++/t Ơe2h8B`+PD +c*j:P\ZHF1H9DR>'+f;(>q4طh-O0 UJLlQ +gQ˱7}7S\?8F3t{jc,Q.ho,I[1HHÑ79l9~-m$\2at8 9Uu%.ΨQR+4i xUH7 EW)3(܉RDڌn3uXOjg;i;hKfQǎhDZ#${P9ɃP:iI#\#F."GjK!:IQUTE擏P]y6>_j vrG5h_jW(ӽb7 AT` loqX!z#P)ZXc.⤹A5èMJ@QK̍@ oL8HPj1DU,rM3P01M;Ɲ3 S^z PإKx{d H䵅NZ0BNqxx˼0!7~uX|ߟ/[ۢӯP8Zn@c'ju)#)8Fpgp[`5wiSV1G2I,c96*9sޖfDPwњ1G-N#8񭼁iU-6͢`BY-.4i;?>RۈZhBFCǎn95_Llզ(ᕕFG4WaObzaDžM`JF$$;07 9D.5+e 44qnamߺRj 0f h'L)Ebl zsW>0F%$+6 Fpvgb8qIme H5w /@wA'k? 0ۦ[^ŏjDѸce6~d䏣髒o23 ;W6Mͨ3o0 41Lq_j >dwUɋ4|cN;:%ڣ;JZ4"$9`G-rvKre&^MgVI )pWm2E'>PI6T$J#"-̅^w7<* 2<(Jl1bZU6C}rC p }r7h}Ƭ"-̅^w7<* 2<*a8#fmxprd"MĞvƶ;yeIb⛎ W"^C9Hx.嫅7,H0xp:BK'0820~Q̺F$':]\N+DѤ5P0(0CUJ'~D"]D*50X6<7>oȲͧbqG~E;m+roNif|c0,Sb3ruںB5Efff!cAɶ![qr (::^EJOǮ3 {Б{9Wc!KVG[Kۋ;{;;,Jv/ks)!3d5z݌~8ggxbuyI03C ŀ;G uˮ1=?O%2Ks_N1S]J$(.6^6`%zwj!os9wV*gPE1\Eq\EE.#f푞>o5E*oGP=_y}=Cbwٲ7$y*C!P៊3uabyU.=ddGu+u?uȺmך)icT1?MZZh6&Xݟu_\Q9dnzy$>~ ukVc{Q.fmSRSy]Ya C(I,AguY9XFסqHv%mcxuZMx2sOQCbeo|)WOUAyp$yŢm˼FYwnk3?O]~cX|{4m4.t2Y٫Č1}΢`Dz%*68 lM~c2ŝ2Evs;ڝcN$Mu-ɴ9<G^.k]{-Sf! H7wl!XRHG_:G J",HBx=pmnZgڮPPadڬva3"^vt3u:DҐX- `wQ3ɲ"EvFy}ΕUF]{-[u[_YDVFfa$oK[q0e`T{Hi#ylM) }0ͳH#8}WU n1n k t,UZ)VOV [4*Q^+9{E$bI_jy'ՍRh=F;{#:]4osSNA#R0bz5q%+/20K9Bw]k&e"e ۷&x-D$[̑$>:q 1N1 Ȯ:Y"i`0$[8nvl~1P Yb! gP=EqF~1@fFxTK(ˆWaҽBEDΎ5h.8GX{vt#t.O,ϰmPyJvC19•2Il[XyZӯL^I<|w,H $mu"CʷƠc~PtmY" GDECa<ĻNkw]Jչ^O %SL^g%+ن@ ɳwDܥ,+c MύI4|rlElI*ޢGXQ1鎛cM1Ƨ鎤rĻ6V.=7#rW1z;^M_̂<ʻH+w]Jyj*,ئ0i8`cHunQ99RY(^V]}lQ9ZkDH[mE!勒6-:o[#hGђUe T8xQ/ݢR Ӱ])[Lo%³u :g g-NwF["*8cLYlU%#vuj䷺2[k MS=[ַV ;wֳꝣI9\?M'6i՝7grMB4n)g\? c!H/9O3 I]A19 L2Ün-rItbscۓUM}r İR9Zli˻h15ٻcq%~Mr ekGUfU|ΑJ-pߤ6NG'%4i@͓fK+ ߻Ϳ(lG_U,ܡ,zfcrmuXKuqpm9ӲQ\}5'vEb\j(#Pc'WNv Κ s"5w_;֮r|_ZWu}jw/]ܾw_;֮r|_Zg8^BFwoݽGWu}jw/]ܾw_;֮r|_ZWu}jw/]Ce.$'~kܾw_;֮r|_ZWu}jw/]ܾw_;֮rԤKdar7Ӱ] y>Xgr(rH]%h}lzj}Nd;}uI3o=25ncM,]t`dneឺm6)p&E$먢I) <~¤lVϺ߬P3L V.Beft@;Ϛ@O}ͩthmT-NqM"MQ;p*.O+{x${U9wAᚿѴb>aRk,ԒFpj@򌳁G74 eF'T-Vcʲ,׫=4lEòv]n9B׺;맏WMrmRM}'ϴh4}HGy2ngcYL*Dxyʳ2N1Vr3 ѴkHgw jK4$3B7yj;% YMJ<5c+:W?Mo$s!zu~MNX1$yBḑ?}kH]c=5mC<@2w5:8gW4/mz$+!WUy=W8e>ORIbHjT5om.n+yyƧ 5moރԷA"ؑYߤHUZy'SBש2Brp?Wvl!X>'jUݣ %`5wٖӴX{W}xeh%4Q瞍z*)X6Z49;qa}ݔܮIm#ngl娥9{h"i>nonxDRO#s|LE ֓QA Mpk2Ia~meg6rI4tT mUϘ.ty}q̻Y5좴BnpH{i.7˯KyuI}Z.i/VE $@ͼ'~>YmfHk)r?V͵+ivE<ڂ}..K!݃[ovzM= T>o^dtLQtf&K0[H5},=#JWf&m\_oEp)[eVӳNzحFVqKDN0|#A4qOݵNLz3ǟ1*K.NQ餙A\SOH^(c%~رkFV֠ny;;oQ+Þ. __Z7F0Vru+wΩzEJ80X|kgk++z*eED,[j߹tFxw*`X}ZRnm$U ?@e.PYI6mn9h3LEM;NӜg\ktlZUr>\,i]71  渖+p䶟sPToMo;BT#N[W}F87!rn'NT͟Q|bdgCg?Nn|^\`\NK H\R4MևJnE [j383Ad| s ZOuȐpsqp91}ckn_WnkKѰXMWW)GU]$t'KO%{-[u[_޶9o׽m~r˯z志^.k]{-VDIR.Y2gFz3*׸_spOEk޶9o׽m~r˯z志^.k]{-[u[_޶9o׽m~r˯z志^.8!뜁ߺk]{-[u[_޶9o׽m~r˯z志^.k]{-[t-3"!)pI/ywpOx{ER}KVBm>7bv>D7Y(k[hճQ[Ft?SzA}՞?!0|j$O$kV-A=ŎFA# !a}(IF*4[ncjH. Nr@[#eDҮrIj a681\PL]'v i%[r{Pa zwT Hug(ͷfʝ H?UU,Pf5D˃=\EGSSl닟c~>.o^l [Kka㚐H&DXj"|9ֹlA^x'_a8l6A9Fy}=Ao)=w (ͷfʝ HJ{Z%O/W-7|YJݎhH-b<0/ {>EP?x]Dd-<'j5yC75D-BOVv 4ن8Ԩk G%"14-P5KMu{3-]ĜiƊC=uI>Rd}Zٿ^2kfe$î1F*=i9R-*DgJicj]ݏ]ѴjwƮm1-<ٓuwKMjts0n#m&՛sqq&j̯}Zٿ^2kfe<]îA#v4SOhḷf_Ynv&rUZ;?g:K5|i%ar:$Iטw^oV8TbIm#Twbkqo4E"ib{H]E2kfe{E ,ޤÇJ솸W"))V0DWWcxf'IO<|nz?X|ߙGt;yj>Q]qh7+wGwFjGn\WYw#<A`tiPŇEb5=,xWtD][3Hn|5mɜ@ׁ\гi)N(QIiLRoi:NNi̸݂ԣT5ҼPJ$|WqVf^Mk)ݼ֞GTHǗ)nLA i=;B)&RBX& GeuNHdhöM!lF㯮V}.I wys\cbG]A,2M ݧ|G9qǢJ1p#9qR .%[0Ovk_w\&zkt0٧pmq|[r['M5o/[-]ӳ9rOZ|tGio6){b|y#G/9aUw+o-nU^Lj`$ebJwT7suo&F-[K Z2ѐ|W'QؼQ#+>=Ƨi2XсZ8^8D-ğEr[O갘p=mo4K+lތ*ᵂx%2l"3G 7FE$‘^ilWLuHV{Ƈ&,u W5&MWo2xs@Q!v-+}Zn-t#£ >G2PG(iL)u>#9T?muI6@KGo[kܾw_;֮rG$ Y&w1OPO'#tE[VFxLcׄq)ekyc׉~En!N 5D(L1yE!%8#?؜qa} *FYy[=V!V <D, #M{QU+MZۑkenPymEqNty*V[w_Z?H>$QoM؞y%Qu A QކRUH%jd*5j G{YٝK&_kkZ}V_kkZ}V_kkZ018Ÿ{$QcpH37HȒEOIɃjW>LNЕ;!cē?'͇:G_/6:OR"^3]?p+hA DI`XA1Pg*-3C…̺yR xWK{nqvo-)cp|u$eG1]];wE 9UrM3*$z 54OPxoop#$*5_!ά(]&EXJzE%um`(_7F<ػN2w#]FyUwWW0pmn,]XpqTf693Kg&ciYfToxf0m:'r禮ڹ JGm9dSrZڶYIk]y7xVAt ۇgBmu \V!Azi h/ hڗ'[jEA88l!hf{5 x,۳kk%Ԗ|n^36'ӑÛTK]^ A- rÇ]Ms]4r}vӶ>e"NeE, u PNFGb(.RhHl3w;HH.5ӌj%̽ ZL9Qp/]%kw_4ծ9p7v}]|_V˯KyuI}Z.i/]%kw_4ծ]|_V˯KyuI}ZPD驂(8/{aR1i& zw[h/ Ӱ׊\y[nwHq2QFV%CiS\CʂF)$niiA|q70TG IQqGBC :^YqV/$>YVXLрмy꭬/Z?>[ı1544fs06l@ܥ9kq Fu[;YTlZ #~[{R[ґBxgu* v@1>znCK0Վf LG39ӃB YBy?HmV#CW禹B)Cg&knG L(fϦ m`nܠ&Ϲ I9㚶q.^3`~(6tJltu7mw(8ikq^ܡ95$4g 5yщ8­Ӻ{iyQ ?AY"x:N9B+(i荇!>f%- <3Or |BunSȌlXl>ɴ~} ܦ2z7kM <57_򤍹N8,GX|{$MҶhArGZfwBiPۥrxcο?̶sl1o;MZK[f>d74ܷf9vqx8foor(F;Fh dm jR]OFcT]$oˮ^x^,E2!{MԆ+.!Ζ;&G1t@MormM@9VҮmn㵸mr[NvqW";kmqNz7mM)HmxQCW&ܖ ׋.Qzˮ^xSJ8pF+wSt[frps6>=3yŎiTKeu<Fr\V7ssMuqis<6cH*KW}n @Dj涹[)X+э^,E׋.Qzˮ^G?v;Gx7QH"#;լ_ze,|t* QOoYW "#&'dv'|M'#Wgm7ɓѿq֛xk^>nf+-8A9w=\Y71E h{K kYV-$n߻qH Lf Q8|5c)#B@r;i MeÅrQTblcU;yQAptt1 9?X|y(ZMZƧbqRHDYru)҄q?ܭ +o%a+l9FMVoOH SG%ţMoe{ʊ#-QXiH,g9nJ61x;@rJ$(F9cL #ߟVFm=VJm=P򕀘0^*I61O+2^٠2+V6oW~l̯}ZٿR%7 ^">}g,(_okVڽ?(hH!1lNN:Q=ƅwmY'tO"8ͻw [R9tN2cpf #Y¥|;x~.\wA-Xu)PU}n2H=\0X`$ݱ߂vDN˿8=2 t (h Zt={}[A5Ē:ں 3ZTǺk%+o:F,05k,Um 'ֹI9B dۣv8.n_hۗɻS0ģ~W -ZD,42xW[[9 w F>ڂ+2V!*ϗ\'Sn Xܦ˩&}1Iw*$5^s[5#vogXfcO1Nђ1taDP^>-l.6xӧDXb%T^ B@ElZcPh=:}:wTlw=԰j㳊w]Jf/RK{s#擳nQe#)R[eE$Tcg{l켡p-譹mM::+oRm)Wg[su!(uzvuٶ68uztV ^8d^3W1z;^7a#g3~/[='2G Rxsh69dÃp)Bѷdp (,L~$eiPpW)dNLP|f/RguًԤʊ#?ǿmKYƽn%Vv"}DFt:e螺q)URXmm 'd! \Fy=v&7y G&nXɞm,VqmdyM=pѧQyt*1(D8rYlY6%[8iw{WEk}dӿyi6ppǖH$Nnxxr䚀PT?t5qﱿ9VM Ƣ-FJ/MM#GRR ȖaH`t?HSY!0k\7}E5 #-Zjjr.i+8?0Nt]S') +݆Iy3v:' 0S]K1ÎKyB;%ljVm8Y"FL9'~j+sSCkQW ܽcyi.;/qj-Ü>(EΓ;95f- '[FNr2urqݿ9~:)[{;{,c\׾ 2OUu4m4e7o̓DZ?rk_qи}iF箯!"ͧN[6>s=?t]ydyx[~GPNjgI3r5P:XaM[$M$P |VŮ'}<q+Z}V(5[˝:?gWLO*R\ZFc:㎽ZYNNc`.#SѹOPഹb7ϜV-iN gOW|jx gg:wQ< Dmqխitꊯ?qWc#ttyqWOpIEC8 _-xVqmc a~~|  kT?6 xGEnsYŏp5AUbLt4gs>,1Bǀku ZW\f⸊T^xwe:tmFst} aW%La']CLYE #ݚvI.fι݆PѤuy$98k7]c(y5|o=jlMg#^jaI_Hٓo\Yov|rgrhg^XߟpqN"vr];#xFR֐׽C5 H pTP3Q s(sQ `G #&d%'#SHfdIO5 :|ɲM,u2O]81!-6-sM Ɲxߎ6IMw7xi<*I-97!F"p ,>_o͓vH[xbڤ0<⭣X}e$/hYd2B5$GxSM${)2 QWǒYȣK5ye!=uw=ēk0J<=ХDwqG]X=O4s ^9[iR^QB[Z'v(7P̆E-1޷Kڬ^|n-v{H5s$ܤ5Yya'<ӌ[ٵ,>_j'шϸ@ Hxo=%U<[FX\I<.FyGW+[Lm\ʹӒ>SK{m/94-ɇ9wyY. ]8VUAc%ϙ=Z! oPf.5XOI{$^3$4#Ƒԗv dqmbO4p3EFh&eLl 9)w\>z3#q8>:ؔ.%#IaYia~wX| to q Eo)̊[z|qQQqË}m̕n`>-A&nY ơXxGq\9Y Evhgn_mk$1ÇMGign mxH Z+K,p1/Ccc%\-:?})#i htkP{V P)֢{Vrķ2v_iR3PG)bxtӿv+paScvYCmI/:A!Vr5TODXި=%4[B]gjXZuR/;tc]z4.tD0ul6@w [|V( V4QwsK&H]fL93OWKķPj ̼S,PIcL6nNp?$U,@ I>CEKkvg"'HU̗-o%[ŒzIh,d1,rG>pS{,,*1|ԫ1Iau'BH,:_< O ңttw>9j-/|_jq LژnF(EhGMwq8֒ zw9;phާrI<:O[=J"A=J;*0fiH<r0ҞEӬfQ10ѫ9("3 qG$uj,Cv7V9rXßX}ƨAOњYieҤ^ZH#*ufx-mضVQ.7ָ83褸y1Q\.~/5گWgƿU_xjk?^5گWgƿU_xjk?^5گWgƿU_xjk?^5گWgƿU_xjk?^5گWgƿU_xjk?^5گWgƿU_xjk?^5گWgƿU_xjk?^5گWgƿU_xjk?^5گWgƿU_kjӜ<>:gk{5v;^qf<k{5v;^qf׳\Gkٮ#k{5v;^qf׳\Gkٮ#%\Gkٮ#k{5v;^qf׳\Gkٮ#k{5v;^qf׳\Gkٮ#k{5v;^qf׳\Gkٮ#k{5v;^qf׳\Gkٮ#k{5v;^qf׳\Gkٮ#k{5v;^qf׳\Gkٮ#k{4[}(<:Wk{5v,Ϊ\Gkٮ#҂ pMYA<?#k{5v,̪If׳\Gkٮ#k{5v;^qf׳\Gkٮ#R\_\Gkٮ#k{5eQ_ٮ#iAusٮ#k{5v 28|?\Gkٮ#k{5vSW|EwTRHn@ln@i)aidwfqKQT[uS:c8>Vl zHǺMq08\;t.NǡgC(o(}Iy4 mV7W,м@Hɜ;1ĺarCDe֬ +P ;ug_N^ydY]}'G2&xX\, cTm jUpNm㕢w3ک̽zO }ZIZ2;[W{T-Z)cde Q 60u~sWX6+mof:2qơ ߅$);QjHK$}̐6SmV$_')U"VIc;R0aZ.4SQ86,]19$$[P7#u&u); WyiLB.V؝7kvT.\ykDH) HᾳZldՌVR1V&1iv=ZvVVHa 3kb4qw+ ʖqE:IoMo>Ǜ' q OsmA`4a`Q G⁾ !Yi8io.wRRCw|}uCM4$'Y',N#ஒ6Gڬ0} Q| ėFھrP WI"\#g*u$ߺeT"2w$b}ШgH;>|Z׺\ͻ+g~mqmӓVE}\(tw`6cE8=5$Vtu1kw"Ew2Uֺiᶊ%k#]YӻycU۔2S&12$1/(<9`HNx*;h$>SWu8tm4{y(1  xT jWӤC&T~6(u}`R:bCшbt/9}2ewތjd&#ye߻dy%"y61bS222xƴ,e =2 6YJn'71T&h)>3P%ØE+4XF?M@[<B (Co,zIV]ӷy Q=HϬ® t#C^&H=AFGKB:cMzMSݴj]بg/}tX&Etd[9RDrԳ맔43L#ihsy8FH$gaW]cHuǮ{x`ٍϒK'R8 oaKc]wy54F$ }2kH#ﲹ= 3 r.]5oB=RFeuIІ9ffW-UZ+?}lee` 7o{tkVGWEZfؒrs5l&2pxպK2If߼jvqtʱa4.aC*:5'H[ato\G >ѠW]QxX>c|HsdKs+&dfL7tP`&7!u._ֶ ^.Ͽ$I{>"fM*}FFkuІtgUL3u;˓Lۃs/|A7Ċ[eE\,fQ1:h՜I4q G$LOQ=E k} Z2ܟ*ȉTƻiL<= {/x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oi<=x{Oh.deи*by:hZ 71uvmw-^T[} ez:w|q.IQW-RaIՋH\8MraZRb|e2fϮ&s~/Gmwq8q ieT)Z֮/«rO3* 5i}j{θ1$8zxT,gu͓LyXϨWzJ+g]b]c5dm'b֭Fsj}<@4(`H=%N<λFV(iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiol8{J O%Tvz"zZ9% A#A}<,Nx=k7.G/D 6-:j@)R 8UwȪ u]:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӧN:tӊϠX6Y"f+6pG +1`_~♶7wٯιO+[Vٵ /{E;ZS4=Ih_il0&9kZz˨`nc +v$vX T*qq-fna/ .ßx?0eJz/7Sw<#fԾj]xBş1WQf~vR@cSK-gK쒮椌r{^p.YaD%dem"BHT) Kr‰:PUQA˧Y"TJs_FlyK47~n$eK`eQ%:\i.M*eCZF Y1v\}cˬREQ#*[*iX-4ȇ"덅m"8 DEنT.3&~y)}iGIAmvYfwLKL&dC_XI3[̀TaKƟ_|62O}!D) QnD?" .bGYL<8v)x; r%V(D62^lXX1ldaHU$/[hE@aÂ(7-k*_~1:Uw^B[!"^Q^EF˹XP)F{3-Tejos@KL ֎ Ç?q>rRO-S>gNrDdjxg*<}*diKѷ]f: wc/ xcJ}XU\$!q>Mq]@K@нDhRwm`uݳ\C {;Ι*_|:qoK Q꘹(7kz}Z-I'VeFz\Eq3ݫ3 1ѝ #];Dħĸ} p{^}7yhl3*pXx| ĥ+7ξ&اQT:J:*۵yLz֗=V˫/՝p>}S0͍U]F]*;K-淙?LVzw`:F]e?Twp:34y WF:GN&6ʹW}S-*jGzcR _| GL{f/U ƽ& xVomU~M}gY7~M}gY77#jcZ( 7~Mڤ/k?&lkEU[T`1?'ݟg}>v~O?'ݟg}>v~O?'ݟg}>v~O?'ݟg}>v~O. 䳆*";M6D<Xyb_`7CS}b"fæjnDJ-3P&Q@x*Sh7X}='5ظ`p udRrg]aN{JR І%a;6 .0Boj\ K7/iaUFFhՠ \E|@,>vq Kuf[9YVd>E-$i)E֧β!\J<=z&XbVHn vlxT1Sc~ҝ^5.Qhf ?W]U(=TwE$TÔ˿<0mҏ~KV;V/:)RNB0Y.̠Ae`-K"W]J ehN~{̺@Ζȴ"CCoyC cyȵa=$V( %+/ak OtR FTۍMj -pPtf7=^FQnX~!4K]26@ݝH]C#-a˦IIIIIIIIIIIIIIIIIIIII:J^)2ޥvNa")# ;]9ƎAP0 AI @-!^$P"&>׾R?'C)x<1qMZu*,g#=/fSkZ!","n#?yvh^ډ^"8/ɘg6USN?H ժfeYO4JI H;ݼ0+Crٯ"W.h 6LJT"n.̰ |]nT? |VyGfQt}MvQށG۬rO'cANpNM=T5a+LJ.xg߼#jy74Z>4YSGAP,zK .l%HnvnX ()f-j+ tvFA)byqT9@gRՒ5ttμ!_K}Ȭ4נp(†׼V:J4g@U`7"*M|H -W=%`7i{(ܪJ5TnD=g(t:` mu2!IE^C Ku_Hi?b.|F&'a pXaG4_)H81}z6mtxd71i,;=sR!b'(b-#!q!`u+`?l(nQ@2IrC8L7#X4:`iKV{LJ54_&rq;~DP8<2 9 yZ%wWZqT7{*dz~Dm 4"H 4.g]6 zjX.-r$n pxB]ZvNB-W:C}3W1Z"7VUt~ ExƘGALy ey4n<8n9V)VhwM֨dxeCt3!#v,5 v!K PvkɕyMC溔 )ܑt\^a9 Q2S ")jN0 jVK`a>!yA$r")hەVc^~!lͱzsm@Z֟oHG'ި1ut2FHG'UO)71PQhoQtpk #J,2 aX*qPyJf .;3 cXY5= #%fQƞсw KvQZx1:)G/AI Z_bj- "HYTq`anŒd5*@4XX`(eL]=*c UMm!D͙a}a2%g5P6ᧈ^b7V%Q螘ԢU+dg{uau(-xzIn]CB#5k57rMۮW-۫(a@cuTq\M#*j(]B3̅(kfǡhp>78@~4hѣGz\kdzѣF4hѣqX).,sB4hѣF4hX $OxڻoT*VG(ƢQ$ mW"%P)oY|)96ETqx3$ rrQݱ'_ha$]@հoDQ:Aޱ11 Qdf+BݓtǕSqƋk\NK<ؗ-5y^cBvgmkPle\Oˬ0wh/.U~ɫ3S}VzT>aeJf//ՁM βL,62^dPN=ðǘn뜺>y@4Pm{@URula#ZvwݜA>V 2DBaW]?;e{eb.5q/nԬE8DQ6qKګJR,KO2= yih :q*T4ZU썕e=ɇY$ew`2zL]_ ElgjyN @XknF{J{n>e9D=@h *-!Vm/f36yVDͷjPp*RZNf 81?ZAx8LHS9f p%nZ:_JQ|78I%`ZVG,Pzyh?b0EK;LB3Z}c&\.bۗT9m ]CjRNU^m> 7 ]pi+yz_h# զMMݽEHgi5NeќBUX:h`Y1hpM& i-[ ]$fƒrS˔xn ̦#HWgEy>X Jb^=Z [M7:zPK$3|8kr=&4^ѮXZ;̇ D<]'o[S42zA#l,+sgX-͑J-¾9EH;+xSPW(ҊKW֍ٰcՌcgbWf.kߎ@y)'YmzƥK%`evU<9MJH0(hh_(#)tEG7s,<7k|7 {1e4p,g1ҠrUBZPTBEb-U^h6ݺ&ڀCL+tmu}"op_N7vźC asjo'> q\CP6#q\vEٓt)$mz 4JSy1N&a 啖ފ^44=*QgT:hz.j] CTF@9YP^w٭n{, P. вLM* D;[y vE V'@4ZlY f X]W1gXJZ3 UTMLKۡȫP5d¿zZ83EM~?+6lV;3Aj n aY9^q"eVh[) 8uPtdq]Qm\K:Ȧ>Q%>( FXho:q%nza7Yz\1/6ƒerzu{;@qF͛6lٳf͛:<6lٳf͛6l9%x6ٳf͛6lٳcZ#@mB]ekXlk~.!7˴8!歷=ZӐ/r?kЋjӍKrX p+.kɻKЈ(k/JpUWLڜ)Ns2W aDu0 Le ϊol  e7X\4nd4G]FB#΅]Lp_toZWG5N Xm!9RV.VZq'f pɏa{s&.(pvVe`2Ӗ;ˡK1 \=w13Rfh"(tȫո2#§lٵΈqD/h\Rvdĥ@= VNIo $C]Jc0ݭűf@ f9zDG,.ˀW5@(ebU 6^h7#Mi7ʙ1SNK{)lԉ"UK8m]%x(AB&(eM览*͛c!@ 4u,ZNU.daۻWVqGdTˀ%я\_kGY5ID<R5vFm?C>+0l Lɧ0S58m^xO4,֚">@ZcЍ^FV7]x`rwLup7B!|C 8XX1F&ЧE&pNA'@{;WS/z-; 8:lE s ˜9j'*@SQI^E:j?vzH` RJ*TPoJGL(Œ~Z*TRJ*T` mJ*TRJg"8û5_ʂq/ZSEWcE԰Uv|~*-;_XqzD|AצNOvHؙ' 'B- /miĭsZoVGŇyw ɰIaBaxWr®-VBsBT Z3l5X]V-ژS&Hp \#6P7p˽) 4 ES{x!Hf86NWU: )tHGF3A+=5%iȩ*k6NiMx!HBv39R~fQ톋~s::R[Qv5{.H9WLe. H u3(.?/׋ Y.nֽjE__RJF sBRL @b]HjiFg ΋y>ʔ uV#Ƭ7_xvn ?BJV7=k"pm0,jM D @*OTrv`g[q17 8/LW \Ҁ|U*"rR(^e[gԘ>+c9(p  JgM#p % ru "ZyEZ8hB-_h /\DΠZ)<Œ$g{ݹ{ TKm^6tlx!i)l46-;R]| }gcorߕNK]V»Q3(|G3e:Y;id0 SV x_UGe!^eʪҏqէ8?ԣsEl0J==PMa\iKx1l9n"0[Q1T,.Rի{#roeZv"v N  BZyk2oe".OL7^Q5s)G?t'SQG~m2ad U*\Q@H^{qlVeL?!V.z<4gkiwLW;^6lyx4ٳf͛6lٳcZ#ZeOlBƫ<@ݼA k|%v{dt%Α{L֥1Y#ʄQ}jh}K[ uhd WU~9oEkNWL]v}!PSQ,`tPJ%-Qw{!kӴ_@Wth3 k- 77cp'-ŕ+ )Ee zt;.f. m̹2ɊP"2k}Jy?Kfha>`w]1.2QS$j .d w&{Xz`. d*4t#*N|8} ,Oa#qsqgQX?C1}6dyZF;O{ $2G#ݏfErB[7&"8%{`_r;1[ViD6\i\_4dj m@/G#QB`..7sfhѱ'KCp}ڙąy+(f}W̸@:Z7atgu[TwY[2AeU:vWF1Hk=(c_mgcg m"C!֩Rҍ lhҰ'PĩamrX?9rl ; [a_1:QcqQ0b,\ ]/rEsM՞i|eʪzKg r\m9zV {DMq!AAUAΊuʨ^ ]y\[w%+ks2F7j{ S)|E"gCӗĔRF̹>OmEV!צ :)PhOjGh<9k`t]-˥j8 o\r:1`4՘q؉قSgk|Tކ2Vްek.乀 H_ aE;tc&0"ٿ|*˦.,oL:/|v"^J>NllA}L*X;@:ʚcsEJm)Ln"w0 ٙ-x*@ăHUKhƉw `THdUsв``OOl[s` *-!V4)k;cL)yi *ΎȏhXT@\AAXUa`L7S>Th 6i@GUjv7/@v)unV 8t=-I}e=0,0 =/®2k˪{jq15G aYDP[}?ClUZk^l>c)=F1JƮvF^tG<[,rՖiO;qPMN]#m0&W$!t{r5 7/7\$g_mGAHMY8jU士arXmc4Af-kb ErXhI[Ax0^/F{AikjQa iHilpbG`f-i;SٌoOȾ/)3 [R0yN%2UFB7+pbfU{,P (D@術+7=yڛo%Gl`0G_Yw8TsCġb])wH1w:Ѫ&+5>6:W@[ l ~Q;[gjԷ3Tq?A|X]TA8g1J'42޳ ,F 脪hB HoKT(7rbŌ@+Y'@5 nwnQ]&b '#}e)ՒF:dY)K3OCKMJQ^_-IDđlKK[zj!|Va \V:J4=Kb UFģ=Jw{fau"|lr`_ɘ(Ϟ~˵@epzZٮ.vYg"u$y!9yM3n1.Q ΢1U2Kn@6˅2z %O2f;U1m ?mbW0ᚡnluה>fثXvo] m)wZ`KX6:hEϙP'%b=gg%2 A|l:*QyHicdPmxz/gx6{A"!VӷxS&5>qsh&ߵIZ uZw&բ '(QSH#Q}TW5wz/^_ U8D\/U%O[xTC*,ê*csIŔ12k% X%\W U[ǞV]*#t Eֽ5 ?du/VUZϯL?!mSl{1c=f7l=+ajWYJV0?5,<@1%J6.J)L)^q)7~nױ$ѭ?4YSTGS3rUwJ(pf#c݋jsCOefxPYj+b,j HA;T~-q/r.j%pE\: z^`컗gjkP}q+R[)eՎ3hй٨&3Ps[:dvYxs(|-o cclsM' v,o'KiLbzJsCIl8B4 sni8 v 8 ˫+2R26U3s6~%oQ"iuRQTƲ.9TxAcsSS@+pH",\7d<%x"6.[njEk*CŲ%U[K;ȼ3;E[TKHxbFrҜu# m%#+ Vr&:G~.@Yp-M#T)K$=$I,D0I$I$I$I$n:O$I$I$I$I$I$I$I$I$I$I$I$I$JD XR7}?BI%V$QhމI$P@?I$I$I$6eM*L$I?C@>$jQE-@lI$KOD$I)U/}'(F^BW ᱸ ̯\̂yZa8>c"Ă y%g3s}yiΎ!pśg+J2]ڧXt'uc +1\@6lM}]d /$ 6>2XJV^w A"`ާ0[sNgF ! MPw?+:E2ޭ`S¼pK6MƳpZ[ZlXee|BxJ԰\d_G$•ELbٚe´9fJo3 4gu=]esZtp]gB\>4r !b% /vyZ/fE6J9QK#{KBЮfp|8%F )1pS畇BJ`[2 iX _1#7jp"{ h v"ͮZtF r@xg>ԇ! NWzċy*j gFxjtebܚ`QZօxDWU9vT3ejN ڹ<U0~sm|3IgG{R!J;`w:O6EiDy Z?<;Wt}W{-e~|DQ!zM )FTXPx_țEXTR6b@YNjڵ o egR`eFwAj6Yn=+j )y eabʕvz .\r˗.\r˗.\r˗.\r˗.\r˗.\r˗.@PTQ:LY-65utE.8x<N&`3R)U1U[YYq *vﱪYKudV}[ oK>u7}]tJ4aVvAb)]`UnwI+a:3ua^n %A$trh!lVZvT|LMD7g- n)N(TVrcZLK[] n(NaxHѪ)|Q/hS}]`8SpU,H\T.x R w)V#P{xM1sBF^ƺ%plĺKѰ.Dֺ 0,<<<<<<<<<<<<<8< 0 0 0 0 0 0 0 0 0 0 0 05n0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4M<4K4<4M<4<28<Ҏ$B0 ,=O =<3<0[4<4?;=  #ZK.<2p<<,1 Ӊ1?0$5,2C`4!_,p p,s(pJ0C0P?-?}0RD_NO\L s5+84Î2ӌ7qIϿA$,M,<<<<<<<<< <<<<<<<B0 1A40 q?< 8Tr!:R)G:Q` ݦ=޵?܈ܝ1 ϟ<z cP*X 2Dqc?q)[lk+CbASj02!Jt-kq­$[˸]wyPTI6TEYbn Ny:>ghÊRڣ!Z'It"x`Y1gČ 2p 1jb'4[p%lcdDN""z_5"NLp}VAYdVAYdVAYdVAYdVAYdVAYdVAYdVAYdVANlylylylylylylylylylylylylylx!21t~(|C]Xl:M<~DH Aߙ['ڷrGSny W`>IwІfƛ\E;V 7lii6EO<-ŧ3L*D Ù>HzMt~5& bD0Ku$Ɩ #w3z@jz;}& @ 8n(ʁ}tIsX4g{ij uՓa-tMR@gCQNi芃".]ΡH=k V$L4g{ĵyo(otOI~?\9c񦂎4!#ھ= 0p}lxH&dDsbN0&tHm.tp?↔NixM#**$X1M(,ܐ7d:rCAӹ[sOsa >gm^1}+rX_]Iw .zorشg{X(4nEn}-&,BDF?Iwx΢(Gi6"-Ҝ4c߱i6 neZPbm-ަZi)!Z@p9>W{KBMC jA 1-@ "@-LIl ND!P5uuDkVīRT?`/0Wz!Ρ~LIl)H9&,2$ Ch(7[( ` )ir[tkCÄ/,mc,u yxIl~Bu!{j<,MG @a҆*.v-[m9x E Ʒy`]n>a-Ӝ&쁏( ""Ĝ~J?ሙRAC ́@ JL88 9%`vMC:٣W@색OXGM7Yb!иI\Y1'=G_5Ĝ~d #qV/z,P(z (P BF :I9$hFqKQ]oCܳ ` xu118ADL _ˉ9:mzX٣T*uX٣0]Dʼn9:CSA ]Y s4ug=h5sAlX٣΀ ]t8s4uGpʆ 5ر'=G_T#+ ʼn9 ڴu€Orʼn9HAb {V53[G2@ nCI]ʼn9Oz-mVG9 bT0'j'8`)Pَ;VG9Dֳ =p3 =p3 =p3 =p3 =p3 =p3 =p3 =p3 f ?,!1AaqQ P`0@p?(ҹ(ɂ* `E@oA.@22"A]GM=z~ 3gO=?z~ 3gO=?z~ 3gO=?z~ 3gO=?z~ 3gO=?z~ 3gO=?z~ 3gO=?z~ 3gO=?z~ 3gO=?z~ 3gO=?z~ 3gO=?z~ 3gO=?z~ 3gO=?z~ 3gO=?z~ 3bkt/jGEZdr 8:aḰ@V\b8|_\$ b($,E g "Z8˄q4vTܐL5ZB88k expl=tUn -\\9#g;Υz kqW@w~D<&fF{?F8cݵzn~g?11^ bبŀ8E0,0҉Az@* &{+{q>Q'T6-b.Gk0BtVa.lJԐH2F]6vi1.`B@!D4J|>B 8MP_LnQb[[T:sF7Yn/(` ˏlpfO%4M3Iv+A{r#KXK7޲ڠbCVLo'"t+1b"$b)͡M#=t8Z1Bv`PJ\ҳImV*h9q*8iE4 mZ x&G J񴫵) u &oq|H؊;pK9&{ȑoOe F9QN}H>B$lE| HvyNTD#D\DV kv|mw7dG2ƴkXU6y4}H&A J(\U2m8K(œe%2?ѕ*TRJ|B b5;HEUtTW"*K^Ls mT" D'(r,іIEے'd2|> O'd2|> O'd2|> O'd2|> uLقnLFҗ40&ߋyˀ%9o*]E9Ev@K|p`PX1Sp&ݟ rqK:;lLznS'ub.,Ntg"̬uP.ā)(8 H*6)Yt`#iP`.kJ;ن *RW> ŠAA}ÃB"fiܻ6AUhiɮh\yqZd}NBkaK#opԫj1;Q:Dt m:]V%=y(Zm"w:_^%!at~\luI[^B#T0;]a&]]<"%&*{*(X|1%_k:4?*b9@,]#\ l7"0>p0=ZBŽV+Gdaec֖/dHLLK! Y'c/0(HQMdMܬJCЧHH>^1xs F)( j18xցD*3\͕QD&l. I!`\HGPEdC]DYĐUPHD&y3ɞLg<&y3ɞLg<&y3ɞLbL< $9lI$/2k$?{TUE1p]j HD] Sl_oʒ3s^@Ȏ̧16(NwͿpy pK/ʁr@r  C, @p!dvYVo'Wχ&~)*y.5ZTA:F^]Q5BgJKu7r >aY-Gn?ZGtn\ .y-V@UbaWeUa76C4L*X<TnEK i~q\H6\^)BZ 2l$L9+ Vݻs=!1C)LHEGO@{ɱ ؤì cD(3a3^58Մ: N7}?Г#yѿO|,hӌ8AT7+u rM:d8m+$->rA\G^ dWYJ +ڀkH\taa X\5Ej ]FK> -GbX#`n\kUia=~{ߞ=~{ߞ=~;W;xEgL$;]1 !gݣ,$JY&(uh D5Ƃ?C6a __uUIT)ҟv۷y b"uAa<PL2€@>?UoHtzNOcm۷ntRY ݼ06Y;UŝBJ%ѧ5@@@u?'#K;p +뮺뮺뮺뮺뮺뮺뮺뮿b̹8N?qS8N?qS8N?qS8N?qSYۗϷ瑌!^?~؈meu@e,?.RV$e.2dðRF063 *!n hѸSQKPA@HP%@aTg `.Qf6u=,@`u8 @@f KC3:?* CR2d Jjnwtl hnI+FIf Ua(R[}xd(Vh>:)HL>%cAyL $o:vm 7۠8DաZ6H T %fE! P˄HxP٦ZɄs@Y_&I"sGVUN]ZG"emk5]ADXb"bY%3sTQ,Ȱ~_H"Pp;_niŜcD:"N ٍ*bNb(wdD w7:kYcb:ˡBIQus:͢{(R5 QA]ҁGpE-pm,5b6b4!Cr1h  MҥF-FWH 7ΎLPW7PWu=Fe4:L@ۡK. 8:%aM\p ) fa1+m4"MNMބhb3 ]\]o Ȉ1twq*q"&8ҼHdV lZ4L@w, updk\ՅT8N1  u71 Zr$$u  1 P]`#iȥ,0^\dE@HnO!%}!F.kF^rmx 5L!swB!iN1'"#7@?{r "1غp&;!۬(vN ; h1dzhF Wj*† (˂8+'5Ex5ȥ8,QEF1dRQSQ{EtK ^t .֯<&?W#wR&6Mspst`fM,YNL@D(@l@9Ȋ XK2/pj$4t܇xؔN"6C h!H,]PǕWL-ȈXn /ho|R DI+Q@K%MT`w 0Z 8J $U!El1ԇweJlP1}Ȍ ,>,))bw1ܲCB 66AM41ЛpN瞯6O'=܁~Z!Rb~ⷨma;R(:5T> T\TJ/]ad)ʺYG:km0jl Jݢ&nEBTym_;c`RB" D6n,٩R/_DVSnײΔQdѺH|%!DK04N,0wtQGbR>V;AҎ*RbK@CDA7"SM;aLW1ۊumP*ɫJhl^m3IE|H@ ,vczc.ڪ%Vh & D ĕ66[b5ըGɶ+T叢#r|uVbi6'pd[bѦra!BvN%ǀT3k#`!K kF&mE%o,Y౤wJ-M88ȳTe&*ƻ0!% ^Mv+ƨ~N,NGrWMr<24UEw{`CjJ4g-]ojef\iN\1. s#z0UgѨ3%ꜚD4 9׀7:@~&L1n;w?2dɓ&L2dł[#D3_N4h(Aɓ&L2dɓ.ӍHBZd (t4PQ]!DIT6w)vs;PM,:;s@@Dُ(!-UvaBs-?-{ۦaR`Zy2IJڞq剜g\A)*فn`\) d=Pw3cKh,֜{Rȃca:RRxzuEfb Jƕ5v%KPH6lLg ~|_Є]3C}c qz`S&|kޡjOVh8{KjU$8sgnxJ6|\<ƽ oΩk(ü7G[֠g 8 J%ȮҔTj WMu'QHn BjrdPP܎\ GA"]ׯ.&M ;jI:Q !X຋ZG=V!kWxErh)8nX 8T{8Mk\/ ԕg/U #L 0^\L wTL!7rNDE3r4aIQ7ryZ(5( YPKW=چeQ(pL M[y> !b _iYyqXȖ衠Yo>)ͣ6ecT"TB!GH!0xn7a97PgQDÐ^{?Zk 'D3N eѳ` p ^1U8HX\ )V<A)k8ȈX9p"1M`C0@a`W=1 [CilCxАH D4I9lS$ɴzI@X @9e9voICuӏ@k[/u;'?>i P%d)]⽰[b H1%c!UH~1F>мQQv׉r/-Nic_bG @Ýegz%:b.Æsw=-H IvHx5.{%QjcK P#QSebFH@H62 JmWDgXHx77מ`#GGux0JyTLu x W}+Z/$̑IM !Sl ȳ=T;mGY P`.]am5F& \dU"6^iS+H)kchcB;nsv`7aeZ@")REVM\Aax MCfnU d ML~rmtmEN))(Bxrt\X YuO3Ȍ2 -Gw>6t2ᾝsۖV P5* Hc;,TLg-^`pֺ-t:D"3dXNhr˨"T4B.[$@lR'޾ AAO|ꮷ3?@jw1bX0AU*k!̀@" Y $P 7`CXr?I#PQweFM:f#N'MŭX["5؀z|ܲ1`M6)aPP"[U]i!ʨwkҢwp"#Јt~KP;z {s->G09&ψ&'D,8zV(n)XNtr EG_r$&1fbt4ґ(^mv;I, ]j9:PhD;^Ah܀oCԁXY_$l{'z(5&ӅUr@TNZP', HSگKK6lٳf͛6lV`Uns_ճf͛6lٳf͒JZҵnklٳf͛6l鞗80 CWQOOp P {I,z<&pNO_0m,TK:k+ iKpa.Y6?T4 C\w!gtG9]E%p"@!ҽM4!PBl^I` ^#]"-!¼"ޑTlC%{ ZybwA跆{2(9`YEН*q"Z`ӃJO+5pFj*X"9kQ2tZ H]CrqkƓM02U+pʨ6Èx >>(3$zuggϿ61|롆lt6 :C02+DFR\aE41ÃnrH([(G7 6;|$zoflhUbM(Qbґ4-:C@Hkv<Q``5! zhl $NQ7tkԘ6l ƅQ&UAP8⪵Z6f(}j Hj4vC~H!0gExSDT 9䚡xS/TJR{Yd$ > X$m0tm9Ŧ(ҸᨗrR(:7\t;ej  'mD*jM?P@FE*ʠ?V"tW 'v1g^߇%hS.x'\xƽ? KA c^1jU,BDDvXwiDtnj.:<[,Q i9 w-:}0$N.Qpi* #uA0WۑSƍ0sxad> Y!DqʴZ4=3j{{U{U~A{+Z]~ц33]gf z>?Ӟ*qUJ4r&a'k -N41'oltv 0ȿ~P5ďÃpxxNVMe u>Lp> xHz*bui _;ny8kW!`^oIv8B0a ǃM׉06IWI ;j0[O8}i!CKLO+ -Spf1(g EӨ;S$u90(Һ` >ȍ+,yU,*CB޵,AAE%Ο X`XdC bgH7E4 34^1ׂ7a ā^3xD~|aHs"@XT1Mf˥VBlO_9~|g>?ȱ:w^sҒK u tʿPf\*ѫM"iQeΪkCIV65<*2 *hՁM!,sVf^J$R#'cnANAgۥw@ Tr<"dojUVS6k``iꉓE(Ym`h w{ 205'z suލ"{3$> C* ?j؉q&fx⊧s»5t󌴺_;!~OaFZ k BtIIn(,kXIJ-,fmKӕӜ4 cͪʯ7 N/{qBՇpr!i&͛$!u  4+WT [ vF=pjy a\b~xѩUP}]{w 7׎^7T['RL͝+@G~S=Vz3ў>ӯ/婊/B$ߧ)2Sz[6SBbmIiK>TU`߄m Oz[ ֕F/ [.H(7Hn8D~9n`}@(ZmFYj?*_7K+Y6I4~?8JȚa>sc_J,5~"v~)9ɮ~`}38qȒ)+L enlDDs<_ C%} |쟞AU^D}L\@r8ޮ$ ' ؈|JeQWu@؈"aE Z 6nk&F4\}"=cz_IVg ]ׇ~\oY9%mhb-V )`w49LtrYSwwWE6B"~SUlhUFKQ% o-51}L s`AC-I0+2e*I!QDi`>׎6h^Ϝi9SHDF=SN7˵pi7IR8g*.򙷀O'Xkg"(??'fxƵn)n U8"e8DTy$S'i6 װ[GleQ-tFQ%gB]qByeChjnC)jF4r&h 68ZD)q'TRJX*: W!8k) 5G,#pN731QN?mm:`Qcѿg?:(7Nu3JM/PьqaNa{d}-1J Lv@![ȌSK}hK I/=[j7+ЁS Aꖓe-c;k)wCbtM t.M/ p=߫EX9'EAIMjIJTe͗Ժ!YҖ:+! ʠ.NWZi< [PCF㿱,0 ]+Q4c,jT&A*䣬TF+H3LՖ4/UF_63d_~\֝=fqEAWg %|u 4/F/6$GMI&GVd(kX@hV_͢٠=Bp91Y.bବW2b'!"t"n࿓D!DyȘ2xh0 y4yP*@\2HiR] 8{( CPluEzA8&LD:[DM\K3e+87l4FO8AΝU9;fʄJcxBp ER* xh%l/@BC P L 蘰Q |Q`/8)EWGo_y_kA/8J lQ4##-1>"NOZQ h\#8E bʡcut  W<#4㓃 (X@&HFL62`-e(z;BmL (-:*k!%%4j4<ȃA%uBHNphSTu$*m;LSx6!kkg_/04C/_l ԣB$r6 Z3Z[U}{{۳<&gB!5bc`%а&TD:2PpS4R"4I{[C;iA;VVc8&N\G'i<扛*Bvn|pFI YZ)|*L.jd0%u)Urdc,B |?!Ti᪥iDv4xʃiH>YlߤDc=tR0wI $p 1S d9`WFbWB7 ٪}a]676oQi_@YO^:]@񉏦F EȘ{tmtpɎ{sѬhу-1rdy%k)UWP(.CSҵsnGf6gormP:k:@ޟ?ɘTUѨ8y;OAzzvO2k|Ck$7]uQa!?%y{YKԔZg#ɬ{EoxB9GegR`֖se5is~dѺH|%!DK04N,0wtQGbR>V;AҎ6Jp GPٕ+DZNMqGVbV<Ob"n/Bv%ǚ!eC#MWF.* K}6K!Q99PD0o-Nҧg^Mhd4S}{ 6?&a Г#4, ?epd[bѦra!BvN%ǀT3k#`!K kFen~8#dcH<A!.j) ,+[0P(5pXFHR9=mN'SCA&֧$ _AqhT|/dGLLT`7plՕ\q*KUU|I _2u2 %?7A~p޲X!$X f UM2avQ5u>i0׏ca+BaL1()4wu@ zaȀ&%P{4KMŗ4(e:2hm\x :hF;m[05_qyofOC5v-c`5 WTeɸ )7mN@m 4HZ};41bo vk( -P:!x&=!'Q,\q8 RsSq^`]=:J Lf3;OW)kz>UXɚH}YHV:AxbǟwG>YbɁY}1)?‘՞YS'3[*7/J&cp$Dխ@qRV!MA!04b)aP*qUR!1.x(h9NxB-FA@I#"8ò`%wxgR\. 4F#TW,_ >:L$dz5Th9H/5&YPCEi!d)J`U*4c rcQLs-ݤA$NqFb"y0 *Fh5J=b\tQAUѬ( :*%b= `B,qߺM`lJ(bTU7qNvt 쥨"`TYFQDɺHrՀ}T vf[&0 DL0;ɿ*v[m',ߌhElxL\X冾-anݷZ9W"`^SȱVx*)]c{m9I&e fpvAaeA.+7TH`@un.:I?Cm{'8aՙ[Qf,Ej&1PQPYCD jk<7|A-E H4ȕsQ?;Jd]Y=,?sFm)DN?888&@w݆'qqqqqqqqqqqqq>l]\ C89o`apقáq T.?K8|qqq~0w8W̥qD T=W|.;0Xt. 6h8hQ(G}?qϗӉmPkFl7Q%~ad!RMFe!/aD0NF1B)e d*y!~+:cL҈YYyH-ANL=#'(* ùtccQ! O7hjH2㕻Vg"x1%۰Wv~y-0 U,D&>^1x?D[^̡6OwRr& J*6yC@Ag5HH'V4akV4iPEd\8%dqQD7x -jA_fV}٧Eao&{sD*Cf =n<j\'DcH&s#EzA$a2R-q|\ܫ ZemE,Gn#[9I{p1 Q8wY.F !W]= ʨ@]1 ~k[W7#PlqDkan$XTB`B)(;M'[o%&5 !%⌔$HaQB*]k '\*-7("hF8!L/@D:-`YyAn9JPJRuƣlg4hѣF4hѣF4hѣF4hѣF4hѣF4hѣF k5xbM g&E0;j :ĂNCO4^8GanlA+x57!Jps݀Ӥw)SH6 \"2&1]"Ax3t](TP\^u+F.v b+ Q*|Eg7e%ilAUP}um$;9S`[ВYMķ4+xpՎFKM{6d{@:n n7~wB&pPh 7($8{WK&ȁ2 ]!٩1 e*ibKa <x[#Y>m*!#(L.@<5FbCXjo͝B7,33d BF億-9T`Fk_uQ-;Ѵ7bKa <Jk+J↻8 C@Ԝqs endstream endobj 79 0 obj <> stream x  Om7x4e endstream endobj 80 0 obj 728 endobj 82 0 obj <> stream x{y|Sǽ,heIdy>`-o Vac0Kb /Ž%c $@I%"$ 4KK.BҴᶷ-%I?M#@n?=Y9s~[3rowŠaD}K귞_KamWfUCpogmG=׹ycYgɚʭv9xI=BGT\ _ ז:͞~J3[r> ]m[O}_xOx==T칷yoM?Y#ކO %PHer2&VhuzC11)9eFj`J7gdfe̙eg/SXT\R:l +lwUVU._oM GXsdj\[Px >FkZO$҇3YhRE?'B? !ZGOۼ˗-]ҰnQmMuU] +[ZRl˝=3+3ÜfiqJ\&%ͮ1׶ ee6h5(@UmBL Zv|-6rT;[1 f^{-BpB,/\x 7&!]B 5Zk;TTB E%33xB,̚ygŲa4l\\Sl2uJ+( Jž7~}sj0;5i4FkFK0\̹ᅩ38\]A>Sm>C@yOwִEk$+2 O&e97͑{mH~٬ ieO^zobOLj61QմF>18Aȝ /~3 υ jb6瘹:2oj(ڢ֜ɷBV`ͦaEsjuH +WuUA}+hft 5cY_Q g Qjat U fGG05!4'sIɬ3#oo_i=՘q.͔ $0iAP 'se9@l`fsEZSccfvu6ihִ2"aM~gOrՅ1r\ YHQMS9qrel~ Pcd4aB!d4}ףmɘSRayL~] ܏X13iFɰUerdbbjOa5Ą~m:Qo7i54fiN*I0:BrMs,G?خ)[j/ tS޲ι7׀dgf(D:RMŦ aħqŚB.2QSCA_Y~ֺ恦y/c,wjS涓#Tyo}Ċ␔#dlЉȐdL0~x;gɣ@3 QZdzR& 5(J V ؠ${$ id';62RSS`*5"Bgeܧm;w<[Y֗I)Z_ڣv->{ўyiO}RO _cҾ&z##<`8 1u;ux{ Nω/?g5NīĨR<@V1H*o'59 lc]4~ŸK_‚͂^'NĜGզ¢<ԬeK1n[e颁5dj{nȂкs_wuŁ.nPe&V{Bw 6f켕S~ŀAH{2sf H3OZQ@6)XYՄ-D 6^PVuւӧ=hr(&@8607FIhͤ+IƏdj˒+ (}@c< eE;#).6RRC^8v,6lHñY [Hȧ~R({%25^i2Uf<&nJ$~>Gq87s@} oj1WLIV/¦ (+ZHEbB2Qm` : 0"b G'2~&\[K(v&PA<}].+T`1| 4X]J?g~>W 'ߧ?ActґKەFw&xܒSS ʕ»srP:},%Ī벲3RJKcu^QE8[-`wF7F\SVfpj,m|-vlhQ_BL{F)e.ukZ1WV U<|yB-d7! .Lqs^ENw0#, 0h ([^``ᡡkϟ\kɁ>mcWG,Hsn)B ab OLwD,P IC=,&/\|8pJC=PF~i|fkkܤTVѢtB:-JN!(y; O&2m4zHTX(6NHf< :3-c"":HD 3DI4'a!ʟj2rOi2 >=tZtG.1B8.+/ίRoC:!-m%*l%,[s&e4adgt1 ߿1"RB +Cc>F DR2b't$'dBCg/@лPRT6GcqVnI.ѩu)d?̫u <w"T#8'|:NQ%/O&@"F+vE‘Et@ %dQ1< 欩h3 =- .\? /y^ҴMZvU0,l1 )\b <1L +m,Dz[Δ)CeQ=3 / 2y(ٌ)t-=FQDYJ@ @}殱um8Qlj~j /dm/w劇n#u/^A|A6 A7g<'e&9h? -99(+6c3y=,ef iMnfw=%ŦbPl ã}>VwNW]=x{uD#!a[ite,wkm鳡EviQM$q~-hxN.cīH|jl7Sb7\P~)] /dGhҧȅ"s.WJ$2 | k0yGzj< d[ֿc.Bi>@gf`DYk wZ@yhm{1it?JP'RL jiv@MMl"ʈ@ՙj3B8aŷPq!ݸp/2NceeKY9K=Ϧ6l`eV [f6,ۑ՟G2ٙJU]A$vԕO`j P@:r1Y_~ xI-jh[m&,r?l IEI钛/%#xLrݳzQ38G<-zl!XxT:N K[RNy 'q7D5!H0P _|GU&1xkĢFoEnG2S_.G3a+FTHjUѣ*IC^'>ܧ$V)b| 'I| _t4jTj7Nh̨`8l:D,L×_7\=^ձ_Wn撟0GvL)1Xg+AR<:J%"T&)NAGXe$=`1 4D<%k`f#(Ue#Q-X6=k[N_ !ax4(Ƶ{ a!5(X/l/[)Sȿn§D~^Kr[Y/މ`A&t rQ[x[d2Q6,I8?" "T˴>$lJ# #7M:v\3xԈsez-]]WI8ӟ'IQu~U2.J+ QD>[)QB]>ÓA3i#\'p tbf@2S/6.@s!1 gcϢ 9yieK۟~nca3{}!9C_KoY0'mAXFFj ^MvxHX9rR!LYE*n]q,)bYzԓx"7 d%ɱ~؀B1urxqMDZětqa|u8ygqꁥ_eoɫޅ "-Prb7~`LvOG#%'RpG"c&Qpߐo:G&h]b .Ǡ$wkut[MwYpe8єݱ_$jؚd:$KN\,͊,Mk޾7ۺ/nۺ楋~⁵O%|ֳN^P[wh_v8/.~58q)mJ2r8]B /}8p:('s[,!~K~ ޣ|B' 2(侌"|+Xf*0lvQGLL9{b>=t~Y<1.>]_* ڼu¿ v;= YkμMe^(Q1Hx_,׀7a|Sv/a9"6Zk!GѺ=& Ww#B?zÖbYFJPF}rA G6S(Hj P%;(#}GOLs|>a?|؊"%B0a Μ!qHC@6Mj.[GruJ}*c 8W' w["*%8o;bB"4Rm5E7zSE򧐎VττFl[ xP@.v{b2g#τ8Ȯ =mkYӾ3?]UK-%YZ2[2d# *UJi@#l.߱MPUlu%Dzl7"BߧB(~zP“7ïs&[!Q ! b,/;'F=,\bwĈWk;$C Ziq)||mఄ:l%*bWWJn] љ[1#q :Om1ʼnIGĠ)nؾ] >_)l\ܾn;s{e2杸m,oW~wganeޮ}EI%:b!2R$! c,Ub[cñqFAe<2'qr.F-ʉ2pIePC-KDVę-7R'+WlG~a\'W؎[MBOB5С#<&yd>)\#2lغyصl?DAY뎥Giw{$g%8?tر'+ _F܍˖ #e?^~pւMC}ħY8P cq9?نKh!_7AƌO2Aa=׻d怆tkpCC] x{tK1_L!eLXI]WcNkpxS*Mv/8Z@ c N&=Q|) ZM),N\ҝq4#ӵ,ڿAK IrMP7L{.p,,Ɓ%jk-4"o_|1ڷpeG?s]=㺹s4YsrM7oV6.5>=64we;瞅9=y(Z} %WW; ^T(9$+n,UL퉪/G;H?!G%ϓס ;Kq&~?ON:4> ㏡ :Wн/Q8m^ɛP1kkpU9˄FI[8~(o+Mڸ7~ kR*$dȫ HR)(KŤTc5Ǿ+^դ:! qRH3y[k~Git ]iBX?lh0<+J5BŽ Z6ģ?N~δ` sehE/y@ e o07h97ap M2 r>Z&P~;Z(-sPI##Ѳhb,GkhvytG ~0'?t UnڼYvwz{ngt-jmٷ;z'yyeѪKXuv:OW/^Srۅn7wss_g_PteSp6wW)r ^r:/Qz`q97{Kr9 endstream endobj 83 0 obj 11349 endobj 84 0 obj <> endobj 85 0 obj <> stream x]͎0<b6xF"g"e5 H ,m.}W6?R|܆ɻ]ғfʊ8/v.j2?M7ç:wakcwϧinaX2[.\bBg=_qʿc 賡J;v>5mUY;N9_ڟ&F˲ud!;Uv`Gރ+f*p+ך!kWe)-xˌZguY}MLI~8k7io -24X)ӿF Ά>kK# =܄JGI]gB-8_+x KAvϖcMn66u_'V_)_JYkҿ~-lSw?2V^3wΏ֡>t~KII3@\Q|C\xC7o4Nߨ endstream endobj 86 0 obj <> endobj 87 0 obj <> stream x8{|[yܫ+O=lrW~9~&N,ے-yȖEd-%Vj[%'8MIQ#,uP`]54mBJVM W`Qִq Hw+{;{9GMMQ&E4N#Bevtoiz DYvEvOTB\]TPFBYƂoʔ#d 2ǀا/* 𒱉mTG Q5S[No(ƀN'?ppL[$з^!㑩`ĩ?y "@kre('8EBRksruy|cBb(OzzR򋾌(U|&,\B$1_1t=A/'a7/o?9_>3b'_MS2Y.O @VK|kE~t7s-^A/Awуݎ/@g_$1 [|R2#|v~OSVf5 t CϠoKϡ_?BC#~w_k-{69]v[gGmZ֙kk+JKbS!WVege)rFFSU۹.+YpX u#>qrfLkjui&Vs3ǞŃ^q0/[DXV&"YL c6V>.t}67uk\z \dWl"@UQHE t\^h2 T;l&NQ H6DLGٹKghW!@7N#JlBw yPlvH[sS%R5?F7m%/Q xv}qمUsxF./p]xQ:1 }cxlW_ӻ+P](mLFf`qˆ15Z l7h,GF.-;gG݃sj8n>Ka10Wcs,""TB$K"8xΊЬ8* ">z#\a4zJԋ4 h*Y2Aacqq 6aJ)s! 6BoK儮z\S?sjH!xϙcp:V efdHga[kLRT|˓1>]"[#8 ., ֚N&0 '*XL\TjJ2\ͷѢm1ϯÍQUr՜`9tujiKRVx,̒ӅTG gTuA&miG{6L's-#gero L\K8o++X YMT[%%2VWl_:SћR*$d -I.^,7bM|}y5brZ^=r/oZҵ!j|q`G0ZvkhPn.~jn;5V=\JRbPs:p-[Yߗ8B|IFwQu%%kӜJe#XUR2.mU){T^QT(tżs^in%^/аxŲvX#{O{l$1t̿S;l贞yی]⛾|N&?JFÌAptִ,E&'nf;m/uC8ȷ~qc_ɞ+ mUF ޺J3ՖTkڼ YiIiVLRiXZNiP7v)>TK\LrB.~u5-L=u`frk}P?|_1}v:M~a֦ Y?>lmVqJ!wy;Vұzu!GruzUq+KϺԞ'rth6r5|邆TpޑIWYɠMN^W 62kz7pדNl3[5O͉O<~azl!"gaa3=^K*#TceW%UiT<*d.E>-}Nvj[vBKܹxu^Tn$vy-\a(:c 촙:ϙ;kw {Զ eWi3 m;h VilM~3lJ,V^L _^@Yz.Rpچzg='^^Mפ+o]I'P҉SHܘR'Jr F`1q3H-ptSG:a+GXC5O6Ē r底5g<^ -779s|=T29hpv~~=k-azV9Z'h_XTg( H.:E}RY9 o%md0R)OKOςee()Id$2h`yply3xj6[il99&L_%{/q6G z IVJ;~Vp̒a<a]\S}thq,-ƒX>O$$3o&^'հ7ĺHG+NA#i+d f>DQp>ZVIgXx5b8KŤpih'{'{H/&^Ńob){TV~|ҟdTn_[$ǔD>EY*Bo6&1AprA-zkg˒Ψ H0 ,)  t ,쁚A5 eC,R0?J0 e G f%XQq-Kp5[BShxW OESX({uψ? 3 (pCQj:( OCA )dhh$0- ~U 8EA0E#0@dEB@$ Hpj"Бhp?p`zdFǖiwhrt|:QeOϰJ681͞$S(M}Izѣh'HB579Vr=8łGaP5EATdXp<2BiB{-(F (ܻP hM%$bQ;  .^x`f7Mè(:+JE$+|h_ꇃXHIcq= ƅ  hߧx8Eg%{=$R@ jJ:@A4޴+,0..y@ +ʍ4/2-J~2kz(^7׹KlEUW 0vp6pa=~ծ9c-P@HoVP{:& g~g[gw)gǛ&F7Bѫ.ι,ESEܾ4=ê(Dg$PyG=f6˱oxU%kd8[54_cQ^a@GӠѾgG{مg] pu)p=yX:wWUU,>*;(ȏ 3;Q =B+fPӣh,VIJH€Dqh<Ň'Dm endstream endobj 88 0 obj 5515 endobj 89 0 obj <> endobj 90 0 obj <> stream x]n0 ;bV``tDy""kKl| MPVĻ38KSfeMc&Mƚ-3?^'GK9C>1S33+b y{IfOyЃ\ȋ Y}WnpXE7fZ endstream endobj 91 0 obj <> endobj 92 0 obj <> stream xToEfe'nHvkyPJUcBI%H8PZ!e !*!UP4v/=pPe^DY/IT?`~=~5j !GҘ\_bX ?P{a!,=Zg FZ-\j4y#O/^3C^% fiW+n@flȿ=|ϭ`y;3|t yycE@<6xV p sr !נAʠ)NMjlt]gmavFMc)bD` j. ۙΔ[dYe Fp7d۬AUhIP->%Ɍ蚅(BK1CŅBFܷAcXV]T,zbyoq%ކ(b$~Ign<9)s{%&(1|4+e3ccqrx.{cb<;gwaarB_p~)vwgW=k l_>`xo?aPFWڢ rzz/N^LG B:؍E>P_DKFv&% w.ԯ9؍SOn;؃VgFz"t`$DNmdr鄙ZK}a͘\}2t=Rz!+h `q,Ld`] )XCdai3` ƃ CߡX=vSX!}!Ϊ&"!:̇ 5@pE ì@_Sw endstream endobj 93 0 obj 1154 endobj 94 0 obj <> endobj 95 0 obj <> stream x]AO 96&f&=?´؁L)VM<@x7kQ13.qe0H|pPewMJ oKƹ16ү-78=8Y;{@ޯ~Mg j[8=O6=u.;"_mKd USU-4[? }Xd-ISSNc2K2{?'ŴSe}~my endstream endobj 96 0 obj <> endobj 97 0 obj <> endobj 98 0 obj <> endobj 99 0 obj <> /ProcSet[/PDF/Text/ImageC/ImageI/ImageB] >> endobj 1 0 obj <>/Contents 2 0 R>> endobj 4 0 obj <>/Contents 5 0 R>> endobj 13 0 obj <>/Contents 14 0 R>> endobj 19 0 obj <>/Contents 20 0 R>> endobj 28 0 obj <>/Contents 29 0 R>> endobj 37 0 obj <>/Contents 38 0 R>> endobj 44 0 obj <>/Contents 45 0 R>> endobj 54 0 obj <>/Contents 55 0 R>> endobj 69 0 obj <>/Contents 70 0 R>> endobj 100 0 obj <> endobj 101 0 obj < /Dest[4 0 R/XYZ 56.7 281.8 0]/Parent 100 0 R/Next 102 0 R>> endobj 102 0 obj < /Dest[19 0 R/XYZ 56.7 456.4 0]/Parent 100 0 R/Prev 101 0 R/Next 103 0 R>> endobj 103 0 obj < /Dest[44 0 R/XYZ 56.7 726.6 0]/Parent 100 0 R/Prev 102 0 R>> endobj 81 0 obj <> endobj 74 0 obj <> >> endobj 75 0 obj <> >> endobj 104 0 obj <> endobj 105 0 obj < /Creator /Producer /CreationDate(D:20141013184206-04'00')>> endobj xref 0 106 0000000000 65535 f 0000691673 00000 n 0000000019 00000 n 0000002000 00000 n 0000691817 00000 n 0000002021 00000 n 0000002927 00000 n 0000026854 00000 n 0000002947 00000 n 0000026457 00000 n 0000026833 00000 n 0000122629 00000 n 0000123538 00000 n 0000691961 00000 n 0000123559 00000 n 0000125170 00000 n 0000125192 00000 n 0000156330 00000 n 0000156804 00000 n 0000692125 00000 n 0000156825 00000 n 0000158265 00000 n 0000192716 00000 n 0000158287 00000 n 0000192221 00000 n 0000192695 00000 n 0000224312 00000 n 0000224786 00000 n 0000692271 00000 n 0000224807 00000 n 0000225692 00000 n 0000323147 00000 n 0000225713 00000 n 0000322217 00000 n 0000323126 00000 n 0000358197 00000 n 0000358671 00000 n 0000692417 00000 n 0000358692 00000 n 0000359685 00000 n 0000359706 00000 n 0000428712 00000 n 0000428735 00000 n 0000429772 00000 n 0000692563 00000 n 0000429793 00000 n 0000431048 00000 n 0000431070 00000 n 0000454872 00000 n 0000454389 00000 n 0000454412 00000 n 0000454851 00000 n 0000484751 00000 n 0000485190 00000 n 0000692709 00000 n 0000485211 00000 n 0000486218 00000 n 0000534678 00000 n 0000510057 00000 n 0000486239 00000 n 0000509574 00000 n 0000509597 00000 n 0000510036 00000 n 0000534195 00000 n 0000534218 00000 n 0000534657 00000 n 0000558814 00000 n 0000558837 00000 n 0000559276 00000 n 0000692855 00000 n 0000559297 00000 n 0000560414 00000 n 0000573524 00000 n 0000560436 00000 n 0000693891 00000 n 0000694082 00000 n 0000573141 00000 n 0000573164 00000 n 0000573503 00000 n 0000669200 00000 n 0000670109 00000 n 0000693736 00000 n 0000670130 00000 n 0000681566 00000 n 0000681589 00000 n 0000681781 00000 n 0000682394 00000 n 0000682845 00000 n 0000688446 00000 n 0000688468 00000 n 0000688665 00000 n 0000689078 00000 n 0000689346 00000 n 0000690586 00000 n 0000690608 00000 n 0000690800 00000 n 0000691091 00000 n 0000691252 00000 n 0000691344 00000 n 0000691407 00000 n 0000693019 00000 n 0000693078 00000 n 0000693287 00000 n 0000693534 00000 n 0000694244 00000 n 0000694361 00000 n trailer < ] /DocChecksum /70836A30FF88A12790780ACF87A05272 >> startxref 694575 %%EOF rtimulib-7.2.1/LICENSE000066400000000000000000000023761254201074400143640ustar00rootroot00000000000000/////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. rtimulib-7.2.1/Linux/000077500000000000000000000000001254201074400144465ustar00rootroot00000000000000rtimulib-7.2.1/Linux/CMakeLists.txt000066400000000000000000000075621254201074400172200ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014, richards-tech #// #// Permission 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. #// The cmake support was based on work by Moritz Fischer at ettus.com. #// Original copyright notice: # # Copyright 2014 Ettus Research LLC # ######################################################################## IF(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) MESSAGE(FATAL_ERROR "Prevented in-tree built. This is bad practice.") ENDIF(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) INCLUDE(CMakeDependentOption) INCLUDE(FeatureSummary) ######################################################################## # Project setup ######################################################################## CMAKE_MINIMUM_REQUIRED(VERSION 2.8.9) PROJECT(RTIMULib CXX) ENABLE_TESTING() OPTION(BUILD_GL "Build RTIMULibGL" ON) OPTION(BUILD_DRIVE "Build RTIMULibDrive" ON) OPTION(BUILD_DRIVE10 "Build RTIMULibDrive10" ON) OPTION(BUILD_DRIVE11 "Build RTIMULibDrive11" ON) OPTION(BUILD_CAL "Build RTIMULibCal" ON) OPTION(BUILD_DEMO "Build RTIMULibDemo" ON) CMAKE_DEPENDENT_OPTION(BUILD_DEMOGL "Build RTIMULibDemoGL" ON "BUILD_GL" OFF) ADD_FEATURE_INFO(RTIMULibGL BUILD_GL "") ADD_FEATURE_INFO(RTIMULibDrive BUILD_DRIVE "App that shows how to use the RTIMULib library in a basic way.") ADD_FEATURE_INFO(RTIMULibDrive10 BUILD_DRIVE10 "App that shows to use pressure/temperature sensors.") ADD_FEATURE_INFO(RTIMULibDrive11 BUILD_DRIVE11 "App that shows to use pressure/temperature/humidity sensors.") ADD_FEATURE_INFO(RTIMULibCal BUILD_CAL "Command line calibration tool for the magnetometers and accelerometers.") ADD_FEATURE_INFO(RTIMULibDemo BUILD_DEMO "GUI app that displays the fused IMU data in real-time") ADD_FEATURE_INFO(RTIMULibDemoGL BUILD_DEMOGL "RTIMULibDemo with OpenGL visualization") INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../RTIMULib) ADD_SUBDIRECTORY(${CMAKE_CURRENT_SOURCE_DIR}/../RTIMULib RTIMULib) IF(BUILD_GL) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/RTIMULibGL) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/RTIMULibGL/QtGLLib) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/RTIMULibGL/VRWidgetLib) ADD_SUBDIRECTORY(${CMAKE_CURRENT_SOURCE_DIR}/RTIMULibGL) ENDIF(BUILD_GL) IF(BUILD_DRIVE) ADD_SUBDIRECTORY(RTIMULibDrive) ENDIF(BUILD_DRIVE) IF(BUILD_DRIVE10) ADD_SUBDIRECTORY(RTIMULibDrive10) ENDIF(BUILD_DRIVE10) IF(BUILD_DRIVE11) ADD_SUBDIRECTORY(RTIMULibDrive11) ENDIF(BUILD_DRIVE11) IF(BUILD_CAL) ADD_SUBDIRECTORY(RTIMULibCal) ENDIF(BUILD_CAL) IF(BUILD_DEMO) ADD_SUBDIRECTORY(RTIMULibDemo) ENDIF(BUILD_DEMO) IF(BUILD_DEMOGL) ADD_SUBDIRECTORY(RTIMULibDemoGL) ENDIF(BUILD_DEMOGL) PRINT_ENABLED_FEATURES() MESSAGE(STATUS "Using install prefix: ${CMAKE_INSTALL_PREFIX}") rtimulib-7.2.1/Linux/README.md000066400000000000000000000164551254201074400157400ustar00rootroot00000000000000# RTIMULib for Linux This directory contains the applications for embedded Linux systems such as the Raspberry Pi and Intel Edison. This description assumes that the Edison image was built using the meta-edison-rt layer, available on the GitHub repo. The RTIMULibvrpm demo app, which shows how to integrate RTIMULib with vrpn, has its own build and run instructions in a README.md within the RTIMULibvrpn directory. ### Setting up the Raspberry Pi #### Connecting the IMU The easiest way to connect the IMU to the Raspberry Pi is to use something like the Adafruit Pi Plate (http://www.adafruit.com/products/801) as it makes it obvious where the I2C bus 1 pins are and where to pick up 3.3V. Basically, you need to connect the I2C SDA, I2C SCL, 3.3V and GND to the IMU breakout board you are using. Take care with these connections or else disaster may follow! #### Enabling and Configuring the I2C Bus Add the following two lines to /etc/modules: i2c-bcm2708 i2c-dev Then, comment out the following line in /etc/modprobe.d/raspi-blacklist.conf: # blacklist i2c-bcm2708 Restart the Raspberry Pi and /dev/i2c-0 and /dev/i2c-1 should appear. It’s also useful to install the I2C tools: sudo apt-get install i2c-tools Then: sudo i2cdetect -y 1 will detect any devices on /dev/i2c-1. If you have the MPU9150 wired up, you should see it at address 0x68. This is the default address expected by the demo programs. If it is at 0x69, the address expected by the demo programs will need to be changed (there's a settings file for doing things like that so it's easy to do). By default, the I2C devices are owned by root. To fix this, reate a file /etc/udev/rules.d/90-i2c.rules and add the line: KERNEL=="i2c-[0-7]",MODE="0666" The Raspberry Pi will need to be rebooted to implement this change. Another thing worth doing is to change the I2C bus speed to 400KHz. Add the following line to /boot/config.txt: dtparam=i2c1_baudrate=400000 Simplest thing is then to reboot to make this change. The I2C bus should now be ready for operation. The Raspberry Pi requires that cmake is installed. Enter: sudo apt-get install cmake If the Qt-based GUI programs are to be compiled, also install Qt: sudo apt-get install libqt4-dev ### Setting up the Intel Edison It's necessary to add an extra directory to ldconfig. Create /etc/ld.so.conf and add the line: /usr/local/lib Then run ldconfig so that the RTIMULib libraries will be found. ### Build using cmake Go to the directory where RTIMULib was cloned and enter: mkdir build cd build cmake .. make -j4 sudo make install sudo ldconfig This will build and install all of the libraries and demo programs. Note that the Intel Edison does not need the "sudo" since the default user is root. The "-j4" part indicates how many cores should be used and will considerably speed up the build on the Pi2 and Edison. ### Build using make Makefiles are provided for RTIMULibCal, RTIMULibDrive, RTIMULibDrive10 and RTIMULibDrive11. To build, navigate to the directory for the app and enter: make -j4 sudo make install ### Build using qmake (Raspberry Pi only) .pro files are included for RTIMULibDemo and RTIMULibDemoGL. To build using qmake, navigate to the RTIMULibDemo or RTIMULibDemoGL directory and enter: qmake make -j4 sudo make install The .pro files can also be used with QtCreator if desired. ### Run the RTIMULibCal App RTIMULibCal can either add calibration data to an existing RTIMULib.ini or else create a new one with the calibration data. RTIMULib.ini is used/created in the working directory. If magnetometer ellipsoid fit isn't required, RTIMULibCal can be run anywhere. If ellipsoid fit is required, then the program assumes that the RTEllipsoidFit directory is at the same level as the working directory so that "../RTEllipsoidFit" refers to the directory holding the RTEllipsoidFit.m octave program. If not, ellipsoid fitting will fail. Note - ellipsoid fit is not supported on the Intel Edison. The normal process is to run the magnetometer min/max option followed by the magnetometer ellipsoid fit option followed finally by the accelerometer min/max option. The program is self-documenting in that the instructions for every option will be displayed when the option is selected. The resulting RTIMULib.ini can then be used by any other RTIMULib application. ### Run the RTIMULibDrive, RTIMULibDrive10 and RTIMULibDrive11 Demo Apps RTIMULibDrive is a simple command line program that shows how simple it is to use RTIMULib. RTIMULibDrive10 extends this to also support 10-dof IMUs with pressure/temperature sensors. RTIMULibDrive11 adds humidity sensor support to RTIMULibDrive10. You should be able to run the program just by entering RTIMULibDrive(10/11). It will try to auto detect the connected IMU If all is well, you should see a line showing the sample rate and the current Euler angles. This is updated 10 times per second, regardless of the sensor sample rate. By default, the driver runs at 50 samples per second in most cases. So, you should see the sample rate indicating around 50 samples per second. The sample rate can be changed by editing the .ini file entry for the appropriate IMU type. The displayed pose shows the roll, pitch and yaw seen by the IMU. Using an aircraft analogy, the roll axis points from the pilot towards the nose, the pitch axis points from the pilot along the right wing and the yaw axis points from the pilot down towards the ground. Right wing down is a positive roll, nose up is a positive pitch and clockwise rotation is a positive yaw. Various parameters can be changed by editing the RTIMULib.ini file. These are described later. Take a look at RTIMULibDrive.cpp. Quite a few of the code lines are just to calculate rates and display outputs! ### Run the RTIMULibDemo and RTIMULibDemoGL Programs (Raspberry Pi only) To run the program, the Raspberry Pi needs to be running the desktop. To do this (if it isn't already), enter: startx Then open a command window and enter: RTIMULibDemo You should see the GUI pop up and, if everything is ok, it will start displaying data from the IMU and the output of the Kalman filter. If the MPU9150 is at the alternate address, you'll need to edit the RTIMULib.ini file that RTIMULibDemo generated and restart the program. To calibrate the compass, click on the "Calibrate compass" tab. A new dialog will pop up showing the maximum and minimum readings seen from the magnetometers. You need to waggle the IMU around, ensuring that each axis (roll, pitch and yaw) point straight down and also straight up at some point. You need to do this in an area clear of magnetic fields otherwise the results will be distorted. Eventually, the readings will stop changing meaning that the real max and min values have been obtained. Click on "Ok" to save the values to the RTIMULib.ini file. Provided this .ini file is used in future (it just has to be in the current directory when RTIMULibDemo is run), the calibration will not have to be repeated. Now that RTIMULibDemo is using calibrated magnetometers, the yaw should be much more reliable. The .ini file created by RTIMULibDemo can also be used by RTIMULibDrive - just run RTIMULibDrive in the same directory and it will pick up the compass calibration data. Running RTIMULibDemoGL is exactly the same except that it takes place in the RTIMULibDemoGL directory. rtimulib-7.2.1/Linux/RTIMULibCal/000077500000000000000000000000001254201074400164155ustar00rootroot00000000000000rtimulib-7.2.1/Linux/RTIMULibCal/CMakeLists.txt000066400000000000000000000030601254201074400211540ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014-2015, richards-tech, LLC #// #// Permission 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. #// The cmake support was based on work by Moritz Fischer at ettus.com. #// Original copyright notice: # # Copyright 2014 Ettus Research LLC # SET(CAL_SRCS RTIMULibCal.cpp) ADD_EXECUTABLE(RTIMULibCal ${CAL_SRCS}) TARGET_LINK_LIBRARIES(RTIMULibCal RTIMULib) INSTALL(TARGETS RTIMULibCal DESTINATION bin) rtimulib-7.2.1/Linux/RTIMULibCal/Makefile000066400000000000000000000117151254201074400200620ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014-2015, richards-tech, LLC #// #// Permission 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. # Compiler, tools and options RTIMULIBPATH = ../../RTIMULib CC = gcc CXX = g++ DEFINES = CFLAGS = -pipe -O2 -Wall -W $(DEFINES) CXXFLAGS = -pipe -O2 -Wall -W $(DEFINES) INCPATH = -I. -I$(RTIMULIBPATH) LINK = g++ LFLAGS = -Wl,-O1 LIBS = -L/usr/lib/arm-linux-gnueabihf COPY = cp -f COPY_FILE = $(COPY) COPY_DIR = $(COPY) -r STRIP = strip INSTALL_FILE = install -m 644 -p INSTALL_DIR = $(COPY_DIR) INSTALL_PROGRAM = install -m 755 -p DEL_FILE = rm -f SYMLINK = ln -f -s DEL_DIR = rmdir MOVE = mv -f CHK_DIR_EXISTS = test -d MKDIR = mkdir -p # Output directory OBJECTS_DIR = objects/ # Files DEPS = $(RTIMULIBPATH)/RTMath.h \ $(RTIMULIBPATH)/RTIMULib.h \ $(RTIMULIBPATH)/RTIMULibDefs.h \ $(RTIMULIBPATH)/RTIMUHal.h \ $(RTIMULIBPATH)/RTFusion.h \ $(RTIMULIBPATH)/RTFusionKalman4.h \ $(RTIMULIBPATH)/RTFusionRTQF.h \ $(RTIMULIBPATH)/RTIMUSettings.h \ $(RTIMULIBPATH)/RTIMUAccelCal.h \ $(RTIMULIBPATH)/RTIMUMagCal.h \ $(RTIMULIBPATH)/RTIMUCalDefs.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMU.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUNull.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUMPU9150.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUMPU9250.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUGD20HM303D.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUGD20M303DLHC.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUGD20HM303DLHC.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMULSM9DS0.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMULSM9DS1.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUBMX055.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUBNO055.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressure.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressureBMP180.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressureLPS25H.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressureMS5611.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressureMS5637.h OBJECTS = objects/RTIMULibCal.o \ objects/RTMath.o \ objects/RTIMUHal.o \ objects/RTFusion.o \ objects/RTFusionKalman4.o \ objects/RTFusionRTQF.o \ objects/RTIMUSettings.o \ objects/RTIMUAccelCal.o \ objects/RTIMUMagCal.o \ objects/RTIMU.o \ objects/RTIMUNull.o \ objects/RTIMUMPU9150.o \ objects/RTIMUMPU9250.o \ objects/RTIMUGD20HM303D.o \ objects/RTIMUGD20M303DLHC.o \ objects/RTIMUGD20HM303DLHC.o \ objects/RTIMULSM9DS0.o \ objects/RTIMULSM9DS1.o \ objects/RTIMUBMX055.o \ objects/RTIMUBNO055.o \ objects/RTPressure.o \ objects/RTPressureBMP180.o \ objects/RTPressureLPS25H.o \ objects/RTPressureMS5611.o \ objects/RTPressureMS5637.o MAKE_TARGET = RTIMULibCal DESTDIR = Output/ TARGET = Output/$(MAKE_TARGET) # Build rules $(TARGET): $(OBJECTS) @$(CHK_DIR_EXISTS) Output/ || $(MKDIR) Output/ $(LINK) $(LFLAGS) -o $(TARGET) $(OBJECTS) $(LIBS) clean: -$(DEL_FILE) $(OBJECTS) -$(DEL_FILE) *~ core *.core # Compile $(OBJECTS_DIR)%.o : $(RTIMULIBPATH)/%.cpp $(DEPS) @$(CHK_DIR_EXISTS) objects/ || $(MKDIR) objects/ $(CXX) -c -o $@ $< $(CFLAGS) $(INCPATH) $(OBJECTS_DIR)%.o : $(RTIMULIBPATH)/IMUDrivers/%.cpp $(DEPS) @$(CHK_DIR_EXISTS) objects/ || $(MKDIR) objects/ $(CXX) -c -o $@ $< $(CFLAGS) $(INCPATH) $(OBJECTS_DIR)RTIMULibCal.o : RTIMULibCal.cpp $(DEPS) @$(CHK_DIR_EXISTS) objects/ || $(MKDIR) objects/ $(CXX) -c -o $@ RTIMULibCal.cpp $(CFLAGS) $(INCPATH) # Install install_target: FORCE @$(CHK_DIR_EXISTS) $(INSTALL_ROOT)/usr/local/bin/ || $(MKDIR) $(INSTALL_ROOT)/usr/local/bin/ -$(INSTALL_PROGRAM) "Output/$(MAKE_TARGET)" "$(INSTALL_ROOT)/usr/local/bin/$(MAKE_TARGET)" -$(STRIP) "$(INSTALL_ROOT)/usr/local/bin/$(MAKE_TARGET)" uninstall_target: FORCE -$(DEL_FILE) "$(INSTALL_ROOT)/usr/local/bin/$(MAKE_TARGET)" install: install_target FORCE uninstall: uninstall_target FORCE FORCE: rtimulib-7.2.1/Linux/RTIMULibCal/README.md000066400000000000000000000030011254201074400176660ustar00rootroot00000000000000# RTIMULibCal - a command line program to generate calibration data It is essential to calibrate the IMU's magnetometer (at the very least) or else very poor results will be obtained. In a non-GUI environment, use RTIMULibCal. RTIMULibCal has minimal pre-requisites so should be usable on any system that is capable of compiling and running RTIMULibDrive. Ellipsoid fit requires octave to be available. If it isn't, only min/max calibration will be available. ### Build Install cmake if not already there: sudo apt-get install cmake Navigate to the RTIMULibCal directory and run: make sudo make install ### Usage RTIMULibCal can either add calibration data to an existing RTIMULib.ini or else create a new one with the calibration data. RTIMULib.ini is used/created in the working directory. If magnetometer ellipsoid fit isn't required, RTIMULibCal can be run anywhere. If ellipsoid fit is required, then the program assumes that the RTEllipsoidFit directory is at the same level as the working directory so that "../RTEllipsoidFit" refers to the directory holding the RTEllipsoidFit.m octave program. If not, ellipsoid fitting will fail. The normal process is to run the magnetometer min/max option followed by the magnetometer ellipsoid fit option followed finally by the accelerometer min/max option. The program is self-documenting in that the instructions for every option will be displayed when the option is selected. The resulting RTIMULib.ini can then be used by any other RTIMULib application. rtimulib-7.2.1/Linux/RTIMULibCal/RTIMULibCal.cpp000066400000000000000000000311131254201074400210670ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTIMULib.h" #include "RTIMUMagCal.h" #include "RTIMUAccelCal.h" #include #include #include #include #include // where to find the ellipsoid fitting code #define ELLIPSOID_FIT_DIR "../RTEllipsoidFit/" // function prototypes void doMagMinMaxCal(); void doMagEllipsoidCal(); void processEllipsoid(); void doAccelCal(); void newIMU(); bool pollIMU(); char getUserChar(); void displayMenu(); void displayMagMinMax(); void displayMagEllipsoid(); void displayAccelMinMax(); // global variables static RTIMUSettings *settings; static RTIMU_DATA imuData; static RTIMU *imu; static RTIMUMagCal *magCal; static RTIMUAccelCal *accelCal; static bool magMinMaxDone; static bool accelEnables[3]; static int accelCurrentAxis; int main(int argc, char **argv) { char *settingsFile; if (argc == 2) settingsFile = argv[1]; else settingsFile = (char *)"RTIMULib"; printf("RTIMULibCal - using %s.ini\n", settingsFile); settings = new RTIMUSettings(settingsFile); bool mustExit = false; imu = NULL; newIMU(); // set up for calibration run imu->setCompassCalibrationMode(true); imu->setAccelCalibrationMode(true); magCal = new RTIMUMagCal(settings); magCal->magCalInit(); magMinMaxDone = false; accelCal = new RTIMUAccelCal(settings); accelCal->accelCalInit(); // set up console io struct termios ctty; tcgetattr(fileno(stdout), &ctty); ctty.c_lflag &= ~(ICANON); tcsetattr(fileno(stdout), TCSANOW, &ctty); // the main loop while (!mustExit) { displayMenu(); switch (tolower(getchar())) { case 'x' : mustExit = true; break; case 'm' : doMagMinMaxCal(); break; case 'e' : doMagEllipsoidCal(); break; case 'a' : doAccelCal(); break; } } printf("\nRTIMULibCal exiting\n"); return 0; } void newIMU() { if (imu != NULL) delete imu; imu = RTIMU::createIMU(settings); if ((imu == NULL) || (imu->IMUType() == RTIMU_TYPE_NULL)) { printf("No IMU found\n"); exit(1); } // set up IMU imu->IMUInit(); } void doMagMinMaxCal() { uint64_t displayTimer; uint64_t now; char input; magCal->magCalInit(); magMinMaxDone = false; // now collect data printf("\n\nMagnetometer min/max calibration\n"); printf("--------------------------------\n"); printf("Waggle the IMU chip around, ensuring that all six axes\n"); printf("(+x, -x, +y, -y and +z, -z) go through their extrema.\n"); printf("When all extrema have been achieved, enter 's' to save, 'r' to reset\n"); printf("or 'x' to abort and discard the data.\n"); printf("\nPress any key to start..."); getchar(); displayTimer = RTMath::currentUSecsSinceEpoch(); while (1) { // poll at the rate recommended by the IMU usleep(imu->IMUGetPollInterval() * 1000); while (pollIMU()) { magCal->newMinMaxData(imuData.compass); now = RTMath::currentUSecsSinceEpoch(); // display 10 times per second if ((now - displayTimer) > 100000) { displayMagMinMax(); displayTimer = now; } } if ((input = getUserChar()) != 0) { switch (input) { case 's' : printf("\nSaving min/max data.\n\n"); magCal->magCalSaveMinMax(); magMinMaxDone = true; return; case 'x' : printf("\nAborting.\n"); return; case 'r' : printf("\nResetting min/max data.\n"); magCal->magCalReset(); break; } } } } void doMagEllipsoidCal() { uint64_t displayTimer; uint64_t now; char input; if (!magMinMaxDone) { printf("\nYou cannot collect ellipsoid data until magnetometer min/max\n"); printf("calibration has been performed.\n"); return; } printf("\n\nMagnetometer ellipsoid calibration\n"); printf("\n\n----------------------------------\n"); printf("Move the magnetometer around in as many poses as possible.\n"); printf("The counts for each of the 8 pose quadrants will be displayed.\n"); printf("When enough data (%d samples per octant) has been collected,\n", RTIMUCALDEFS_OCTANT_MIN_SAMPLES); printf("ellipsoid processing will begin.\n"); printf("Enter 'x' at any time to abort and discard the data.\n"); printf("\nPress any key to start..."); getchar(); displayTimer = RTMath::currentUSecsSinceEpoch(); while (1) { // poll at the rate recommended by the IMU usleep(imu->IMUGetPollInterval() * 1000); while (pollIMU()) { magCal->newEllipsoidData(imuData.compass); if (magCal->magCalEllipsoidValid()) { magCal->magCalSaveRaw(ELLIPSOID_FIT_DIR); processEllipsoid(); return; } now = RTMath::currentUSecsSinceEpoch(); // display 10 times per second if ((now - displayTimer) > 100000) { displayMagEllipsoid(); displayTimer = now; } } if ((input = getUserChar()) != 0) { switch (input) { case 'x' : printf("\nAborting.\n"); return; } } } } void processEllipsoid() { pid_t pid; int status; printf("\n\nProcessing ellipsoid fit data...\n"); pid = fork(); if (pid == 0) { // child process chdir(ELLIPSOID_FIT_DIR); execl("/bin/sh", "/bin/sh", "-c", RTIMUCALDEFS_OCTAVE_COMMAND, NULL); printf("here"); _exit(EXIT_FAILURE); } else if (pid < 0) { printf("\nFailed to start ellipsoid fitting code.\n"); return; } else { // parent process - wait for child if (waitpid(pid, &status, 0) != pid) { printf("\n\nEllipsoid fit failed, %d\n", status); } else { if (status == 0) { printf("\nEllipsoid fit completed - saving data to file."); magCal->magCalSaveCorr(ELLIPSOID_FIT_DIR); } else { printf("\nEllipsoid fit returned %d - aborting.\n", status); } } } } void doAccelCal() { uint64_t displayTimer; uint64_t now; char input; printf("\n\nAccelerometer Calibration\n"); printf("-------------------------\n"); printf("The code normally ignores readings until an axis has been enabled.\n"); printf("The idea is to orient the IMU near the current extrema (+x, -x, +y, -y, +z, -z)\n"); printf("and then enable the axis, moving the IMU very gently around to find the\n"); printf("extreme value. Now disable the axis again so that the IMU can be inverted.\n"); printf("When the IMU has been inverted, enable the axis again and find the extreme\n"); printf("point. Disable the axis again and press the space bar to move to the next\n"); printf("axis and repeat. The software will display the current axis and enable state.\n"); printf("Available options are:\n"); printf(" e - enable the current axis.\n"); printf(" d - disable the current axis.\n"); printf(" space bar - move to the next axis (x then y then z then x etc.\n"); printf(" r - reset the current axis (if enabled).\n"); printf(" s - save the data once all 6 extrema have been collected.\n"); printf(" x - abort and discard the data.\n"); printf("\nPress any key to start..."); getchar(); // perform all axis reset for (int i = 0; i < 3; i++) accelCal->accelCalEnable(i, true); accelCal->accelCalReset(); for (int i = 0; i < 3; i++) accelCal->accelCalEnable(i, false); accelCurrentAxis = 0; for (int i = 0; i < 3; i++) accelEnables[i] = false; displayTimer = RTMath::currentUSecsSinceEpoch(); while (1) { // poll at the rate recommended by the IMU usleep(imu->IMUGetPollInterval() * 1000); while (pollIMU()) { for (int i = 0; i < 3; i++) accelCal->accelCalEnable(i, accelEnables[i]); accelCal->newAccelCalData(imuData.accel); now = RTMath::currentUSecsSinceEpoch(); // display 10 times per second if ((now - displayTimer) > 100000) { displayAccelMinMax(); displayTimer = now; } } if ((input = getUserChar()) != 0) { switch (input) { case 'e' : accelEnables[accelCurrentAxis] = true; break; case 'd' : accelEnables[accelCurrentAxis] = false; break; case 'r' : accelCal->accelCalReset(); break; case ' ' : if (++accelCurrentAxis == 3) accelCurrentAxis = 0; break; case 's' : accelCal->accelCalSave(); printf("\nAccelerometer calibration data saved to file.\n"); return; case 'x' : printf("\nAborting.\n"); return; } } } } bool pollIMU() { if (imu->IMURead()) { imuData = imu->getIMUData(); return true; } else { return false; } } char getUserChar() { int i; ioctl(0, FIONREAD, &i); if (i <= 0) return 0; return tolower(getchar()); } void displayMenu() { printf("\n"); printf("Options are: \n\n"); printf(" m - calibrate magnetometer with min/max\n"); printf(" e - calibrate magnetometer with ellipsoid (do min/max first)\n"); printf(" a - calibrate accelerometers\n"); printf(" x - exit\n\n"); printf("Enter option: "); } void displayMagMinMax() { printf("\n\n"); printf("Min x: %6.2f min y: %6.2f min z: %6.2f\n", magCal->m_magMin.data(0), magCal->m_magMin.data(1), magCal->m_magMin.data(2)); printf("Max x: %6.2f max y: %6.2f max z: %6.2f\n", magCal->m_magMax.data(0), magCal->m_magMax.data(1), magCal->m_magMax.data(2)); fflush(stdout); } void displayMagEllipsoid() { int counts[RTIMUCALDEFS_OCTANT_COUNT]; printf("\n\n"); magCal->magCalOctantCounts(counts); printf("---: %d +--: %d -+-: %d ++-: %d\n", counts[0], counts[1], counts[2], counts[3]); printf("--+: %d +-+: %d -++: %d +++: %d\n", counts[4], counts[5], counts[6], counts[7]); fflush(stdout); } void displayAccelMinMax() { printf("\n\n"); printf("Current axis: "); if (accelCurrentAxis == 0) { printf("x - %s", accelEnables[0] ? "enabled" : "disabled"); } else if (accelCurrentAxis == 1) { printf("y - %s", accelEnables[1] ? "enabled" : "disabled"); } else if (accelCurrentAxis == 2) { printf("z - %s", accelEnables[2] ? "enabled" : "disabled"); } printf("\nMin x: %6.2f min y: %6.2f min z: %6.2f\n", accelCal->m_accelMin.data(0), accelCal->m_accelMin.data(1), accelCal->m_accelMin.data(2)); printf("Max x: %6.2f max y: %6.2f max z: %6.2f\n", accelCal->m_accelMax.data(0), accelCal->m_accelMax.data(1), accelCal->m_accelMax.data(2)); fflush(stdout); } rtimulib-7.2.1/Linux/RTIMULibDemo/000077500000000000000000000000001254201074400166025ustar00rootroot00000000000000rtimulib-7.2.1/Linux/RTIMULibDemo/AccelCalDlg.cpp000066400000000000000000000230161254201074400213660ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "AccelCalDlg.h" #include "RTIMUAccelCal.h" #include "RTIMUSettings.h" #include #include #include #include #include AccelCalDlg::AccelCalDlg(QWidget *parent, RTIMUSettings* settings) : QDialog(parent) { m_cal = new RTIMUAccelCal(settings); m_newData = false; m_cal->accelCalInit(); layoutWindow(); setWindowTitle("Accelerometer Calibration"); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); m_timer = startTimer(50); connect(m_checkAllBtn, SIGNAL(clicked()), this, SLOT(onCheckAll())); connect(m_uncheckAllBtn, SIGNAL(clicked()), this, SLOT(onUncheckAll())); connect(m_resetBtn, SIGNAL(clicked()), this, SLOT(onReset())); connect(m_okBtn, SIGNAL(clicked()), this, SLOT(onOk())); connect(m_cancelBtn, SIGNAL(clicked()), this, SLOT(onCancel())); } AccelCalDlg::~AccelCalDlg() { } void AccelCalDlg::newIMUData(const RTIMU_DATA& data) { QMutexLocker lock(&m_refreshMutex); m_currentVal = data.accel; for (int i = 0; i < 3; i++) m_cal->accelCalEnable(i, m_check[i]->checkState() == Qt::Checked); m_cal->newAccelCalData(data.accel); m_newData = true; } void AccelCalDlg::onOk() { killTimer(m_timer); m_cal->accelCalSave(); accept(); } void AccelCalDlg::onCancel() { killTimer(m_timer); reject(); } void AccelCalDlg::onReset() { QMutexLocker lock(&m_refreshMutex); for (int i = 0; i < 3; i++) m_cal->accelCalEnable(i, m_check[i]->checkState() == Qt::Checked); m_cal->accelCalReset(); m_okBtn->setEnabled(m_cal->accelCalValid()); } void AccelCalDlg::onCheckAll() { for (int j = 0; j < 3; j++) m_check[j]->setChecked(true); } void AccelCalDlg::onUncheckAll() { for (int j = 0; j < 3; j++) m_check[j]->setChecked(false); } void AccelCalDlg::timerEvent(QTimerEvent *) { QMutexLocker lock(&m_refreshMutex); m_okBtn->setEnabled(m_cal->accelCalValid()); if (m_newData) updateControls(); m_newData = false; } void AccelCalDlg::updateControls() { for (int i = 0; i < 3; i++) { setRaw(m_raw[i], m_currentVal.data(i)); setRawMinMax(m_rawMin[i], m_cal->m_accelMin.data(i)); setRawMinMax(m_rawMax[i], m_cal->m_accelMax.data(i)); } } void AccelCalDlg::setRaw(QLabel *label, float val) { label->setText(QString::number(val, 'f', 4)); if (val < 0) label->setStyleSheet(m_redStyleSheet); else label->setStyleSheet(m_greenStyleSheet); } void AccelCalDlg::setRawMinMax(QLabel *label, float val) { label->setText(QString::number(val, 'f', 4)); if (val < 0) label->setStyleSheet(m_lightRedStyleSheet); else label->setStyleSheet(m_lightGreenStyleSheet); } void AccelCalDlg::layoutWindow() { QHBoxLayout *hLayout; QHBoxLayout *checkLayout; QLabel *label; QSpacerItem *spacer; int gridRow = 0; m_whiteStyleSheet = QString::fromUtf8("background-color: rgb(255, 255, 255);"); m_lightRedStyleSheet = QString::fromUtf8("background-color: rgb(200, 100, 100);"); m_lightGreenStyleSheet = QString::fromUtf8("background-color: rgb(100, 200, 100);"); m_redStyleSheet = QString::fromUtf8("background-color: rgb(220, 80, 80);"); m_greenStyleSheet = QString::fromUtf8("background-color: rgb(38, 244, 54);"); QVBoxLayout *centralLayout = new QVBoxLayout(this); centralLayout->setSpacing(20); centralLayout->setContentsMargins(6, 6, 6, 6); QGridLayout *gridLayout = new QGridLayout(); gridLayout->setSpacing(6); gridLayout->setContentsMargins(4, 4, 4, 4); hLayout = new QHBoxLayout(); hLayout->setSpacing(4); spacer = new QSpacerItem(80, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); label = getFixedLabel("XMin", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("X", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("XMax", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); label = getFixedLabel("YMin", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("Y", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("YMax", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); label = getFixedLabel("ZMin", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("Z", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("ZMax", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); gridLayout->addLayout(hLayout, gridRow++, 0, 1, 1); spacer = new QSpacerItem(20, 24, QSizePolicy::Minimum, QSizePolicy::Expanding); gridLayout->addItem(spacer, gridRow++, 0, 1, 1); // the old row hLayout = new QHBoxLayout(); hLayout->setSpacing(4); label = getFixedLabel("Old ", 80, 20, Qt::AlignLeft | Qt::AlignVCenter); hLayout->addWidget(label); m_oldMin = m_cal->m_accelMin; m_oldMax = m_cal->m_accelMax; for (int j = 0; j < 3; j++) { m_oldRawMin[j] = getFixedLabel(QString::number(m_oldMin.data(j)), 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_oldRawMin[j]); checkLayout = new QHBoxLayout(); checkLayout->setMargin(0); checkLayout->setAlignment(Qt::AlignCenter); m_check[j] = new QCheckBox(); checkLayout->addWidget(m_check[j]); hLayout->addLayout(checkLayout); m_oldRawMax[j] = getFixedLabel(QString::number(m_oldMax.data(j)), 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_oldRawMax[j]); if (j < 2) { spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); } } gridLayout->addLayout(hLayout, gridRow++, 0, 1, 1); spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding); gridLayout->addItem(spacer, gridRow++, 0, 1, 1); // the current row hLayout = new QHBoxLayout(); hLayout->setSpacing(4); label = getFixedLabel("Current ", 80, 20, Qt::AlignLeft | Qt::AlignVCenter); hLayout->addWidget(label); for (int j = 0; j < 3; j++) { m_rawMin[j] = getFixedLabel("0", 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_rawMin[j]); m_raw[j] = getFixedLabel("0", 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_raw[j]); m_rawMax[j] = getFixedLabel("0", 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_rawMax[j]); if (j < 2) { spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); } } gridLayout->addLayout(hLayout, gridRow++, 0, 1, 1); spacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); gridLayout->addItem(spacer, gridRow++, 0, 1, 1); spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding); gridLayout->addItem(spacer, gridRow++, 0, 1, 1); centralLayout->addLayout(gridLayout); m_checkAllBtn = new QPushButton("Check All"); m_uncheckAllBtn = new QPushButton("Uncheck All"); m_resetBtn = new QPushButton("Reset"); m_resetBtn->setDefault(true); m_okBtn = new QPushButton("OK"); m_okBtn->setEnabled(m_cal->accelCalValid()); m_cancelBtn = new QPushButton("Cancel"); hLayout = new QHBoxLayout(); hLayout->addSpacing(24); hLayout->addWidget(m_checkAllBtn); hLayout->addWidget(m_uncheckAllBtn); hLayout->addWidget(m_resetBtn); hLayout->addStretch(); hLayout->addWidget(m_okBtn); hLayout->addWidget(m_cancelBtn); hLayout->addSpacing(24); centralLayout->addLayout(hLayout); } QLabel* AccelCalDlg::getFixedLabel(QString text, int w, int h, Qt::Alignment alignment, QString styleSheet) { QLabel *label = new QLabel(text); label->setMaximumSize(QSize(w + 10, h)); label->setMinimumSize(QSize(w, h)); label->setAlignment(alignment); if (styleSheet.length() > 0) label->setStyleSheet(styleSheet); return label; } rtimulib-7.2.1/Linux/RTIMULibDemo/AccelCalDlg.h000066400000000000000000000054431254201074400210370ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef ACCELCALDLG_H #define ACCELCALDLG_H #include "RTIMULib.h" #include "RTIMUCalDefs.h" #include #include #include #include #include #include #include #include class RTIMUAccelCal; class AccelCalDlg : public QDialog { Q_OBJECT public: AccelCalDlg(QWidget *parent, RTIMUSettings* settings); ~AccelCalDlg(); public slots: void onOk(); void onCancel(); void onReset(); void onCheckAll(); void onUncheckAll(); void newIMUData(const RTIMU_DATA& data); protected: void timerEvent(QTimerEvent *); private: void updateControls(); void setRaw(QLabel *label, float val); void setRawMinMax(QLabel *label, float val); void layoutWindow(); QLabel* getFixedLabel(QString text, int w, int h, Qt::Alignment alignment = Qt::AlignCenter, QString styleSheet = ""); int m_timer; QMutex m_refreshMutex; QString m_whiteStyleSheet; QString m_lightRedStyleSheet; QString m_lightGreenStyleSheet; QString m_redStyleSheet; QString m_greenStyleSheet; RTVector3 m_currentVal; RTVector3 m_oldMin; RTVector3 m_oldMax; QLabel *m_rawMin[3]; QLabel *m_raw[3]; QLabel *m_rawMax[3]; QLabel *m_oldRawMin[3]; QLabel *m_oldRawMax[3]; QCheckBox *m_check[3]; QPushButton *m_okBtn; QPushButton *m_cancelBtn; QPushButton *m_resetBtn; QPushButton *m_checkAllBtn; QPushButton *m_uncheckAllBtn; bool m_newData; RTIMUAccelCal *m_cal; }; #endif // ACCELCALDLG_H rtimulib-7.2.1/Linux/RTIMULibDemo/CMakeLists.txt000066400000000000000000000046451254201074400213530ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014, richards-tech #// #// Permission 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. #// The cmake support was based on work by Moritz Fischer at ettus.com. #// Original copyright notice: # # Copyright 2014 Ettus Research LLC # SET(DEMO_SRCS AccelCalDlg.cpp RTIMULibDemo.cpp IMUThread.cpp MagCalDlg.cpp SelectIMUDlg.cpp SelectFusionDlg.cpp main.cpp) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) SET(CMAKE_AUTOMOC ON) IF(DEFINED QT5) FIND_PACKAGE(Qt5Widgets) FIND_PACKAGE(Qt5Gui) qt5_wrap_ui(UI_HEADERS RTIMULibDemo.ui) ADD_EXECUTABLE(RTIMULibDemo ${DEMO_SRCS} ${UI_HEADERS}) TARGET_LINK_LIBRARIES(RTIMULibDemo RTIMULib) qt5_use_modules(RTIMULibDemo Widgets Gui) INSTALL(TARGETS RTIMULibDemo DESTINATION bin) ELSE(DEFINED QT5) FIND_PACKAGE(Qt4) IF(QT4_FOUND) QT4_WRAP_UI(UI_HEADERS RTIMULibDemo.ui) INCLUDE(${QT_USE_FILE}) ADD_DEFINITIONS(${QT_DEFINITIONS}) ADD_EXECUTABLE(RTIMULibDemo ${DEMO_SRCS} ${UI_HEADERS}) TARGET_LINK_LIBRARIES(RTIMULibDemo RTIMULib ${QT_LIBRARIES}) INSTALL(TARGETS RTIMULibDemo DESTINATION bin) ELSE(QT4_FOUND) MESSAGE(STATUS "Qt4 not found") ENDIF(QT4_FOUND) ENDIF(DEFINED QT5) rtimulib-7.2.1/Linux/RTIMULibDemo/IMUThread.cpp000066400000000000000000000116771254201074400211040ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "IMUThread.h" #include "IMUDrivers/RTPressure.h" #include "IMUDrivers/RTHumidity.h" #include IMUThread::IMUThread() : QObject() { m_imu = NULL; m_pressure = NULL; m_humidity = NULL; m_calibrationMode = false; m_settings = new RTIMUSettings(); // just use default name (RTIMULib.ini) for settings file m_timer = -1; } IMUThread::~IMUThread() { } void IMUThread::newIMU() { if (m_imu != NULL) { delete m_imu; m_imu = NULL; } if (m_timer != -1) { killTimer(m_timer); m_timer = -1; } m_imu = RTIMU::createIMU(m_settings); if (m_imu == NULL) return; // set up IMU m_imu->IMUInit(); m_timer = startTimer(m_imu->IMUGetPollInterval()); } void IMUThread::newPressure() { if (m_pressure != NULL) { delete m_pressure; m_pressure = NULL; } m_pressure = RTPressure::createPressure(m_settings); if (m_pressure == NULL) return; // set up pressure sensor m_pressure->pressureInit(); } void IMUThread::newHumidity() { if (m_humidity != NULL) { delete m_humidity; m_humidity = NULL; } m_humidity = RTHumidity::createHumidity(m_settings); if (m_humidity == NULL) return; // set up humidity sensor m_humidity->humidityInit(); } void IMUThread::initThread() { // create IMU. There's a special function call for this // as it makes sure that the required one is created as specified in the settings. m_imu = RTIMU::createIMU(m_settings); if (m_imu == NULL) { qDebug() << "No IMU found."; return; } // set up IMU m_imu->IMUInit(); // create pressure sensor. There's a special function call for this // as it makes sure that the required one is created as specified in the settings. m_pressure = RTPressure::createPressure(m_settings); newPressure(); newHumidity(); // poll at the rate suggested bu the IMU m_timer = startTimer(m_imu->IMUGetPollInterval()); // up the priority in case it's helpful m_thread->setPriority(QThread::TimeCriticalPriority); } void IMUThread::finishThread() { if (m_timer != -1) killTimer(m_timer); m_timer = -1; if (m_imu != NULL) delete m_imu; m_imu = NULL; if (m_pressure != NULL) delete m_pressure; m_pressure = NULL; if (m_humidity != NULL) delete m_humidity; m_humidity = NULL; delete m_settings; } void IMUThread::timerEvent(QTimerEvent * /* event */) { // check for valid IMU if (m_imu == NULL) return; if (m_imu->IMUType() == RTIMU_TYPE_NULL) return; // loop here to clear all samples just in case things aren't keeping up while (m_imu->IMURead()) { if (m_calibrationMode) { emit newCalData(m_imu->getCompass()); } else { RTIMU_DATA data = m_imu->getIMUData(); if (m_pressure != NULL) m_pressure->pressureRead(data); if (m_humidity != NULL) m_humidity->humidityRead(data); emit newIMUData(data); } } } //---------------------------------------------------------- // // The following is some Qt threading stuff void IMUThread::resumeThread() { m_thread = new QThread(); moveToThread(m_thread); connect(m_thread, SIGNAL(started()), this, SLOT(internalRunLoop())); connect(this, SIGNAL(internalEndThread()), this, SLOT(cleanup())); connect(this, SIGNAL(internalKillThread()), m_thread, SLOT(quit())); connect(m_thread, SIGNAL(finished()), m_thread, SLOT(deleteLater())); connect(m_thread, SIGNAL(finished()), this, SLOT(deleteLater())); m_thread->start(); } rtimulib-7.2.1/Linux/RTIMULibDemo/IMUThread.h000066400000000000000000000056351254201074400205460ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _IMUTHREAD_H #define _IMUTHREAD_H #include #include "RTIMULib.h" class RTPressure; class RTHumidity; class IMUThread : public QObject { Q_OBJECT public: IMUThread(); virtual ~IMUThread(); // resumeThread() is called when init is complete void resumeThread(); // exitThread is called to terminate and delete the thread void exitThread() { emit internalEndThread(); } RTIMUSettings *getSettings() { return m_settings; } void setCalibrationMode(bool enable); void newCompassCalData(const RTVector3& compassCalMin, const RTVector3& compassCalMax); RTIMU *getIMU() { return m_imu; } RTPressure *getPressure() { return m_pressure; } RTHumidity *getHumidity() { return m_humidity; } public slots: void internalRunLoop() { initThread(); emit running();} void cleanup() {finishThread(); emit internalKillThread(); } void newIMU(); void newPressure(); void newHumidity(); signals: void running(); // emitted when everything set up and thread active void internalEndThread(); // this to end thread void internalKillThread(); // tells the QThread to quit void newCalData(const RTVector3& compass); // this is uncalibrated compass data emitted in cal mode void newIMUData(const RTIMU_DATA& data); // this contains the latest data form the IMU protected: void initThread(); void finishThread(); void timerEvent(QTimerEvent *event); private: int m_timer; RTIMUSettings *m_settings; RTIMU *m_imu; RTPressure *m_pressure; RTHumidity *m_humidity; bool m_calibrationMode; QThread *m_thread; }; #endif // _IMUTHREAD_H rtimulib-7.2.1/Linux/RTIMULibDemo/MagCalDlg.cpp000066400000000000000000000255251254201074400210720ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "MagCalDlg.h" #include "RTIMUMagCal.h" #include #include #include #include #include #include #include MagCalDlg::MagCalDlg(QWidget *parent, RTIMUSettings* settings) : QDialog(parent) { m_cal = new RTIMUMagCal(settings); m_newData = false; m_fitDirOptions.append("./RTEllipsoidFit/"); m_fitDirOptions.append("../RTEllipsoidFit/"); m_fitDirOptions.append("../../RTEllipsoidFit/"); findFitDir(); m_cal->magCalInit(); m_minMaxMode = true; layoutWindow(); setButtonEnables(); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); m_timer = startTimer(50); connect(m_cancelBtn, SIGNAL(clicked()), this, SLOT(onCancel())); connect(m_resetBtn, SIGNAL(clicked()), this, SLOT(onReset())); connect(m_saveMinMaxBtn, SIGNAL(clicked()), this, SLOT(onSaveMinMax())); connect(m_processEllipsoidBtn, SIGNAL(clicked()), this, SLOT(onProcess())); } MagCalDlg::~MagCalDlg() { } void MagCalDlg::newIMUData(const RTIMU_DATA& data) { QMutexLocker lock(&m_refreshMutex); m_currentVal = data.compass; if (m_minMaxMode) m_cal->newMinMaxData(data.compass); else m_cal->newEllipsoidData(data.compass); m_newData = true; } void MagCalDlg::findFitDir() { m_fitDir = ""; m_usingEllipsoidFit = false; for (int i = 0; i < m_fitDirOptions.count(); i++) { QFileInfo file(m_fitDirOptions.at(i) + RTIMUCALDEFS_OCTAVE_CODE); if (file.exists()) { m_fitDir = m_fitDirOptions.at(i); m_usingEllipsoidFit = true; return; } } } void MagCalDlg::onCancel() { killTimer(m_timer); reject(); } void MagCalDlg::onReset() { m_cal->magCalReset(); m_minMaxMode = true; setButtonEnables(); } void MagCalDlg::onSaveMinMax() { m_cal->magCalSaveMinMax(); if (m_usingEllipsoidFit) { m_minMaxMode = false; setButtonEnables(); } else { accept(); } } void MagCalDlg::onProcess() { m_cal->magCalSaveRaw(qPrintable(m_fitDir)); QProcess proc; proc.setWorkingDirectory(m_fitDir); proc.start(RTIMUCALDEFS_OCTAVE_COMMAND); proc.waitForFinished(20000); if (proc.exitCode() == 0) { m_cal->magCalSaveCorr(qPrintable(m_fitDir)); } else { QMessageBox::warning(this, "Ellipsoid fit error", "Failed to execute RTEllipsoidFit.m. Only min/max calibration available", QMessageBox::Ok); } accept(); } void MagCalDlg::timerEvent(QTimerEvent *) { QMutexLocker lock(&m_refreshMutex); if (m_newData) updateControls(); m_newData = false; } void MagCalDlg::updateControls() { for (int i = 0; i < 3; i++) { setRaw(m_raw[i], m_currentVal.data(i)); setRawMinMax(m_rawMin[i], m_cal->m_magMin.data(i)); setRawMinMax(m_rawMax[i], m_cal->m_magMax.data(i)); } if (m_usingEllipsoidFit) setOctantCounts(); if (m_minMaxMode) { if (!m_saveMinMaxBtn->isEnabled() && m_cal->magCalValid()) m_saveMinMaxBtn->setEnabled(true); } else { if (!m_processEllipsoidBtn->isEnabled() && m_cal->magCalEllipsoidValid()) m_processEllipsoidBtn->setEnabled(true); } } void MagCalDlg::setRaw(QLabel *label, float val) { label->setText(QString::number(val, 'f', 4)); if (val < 0) label->setStyleSheet(m_redStyleSheet); else label->setStyleSheet(m_greenStyleSheet); } void MagCalDlg::setRawMinMax(QLabel *label, float val) { label->setText(QString::number(val, 'f', 4)); if (val < 0) label->setStyleSheet(m_lightRedStyleSheet); else label->setStyleSheet(m_lightGreenStyleSheet); } void MagCalDlg::setOctantCounts() { int counts[RTIMUCALDEFS_OCTANT_COUNT]; m_cal->magCalOctantCounts(counts); for (int i = 0; i < RTIMUCALDEFS_OCTANT_COUNT; i++) { m_octantCount[i]->setText(QString::number(counts[i])); } } void MagCalDlg::layoutWindow() { QHBoxLayout *hLayout; QLabel *label; QSpacerItem *spacer; int gridRow = 0; QString octantNames[] = {"---", "+--", "-+-", "++-", "--+", "+-+", "-++", "+++"}; m_whiteStyleSheet = QString::fromUtf8("background-color: rgb(255, 255, 255);"); m_lightRedStyleSheet = QString::fromUtf8("background-color: rgb(200, 100, 100);"); m_lightGreenStyleSheet = QString::fromUtf8("background-color: rgb(100, 200, 100);"); m_redStyleSheet = QString::fromUtf8("background-color: rgb(220, 80, 80);"); m_greenStyleSheet = QString::fromUtf8("background-color: rgb(38, 244, 54);"); QVBoxLayout *centralLayout = new QVBoxLayout(this); centralLayout->setSpacing(20); centralLayout->setContentsMargins(6, 6, 6, 6); QGridLayout *gridLayout = new QGridLayout(); gridLayout->setSpacing(6); gridLayout->setContentsMargins(4, 4, 4, 4); hLayout = new QHBoxLayout(); hLayout->setSpacing(4); spacer = new QSpacerItem(80, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); label = getFixedLabel("XMin", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("X", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("XMax", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); label = getFixedLabel("YMin", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("Y", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("YMax", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); label = getFixedLabel("ZMin", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("Z", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("ZMax", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); gridLayout->addLayout(hLayout, gridRow++, 0, 1, 1); spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding); gridLayout->addItem(spacer, gridRow++, 0, 1, 1); // the current row hLayout = new QHBoxLayout(); hLayout->setSpacing(4); label = getFixedLabel("Current ", 80, 20, Qt::AlignLeft | Qt::AlignVCenter); hLayout->addWidget(label); for (int j = 0; j < 3; j++) { m_rawMin[j] = getFixedLabel("0", 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_rawMin[j]); m_raw[j] = getFixedLabel("0", 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_raw[j]); m_rawMax[j] = getFixedLabel("0", 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_rawMax[j]); if (j < 2) { spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); } } gridLayout->addLayout(hLayout, gridRow++, 0, 1, 1); spacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); gridLayout->addItem(spacer, gridRow++, 0, 1, 1); centralLayout->addLayout(gridLayout); if (m_usingEllipsoidFit) { // Do octant displays centralLayout->addWidget(new QLabel("Octant counts:")); QGridLayout *octantLayout = new QGridLayout(); octantLayout->setSpacing(6); octantLayout->setContentsMargins(4, 4, 4, 4); for (int i = 0; i < RTIMUCALDEFS_OCTANT_COUNT; i++) { QHBoxLayout *hOctant = new QHBoxLayout(); QLabel *label = getFixedLabel(octantNames[i], 50, 20, Qt::AlignCenter, m_whiteStyleSheet); m_octantCount[i] = getFixedLabel("0", 50, 20, Qt::AlignCenter, m_whiteStyleSheet); hOctant->addWidget(label); hOctant->addWidget(m_octantCount[i]); octantLayout->addLayout(hOctant, i / 4, i % 4); } centralLayout->addLayout(octantLayout); } QHBoxLayout *hBox = new QHBoxLayout(); centralLayout->addLayout(hBox); QFormLayout *formLayout = new QFormLayout(); hBox->addLayout(formLayout); hBox->setAlignment(Qt::AlignHCenter); QHBoxLayout *buttonLayout = new QHBoxLayout(); m_processEllipsoidBtn = new QPushButton("Process ellipsoid"); m_saveMinMaxBtn = new QPushButton("Save min/max"); m_resetBtn = new QPushButton("Reset"); m_cancelBtn = new QPushButton("Cancel"); buttonLayout->addWidget(m_resetBtn); buttonLayout->addWidget(m_saveMinMaxBtn); if (m_usingEllipsoidFit) buttonLayout->addWidget(m_processEllipsoidBtn); buttonLayout->addWidget(m_cancelBtn); hBox->addLayout(buttonLayout); hLayout = new QHBoxLayout(); hLayout->addSpacing(24); hLayout->addStretch(); centralLayout->addLayout(hLayout); } QLabel* MagCalDlg::getFixedLabel(QString text, int w, int h, Qt::Alignment alignment, QString styleSheet) { QLabel *label = new QLabel(text); label->setMaximumSize(QSize(w + 10, h)); label->setMinimumSize(QSize(w, h)); label->setAlignment(alignment); if (styleSheet.length() > 0) label->setStyleSheet(styleSheet); return label; } void MagCalDlg::setButtonEnables() { m_saveMinMaxBtn->setEnabled(false); if (m_usingEllipsoidFit) m_processEllipsoidBtn->setEnabled(false); if (m_minMaxMode) { setWindowTitle("Magnetomer Calibration - collecting min/max data"); } else { setWindowTitle(QString("Magnetomer Calibration - collecting ellipsoid data (need %1 in each octant)") .arg(RTIMUCALDEFS_OCTANT_MIN_SAMPLES)); } } rtimulib-7.2.1/Linux/RTIMULibDemo/MagCalDlg.h000066400000000000000000000055361254201074400205370ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef MAGCALDLG_H #define MAGCALDLG_H #include "RTIMULib.h" #include "RTIMUCalDefs.h" #include #include #include #include #include #include #include #include class RTIMUMagCal; class MagCalDlg : public QDialog { Q_OBJECT public: MagCalDlg(QWidget *parent, RTIMUSettings* settings); ~MagCalDlg(); public slots: void onCancel(); void onReset(); void onSaveMinMax(); void onProcess(); void newIMUData(const RTIMU_DATA& data); protected: void timerEvent(QTimerEvent *); private: void updateControls(); void setRaw(QLabel *label, float val); void setRawMinMax(QLabel *label, float val); void setOctantCounts(); void setButtonEnables(); void findFitDir(); void layoutWindow(); QLabel* getFixedLabel(QString text, int w, int h, Qt::Alignment alignment = Qt::AlignCenter, QString styleSheet = ""); int m_timer; QMutex m_refreshMutex; QString m_whiteStyleSheet; QString m_lightRedStyleSheet; QString m_lightGreenStyleSheet; QString m_redStyleSheet; QString m_greenStyleSheet; RTVector3 m_currentVal; QLabel *m_rawMin[3]; QLabel *m_raw[3]; QLabel *m_rawMax[3]; QLabel *m_octantCount[RTIMUCALDEFS_OCTANT_COUNT]; QPushButton *m_resetBtn; QPushButton *m_saveMinMaxBtn; QPushButton *m_processEllipsoidBtn; QPushButton *m_cancelBtn; bool m_newData; bool m_minMaxMode; RTIMUMagCal *m_cal; QString m_fitDir; QStringList m_fitDirOptions; bool m_usingEllipsoidFit; }; #endif // MAGCALDLG_H rtimulib-7.2.1/Linux/RTIMULibDemo/RTIMULibDemo.cpp000066400000000000000000000377601254201074400214570ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include #include #include #include "RTIMULibDemo.h" #include "SelectIMUDlg.h" #include "SelectFusionDlg.h" #include "IMUThread.h" #include "AccelCalDlg.h" #include "MagCalDlg.h" #define RATE_TIMER_INTERVAL 2 RTIMULibDemo::RTIMULibDemo() : QMainWindow() { // This is some normal Qt GUI stuff ui.setupUi(this); layoutWindow(); layoutStatusBar(); // This code connects up signals from the GUI connect(ui.actionExit, SIGNAL(triggered()), this, SLOT(close())); connect(ui.actionSelectFusionAlgorithm, SIGNAL(triggered()), this, SLOT(onSelectFusionAlgorithm())); connect(ui.actionCalibrateAccelerometers, SIGNAL(triggered()), this, SLOT(onCalibrateAccelerometers())); connect(ui.actionCalibrateMagnetometers, SIGNAL(triggered()), this, SLOT(onCalibrateMagnetometers())); connect(ui.actionSelectIMU, SIGNAL(triggered()), this, SLOT(onSelectIMU())); connect(m_enableGyro, SIGNAL(stateChanged(int)), this, SLOT(onEnableGyro(int))); connect(m_enableAccel, SIGNAL(stateChanged(int)), this, SLOT(onEnableAccel(int))); connect(m_enableCompass, SIGNAL(stateChanged(int)), this, SLOT(onEnableCompass(int))); connect(m_enableDebug, SIGNAL(stateChanged(int)), this, SLOT(onEnableDebug(int))); // create the imu thread and connect up the signal m_imuThread = new IMUThread(); connect(m_imuThread, SIGNAL(newIMUData(const RTIMU_DATA&)), this, SLOT(newIMUData(const RTIMU_DATA&)), Qt::DirectConnection); connect(this, SIGNAL(newIMU()), m_imuThread, SLOT(newIMU())); m_imuThread->resumeThread(); // This value allows a sample rate to be calculated m_sampleCount = 0; // start some timers to get things going m_rateTimer = startTimer(RATE_TIMER_INTERVAL * 1000); // Only update the display 10 times per second to keep CPU reasonable m_displayTimer = startTimer(100); m_fusionType->setText(RTFusion::fusionName(m_imuThread->getSettings()->m_fusionType)); } RTIMULibDemo::~RTIMULibDemo() { } void RTIMULibDemo::onSelectFusionAlgorithm() { SelectFusionDlg dlg(m_imuThread->getSettings(), this); if (dlg.exec() == QDialog::Accepted) { emit newIMU(); m_fusionType->setText(RTFusion::fusionName(m_imuThread->getSettings()->m_fusionType)); } } void RTIMULibDemo::onSelectIMU() { SelectIMUDlg dlg(m_imuThread->getSettings(), this); if (dlg.exec() == QDialog::Accepted) { emit newIMU(); } } void RTIMULibDemo::onCalibrateAccelerometers() { m_imuThread->getIMU()->setAccelCalibrationMode(true); AccelCalDlg dlg(this, m_imuThread->getSettings()); connect(m_imuThread, SIGNAL(newIMUData(const RTIMU_DATA&)), &dlg, SLOT(newIMUData(const RTIMU_DATA&)), Qt::DirectConnection); if (dlg.exec() == QDialog::Accepted) { } disconnect(m_imuThread, SIGNAL(newIMUData(const RTIMU_DATA&)), &dlg, SLOT(newIMUData(const RTIMU_DATA&))); emit newIMU(); } void RTIMULibDemo::onCalibrateMagnetometers() { m_imuThread->getIMU()->setCompassCalibrationMode(true); MagCalDlg dlg(this, m_imuThread->getSettings()); connect(m_imuThread, SIGNAL(newIMUData(const RTIMU_DATA&)), &dlg, SLOT(newIMUData(const RTIMU_DATA&)), Qt::DirectConnection); if (dlg.exec() == QDialog::Accepted) { } disconnect(m_imuThread, SIGNAL(newIMUData(const RTIMU_DATA&)), &dlg, SLOT(newIMUData(const RTIMU_DATA&))); emit newIMU(); } void RTIMULibDemo::newIMUData(const RTIMU_DATA& data) { m_imuData = data; m_sampleCount++; } void RTIMULibDemo::onEnableGyro(int state) { if (m_imuThread->getIMU() != NULL) m_imuThread->getIMU()->setGyroEnable(state == Qt::Checked); } void RTIMULibDemo::onEnableAccel(int state) { if (m_imuThread->getIMU() != NULL) m_imuThread->getIMU()->setAccelEnable(state == Qt::Checked); } void RTIMULibDemo::onEnableCompass(int state) { if (m_imuThread->getIMU() != NULL) m_imuThread->getIMU()->setCompassEnable(state == Qt::Checked); } void RTIMULibDemo::onEnableDebug(int state) { if (m_imuThread->getIMU() != NULL) m_imuThread->getIMU()->setDebugEnable(state == Qt::Checked); } void RTIMULibDemo::closeEvent(QCloseEvent *) { killTimer(m_displayTimer); killTimer(m_rateTimer); m_imuThread->exitThread(); } void RTIMULibDemo::timerEvent(QTimerEvent *event) { if (event->timerId() == m_displayTimer) { // Update the GUI m_gyroX->setText(QString::number(m_imuData.gyro.x(), 'f', 6)); m_gyroY->setText(QString::number(m_imuData.gyro.y(), 'f', 6)); m_gyroZ->setText(QString::number(m_imuData.gyro.z(), 'f', 6)); m_accelX->setText(QString::number(m_imuData.accel.x(), 'f', 6)); m_accelY->setText(QString::number(m_imuData.accel.y(), 'f', 6)); m_accelZ->setText(QString::number(m_imuData.accel.z(), 'f', 6)); m_compassX->setText(QString::number(m_imuData.compass.x(), 'f', 6)); m_compassY->setText(QString::number(m_imuData.compass.y(), 'f', 6)); m_compassZ->setText(QString::number(m_imuData.compass.z(), 'f', 6)); m_fusionPoseX->setText(QString::number(m_imuData.fusionPose.x() * RTMATH_RAD_TO_DEGREE, 'f', 6)); m_fusionPoseY->setText(QString::number(m_imuData.fusionPose.y() * RTMATH_RAD_TO_DEGREE, 'f', 6)); m_fusionPoseZ->setText(QString::number(m_imuData.fusionPose.z() * RTMATH_RAD_TO_DEGREE, 'f', 6)); m_fusionQPoseScalar->setText(QString::number(m_imuData.fusionQPose.scalar(), 'f', 6)); m_fusionQPoseX->setText(QString::number(m_imuData.fusionQPose.x(), 'f', 6)); m_fusionQPoseY->setText(QString::number(m_imuData.fusionQPose.y(), 'f', 6)); m_fusionQPoseZ->setText(QString::number(m_imuData.fusionQPose.z(), 'f', 6)); m_accelMagnitude->setText(QString::number(m_imuData.accel.length(), 'f', 6)); m_compassMagnitude->setText(QString::number(m_imuData.compass.length(), 'f', 6)); if (m_imuData.pressureValid) { m_pressure->setText(QString::number(m_imuData.pressure, 'f', 2)); m_height->setText(QString::number(RTMath::convertPressureToHeight(m_imuData.pressure), 'f', 2)); } else { m_pressure->setText("0"); m_height->setText("0"); } if (m_imuData.humidityValid) { m_humidity->setText(QString::number(m_imuData.humidity, 'f', 2)); } else { m_humidity->setText("0"); } if (m_imuData.temperatureValid) m_temperature->setText(QString::number(m_imuData.temperature, 'f', 2)); else m_temperature->setText("0"); if (m_imuThread->getIMU() != NULL) { RTVector3 residuals = m_imuThread->getIMU()->getAccelResiduals(); m_accelResidualX->setText(QString::number(residuals.x(), 'f', 6)); m_accelResidualY->setText(QString::number(residuals.y(), 'f', 6)); m_accelResidualZ->setText(QString::number(residuals.z(), 'f', 6)); } } else { // Update the sample rate float rate = (float)m_sampleCount / (float(RATE_TIMER_INTERVAL)); m_sampleCount = 0; m_rateStatus->setText(QString("Sample rate: %1 per second").arg(rate)); if (m_imuThread->getIMU() == NULL) { m_calStatus->setText("No IMU found"); } else { m_calStatus->setText(QString("Accel %1 : Compass %2 : Ellipsoid %3") .arg(m_imuThread->getIMU()->getAccelCalibrationValid() ? "calibrated" : "uncalibrated") .arg(m_imuThread->getIMU()->getCompassCalibrationValid() ? "calibrated" : "uncalibrated") .arg(m_imuThread->getIMU()->getCompassCalibrationEllipsoidValid() ? "in use" : "not in use")); } if (m_imuThread->getIMU() != NULL) { m_imuType->setText(m_imuThread->getIMU()->IMUName()); if (!m_imuThread->getIMU()->IMUGyroBiasValid()) m_biasStatus->setText("Gyro bias being calculated - keep IMU still!"); else m_biasStatus->setText("Gyro bias valid"); } } } void RTIMULibDemo::layoutWindow() { QVBoxLayout *vLayout = new QVBoxLayout(); vLayout->setContentsMargins(3, 3, 3, 3); vLayout->setSpacing(3); QHBoxLayout *imuLayout = new QHBoxLayout(); vLayout->addLayout(imuLayout); imuLayout->addWidget(new QLabel("IMU type: ")); m_imuType = new QLabel(); imuLayout->addWidget(m_imuType); imuLayout->setStretch(1, 1); vLayout->addSpacing(10); QHBoxLayout *biasLayout = new QHBoxLayout(); vLayout->addLayout(biasLayout); biasLayout->addWidget(new QLabel("Gyro bias status: ")); m_biasStatus = new QLabel(); biasLayout->addWidget(m_biasStatus); biasLayout->setStretch(1, 1); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Fusion state (quaternion): ")); QHBoxLayout *dataLayout = new QHBoxLayout(); dataLayout->setAlignment(Qt::AlignLeft); m_fusionQPoseScalar = getFixedPanel("1"); m_fusionQPoseX = getFixedPanel("0"); m_fusionQPoseY = getFixedPanel("0"); m_fusionQPoseZ = getFixedPanel("0"); dataLayout->addSpacing(30); dataLayout->addWidget(m_fusionQPoseScalar); dataLayout->addWidget(m_fusionQPoseX); dataLayout->addWidget(m_fusionQPoseY); dataLayout->addWidget(m_fusionQPoseZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Pose - roll, pitch, yaw (degrees): ")); m_fusionPoseX = getFixedPanel("0"); m_fusionPoseY = getFixedPanel("0"); m_fusionPoseZ = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->setAlignment(Qt::AlignLeft); dataLayout->addSpacing(30); dataLayout->addWidget(m_fusionPoseX); dataLayout->addWidget(m_fusionPoseY); dataLayout->addWidget(m_fusionPoseZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Gyros (radians/s): ")); m_gyroX = getFixedPanel("0"); m_gyroY = getFixedPanel("0"); m_gyroZ = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->setAlignment(Qt::AlignLeft); dataLayout->addSpacing(30); dataLayout->addWidget(m_gyroX); dataLayout->addWidget(m_gyroY); dataLayout->addWidget(m_gyroZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Accelerometers (g): ")); m_accelX = getFixedPanel("0"); m_accelY = getFixedPanel("0"); m_accelZ = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->addSpacing(30); dataLayout->setAlignment(Qt::AlignLeft); dataLayout->addWidget(m_accelX); dataLayout->addWidget(m_accelY); dataLayout->addWidget(m_accelZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Accelerometer magnitude (g): ")); m_accelMagnitude = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->addSpacing(30); dataLayout->addWidget(m_accelMagnitude); dataLayout->setAlignment(Qt::AlignLeft); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Accelerometer residuals (g): ")); m_accelResidualX = getFixedPanel("0"); m_accelResidualY = getFixedPanel("0"); m_accelResidualZ = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->addSpacing(30); dataLayout->setAlignment(Qt::AlignLeft); dataLayout->addWidget(m_accelResidualX); dataLayout->addWidget(m_accelResidualY); dataLayout->addWidget(m_accelResidualZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Magnetometers (uT): ")); m_compassX = getFixedPanel("0"); m_compassY = getFixedPanel("0"); m_compassZ = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->setAlignment(Qt::AlignLeft); dataLayout->addSpacing(30); dataLayout->addWidget(m_compassX); dataLayout->addWidget(m_compassY); dataLayout->addWidget(m_compassZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Compass magnitude (uT): ")); m_compassMagnitude = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->addSpacing(30); dataLayout->addWidget(m_compassMagnitude); dataLayout->setAlignment(Qt::AlignLeft); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Pressure (hPa), height above sea level (m): ")); m_pressure = getFixedPanel("0"); m_height = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->addSpacing(30); dataLayout->addWidget(m_pressure); dataLayout->addWidget(m_height); dataLayout->setAlignment(Qt::AlignLeft); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Temperature (C): ")); m_temperature = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->addSpacing(30); dataLayout->addWidget(m_temperature); dataLayout->setAlignment(Qt::AlignLeft); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Humidity (RH): ")); m_humidity = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->addSpacing(30); dataLayout->addWidget(m_humidity); dataLayout->setAlignment(Qt::AlignLeft); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); QHBoxLayout *fusionBox = new QHBoxLayout(); QLabel *fusionTypeLabel = new QLabel("Fusion algorithm: "); fusionBox->addWidget(fusionTypeLabel); fusionTypeLabel->setMaximumWidth(150); m_fusionType = new QLabel(); fusionBox->addWidget(m_fusionType); vLayout->addLayout(fusionBox); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Fusion controls: ")); m_enableGyro = new QCheckBox("Enable gyros"); m_enableGyro->setChecked(true); vLayout->addWidget(m_enableGyro); m_enableAccel = new QCheckBox("Enable accels"); m_enableAccel->setChecked(true); vLayout->addWidget(m_enableAccel); m_enableCompass = new QCheckBox("Enable compass"); m_enableCompass->setChecked(true); vLayout->addWidget(m_enableCompass); m_enableDebug = new QCheckBox("Enable debug messages"); m_enableDebug->setChecked(false); vLayout->addWidget(m_enableDebug); vLayout->addStretch(1); centralWidget()->setLayout(vLayout); setFixedSize(750, 700); } QLabel* RTIMULibDemo::getFixedPanel(QString text) { QLabel *label = new QLabel(text); label->setFrameStyle(QFrame::Panel); label->setFixedSize(QSize(100, 16)); return label; } void RTIMULibDemo::layoutStatusBar() { m_rateStatus = new QLabel(this); m_rateStatus->setAlignment(Qt::AlignLeft); ui.statusBar->addWidget(m_rateStatus, 1); m_calStatus = new QLabel(this); m_calStatus->setAlignment(Qt::AlignLeft); ui.statusBar->addWidget(m_calStatus, 0); } rtimulib-7.2.1/Linux/RTIMULibDemo/RTIMULibDemo.h000066400000000000000000000064161254201074400211160ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTIMULIBDEMO_H #define _RTIMULIBDEMO_H #include #include #include #include "ui_RTIMULibDemo.h" #include "RTIMULib.h" class IMUThread; class RTIMULibDemo : public QMainWindow { Q_OBJECT public: RTIMULibDemo(); ~RTIMULibDemo(); public slots: void onSelectFusionAlgorithm(); void onCalibrateAccelerometers(); void onCalibrateMagnetometers(); void onSelectIMU(); void onEnableGyro(int); void onEnableAccel(int); void onEnableCompass(int); void onEnableDebug(int); void newIMUData(const RTIMU_DATA&); signals: void newIMU(); protected: void timerEvent(QTimerEvent *event); void closeEvent(QCloseEvent *event); private: void layoutStatusBar(); void layoutWindow(); QLabel *getFixedPanel(QString text); IMUThread *m_imuThread; // the thread that operates the imu RTIMU_DATA m_imuData; // this holds the IMU information and funsion output // Qt GUI stuff Ui::RTIMULibDemoClass ui; QLabel *m_fusionQPoseScalar; QLabel *m_fusionQPoseX; QLabel *m_fusionQPoseY; QLabel *m_fusionQPoseZ; QLabel *m_fusionPoseX; QLabel *m_fusionPoseY; QLabel *m_fusionPoseZ; QLabel *m_gyroX; QLabel *m_gyroY; QLabel *m_gyroZ; QLabel *m_accelX; QLabel *m_accelY; QLabel *m_accelZ; QLabel *m_accelMagnitude; QLabel *m_accelResidualX; QLabel *m_accelResidualY; QLabel *m_accelResidualZ; QLabel *m_compassX; QLabel *m_compassY; QLabel *m_compassZ; QLabel *m_compassMagnitude; QLabel *m_pressure; QLabel *m_height; QLabel *m_temperature; QLabel *m_humidity; QLabel *m_fusionType; QCheckBox *m_enableGyro; QCheckBox *m_enableAccel; QCheckBox *m_enableCompass; QCheckBox *m_enableDebug; QLabel *m_imuType; QLabel *m_biasStatus; QLabel *m_rateStatus; QLabel *m_calStatus; int m_rateTimer; int m_displayTimer; int m_sampleCount; }; #endif // _RTIMULIBDEMO_H rtimulib-7.2.1/Linux/RTIMULibDemo/RTIMULibDemo.pri000066400000000000000000000032111254201074400214470ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014-2015, richards-tech, LLC #// #// Permission 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. INCLUDEPATH += $$PWD DEPENDPATH += $$PWD HEADERS += RTIMULibDemo.h \ SelectIMUDlg.h \ SelectFusionDlg.h \ IMUThread.h \ AccelCalDlg.h \ MagCalDlg.h \ SOURCES += main.cpp \ RTIMULibDemo.cpp \ SelectIMUDlg.cpp \ SelectFusionDlg.cpp \ IMUThread.cpp \ AccelCalDlg.cpp \ MagCalDlg.cpp \ FORMS += RTIMULibDemo.ui rtimulib-7.2.1/Linux/RTIMULibDemo/RTIMULibDemo.pro000066400000000000000000000033241254201074400214620ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014-2015, richards-tech, LLC #// #// Permission 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. greaterThan(QT_MAJOR_VERSION, 4): cache() TEMPLATE = app TARGET = RTIMULibDemo DESTDIR = Output QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += debug_and_release target.path = /usr/local/bin INSTALLS += target DEFINES += QT_NETWORK_LIB INCLUDEPATH += GeneratedFiles MOC_DIR += GeneratedFiles/moc OBJECTS_DIR += objects UI_DIR += GeneratedFiles RCC_DIR += GeneratedFiles include(RTIMULibDemo.pri) include(../../RTIMULib/RTIMULib.pri) rtimulib-7.2.1/Linux/RTIMULibDemo/RTIMULibDemo.ui000066400000000000000000000052631254201074400213030ustar00rootroot00000000000000 RTIMULibDemoClass 0 0 658 562 0 0 RTIMULibDemo 400 300 0 0 658 25 Actions toolBar TopToolBarArea false Select IMU Select fusion algorithm Calibrate magnetometers Calibrate accelerometers Exit rtimulib-7.2.1/Linux/RTIMULibDemo/SelectFusionDlg.cpp000066400000000000000000000053121254201074400223410ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "SelectFusionDlg.h" #include "RTIMUSettings.h" #include "RTFusion.h" #include #include SelectFusionDlg::SelectFusionDlg(RTIMUSettings *settings, QWidget *parent) : QDialog(parent, Qt::WindowCloseButtonHint | Qt::WindowTitleHint) { m_settings = settings; layoutWindow(); setWindowTitle("Select Fusion algorithm"); connect(m_buttons, SIGNAL(accepted()), this, SLOT(onOk())); connect(m_buttons, SIGNAL(rejected()), this, SLOT(onCancel())); } SelectFusionDlg::~SelectFusionDlg() { } void SelectFusionDlg::onOk() { m_settings->m_fusionType = m_selectFusion->currentIndex(); m_settings->saveSettings(); accept(); } void SelectFusionDlg::onCancel() { reject(); } void SelectFusionDlg::layoutWindow() { QVBoxLayout *mainLayout; QFormLayout *form; setModal(true); mainLayout = new QVBoxLayout(this); mainLayout->setSpacing(20); mainLayout->setContentsMargins(11, 11, 11, 11); form = new QFormLayout(); mainLayout->addLayout(form); m_selectFusion = new QComboBox(); for (int i = 0; i < RTFUSION_TYPE_COUNT; i++) m_selectFusion->addItem(RTFusion::fusionName(i)); m_selectFusion->setCurrentIndex(m_settings->m_fusionType); form->addRow("Select Fusion algorithm type: ", m_selectFusion); m_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this); m_buttons->setCenterButtons(true); mainLayout->addWidget(m_buttons); } rtimulib-7.2.1/Linux/RTIMULibDemo/SelectFusionDlg.h000066400000000000000000000033471254201074400220140ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef SELECTFUSIONDLG_H #define SELECTFUSIONDLG_H #include #include #include class RTIMUSettings; class SelectFusionDlg : public QDialog { Q_OBJECT public: SelectFusionDlg(RTIMUSettings *settings, QWidget *parent = 0); ~SelectFusionDlg(); public slots: void onOk(); void onCancel(); private: void layoutWindow(); RTIMUSettings *m_settings; QDialogButtonBox *m_buttons; QComboBox *m_selectFusion; }; #endif // SELECTFUSIONDLG_H rtimulib-7.2.1/Linux/RTIMULibDemo/SelectIMUDlg.cpp000066400000000000000000000214131254201074400215300ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "SelectIMUDlg.h" #include "RTIMUSettings.h" #include "IMUDrivers/RTIMUDefs.h" #include #include SelectIMUDlg::SelectIMUDlg(RTIMUSettings *settings, QWidget *parent) : QDialog(parent, Qt::WindowCloseButtonHint | Qt::WindowTitleHint) { m_settings = settings; layoutWindow(); setWindowTitle("Select IMU"); connect(m_buttons, SIGNAL(accepted()), this, SLOT(onOk())); connect(m_buttons, SIGNAL(rejected()), this, SLOT(onCancel())); connect(m_selectIMU, SIGNAL(currentIndexChanged(int)), this, SLOT(setSelectAddress(int))); connect(m_selectBus, SIGNAL(currentIndexChanged(int)), this, SLOT(setSelectAddress(int))); } SelectIMUDlg::~SelectIMUDlg() { } void SelectIMUDlg::onOk() { if (m_selectBus->currentIndex() < 8) { // I2C m_settings->m_busIsI2C = true; m_settings->m_I2CBus = m_selectBus->currentIndex(); m_settings->m_I2CSlaveAddress = m_selectAddress->itemData(m_selectAddress->currentIndex()).toInt(); } else { // SPI m_settings->m_busIsI2C = false; m_settings->m_SPIBus = m_selectBus->currentIndex() - 8; } m_settings->m_imuType = m_selectIMU->currentIndex(); m_settings->saveSettings(); accept(); } void SelectIMUDlg::onCancel() { reject(); } void SelectIMUDlg::layoutWindow() { QVBoxLayout *mainLayout; QFormLayout *form; setModal(true); mainLayout = new QVBoxLayout(this); mainLayout->setSpacing(20); mainLayout->setContentsMargins(11, 11, 11, 11); form = new QFormLayout(); mainLayout->addLayout(form); m_selectBus = new QComboBox(); m_selectBus->addItem("I2C bus 0"); m_selectBus->addItem("I2C bus 1"); m_selectBus->addItem("I2C bus 2"); m_selectBus->addItem("I2C bus 3"); m_selectBus->addItem("I2C bus 4"); m_selectBus->addItem("I2C bus 5"); m_selectBus->addItem("I2C bus 6"); m_selectBus->addItem("I2C bus 7"); m_selectBus->addItem("SPI bus 0"); m_selectBus->addItem("SPI bus 1"); m_selectBus->addItem("SPI bus 2"); m_selectBus->addItem("SPI bus 3"); m_selectBus->addItem("SPI bus 4"); m_selectBus->addItem("SPI bus 5"); m_selectBus->addItem("SPI bus 6"); m_selectBus->addItem("SPI bus 7"); if (m_settings->m_busIsI2C) m_selectBus->setCurrentIndex(m_settings->m_I2CBus); else m_selectBus->setCurrentIndex(m_settings->m_SPIBus + 8); form->addRow("select bus type: ", m_selectBus); m_selectIMU = new QComboBox(); m_selectIMU->addItem("Auto detect IMU"); m_selectIMU->addItem("Null IMU"); m_selectIMU->addItem("InvenSense MPU9150"); m_selectIMU->addItem("STM L3GD20H/LSM303D"); m_selectIMU->addItem("STM L3GD20/LSM303DLHC"); m_selectIMU->addItem("STM LSM9DS0"); m_selectIMU->addItem("STM LSM9DS1"); m_selectIMU->addItem("InvenSense MPU9250"); m_selectIMU->addItem("STM L3GD20H/LSM303DLHC"); m_selectIMU->addItem("Bosch BMX055"); m_selectIMU->addItem("Bosch BNO055"); m_selectIMU->setCurrentIndex(m_settings->m_imuType); form->addRow("select IMU type: ", m_selectIMU); m_selectAddress = new QComboBox(); setSelectAddress(m_settings->m_imuType, m_settings->m_I2CSlaveAddress); form->addRow("select I2C address type: ", m_selectAddress); m_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this); m_buttons->setCenterButtons(true); mainLayout->addWidget(m_buttons); } void SelectIMUDlg::setSelectAddress(int) { int imuType = m_selectIMU->currentIndex(); if (imuType == m_settings->m_imuType) setSelectAddress(imuType, m_settings->m_I2CSlaveAddress); else setSelectAddress(imuType, -1); } void SelectIMUDlg::setSelectAddress(int imuType, int slaveAddress) { m_selectAddress->clear(); if (m_selectBus->currentIndex() < 8) { switch (imuType) { case RTIMU_TYPE_MPU9150: m_selectAddress->addItem("Standard (0x68)", MPU9150_ADDRESS0); m_selectAddress->addItem("Option (0x69)", MPU9150_ADDRESS1); if (slaveAddress == MPU9150_ADDRESS1) m_selectAddress->setCurrentIndex(1); else m_selectAddress->setCurrentIndex(0); break; case RTIMU_TYPE_MPU9250: m_selectAddress->addItem("Standard (0x68)", MPU9250_ADDRESS0); m_selectAddress->addItem("Option (0x69)", MPU9250_ADDRESS1); if (slaveAddress == MPU9250_ADDRESS1) m_selectAddress->setCurrentIndex(1); else m_selectAddress->setCurrentIndex(0); break; case RTIMU_TYPE_GD20HM303D: m_selectAddress->addItem("Standard (0x6a)", L3GD20H_ADDRESS0); m_selectAddress->addItem("Option (0x6b)", L3GD20H_ADDRESS1); if (slaveAddress == L3GD20H_ADDRESS1) m_selectAddress->setCurrentIndex(1); else m_selectAddress->setCurrentIndex(0); break; case RTIMU_TYPE_GD20M303DLHC: m_selectAddress->addItem("Standard (0x6a)", L3GD20_ADDRESS0); m_selectAddress->addItem("Option (0x6b)", L3GD20_ADDRESS1); if (slaveAddress == L3GD20_ADDRESS1) m_selectAddress->setCurrentIndex(1); else m_selectAddress->setCurrentIndex(0); break; case RTIMU_TYPE_LSM9DS0: m_selectAddress->addItem("Standard (0x6a)", LSM9DS0_GYRO_ADDRESS0); m_selectAddress->addItem("Option (0x6b)", LSM9DS0_GYRO_ADDRESS1); if (slaveAddress == LSM9DS0_GYRO_ADDRESS1) m_selectAddress->setCurrentIndex(1); else m_selectAddress->setCurrentIndex(0); break; case RTIMU_TYPE_LSM9DS1: m_selectAddress->addItem("Standard (0x6a)", LSM9DS1_ADDRESS0); m_selectAddress->addItem("Option (0x6b)", LSM9DS1_ADDRESS1); if (slaveAddress == LSM9DS1_ADDRESS1) m_selectAddress->setCurrentIndex(1); else m_selectAddress->setCurrentIndex(0); break; case RTIMU_TYPE_GD20HM303DLHC: m_selectAddress->addItem("Standard (0x6a)", L3GD20H_ADDRESS0); m_selectAddress->addItem("Option (0x6b)", L3GD20H_ADDRESS1); if (slaveAddress == L3GD20H_ADDRESS1) m_selectAddress->setCurrentIndex(1); else m_selectAddress->setCurrentIndex(0); break; case RTIMU_TYPE_BMX055: m_selectAddress->addItem("Standard (0x68)", BMX055_GYRO_ADDRESS0); m_selectAddress->addItem("Option (0x69)", BMX055_GYRO_ADDRESS1); if (slaveAddress == BMX055_GYRO_ADDRESS1) m_selectAddress->setCurrentIndex(1); else m_selectAddress->setCurrentIndex(0); break; case RTIMU_TYPE_BNO055: m_selectAddress->addItem("Standard (0x28)", BNO055_ADDRESS0); m_selectAddress->addItem("Option (0x29)", BNO055_ADDRESS1); if (slaveAddress == BNO055_ADDRESS1) m_selectAddress->setCurrentIndex(1); else m_selectAddress->setCurrentIndex(0); break; default: m_selectAddress->addItem("N/A", 0); break; } } else { switch (imuType) { case RTIMU_TYPE_MPU9250: m_selectAddress->addItem("Standard", MPU9250_ADDRESS0); m_selectAddress->setCurrentIndex(0); break; default: m_selectAddress->addItem("N/A", 0); break; } } } rtimulib-7.2.1/Linux/RTIMULibDemo/SelectIMUDlg.h000066400000000000000000000035611254201074400212010ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef SELECTIMUDLG_H #define SELECTIMUDLG_H #include #include #include class RTIMUSettings; class SelectIMUDlg : public QDialog { Q_OBJECT public: SelectIMUDlg(RTIMUSettings *settings, QWidget *parent = 0); ~SelectIMUDlg(); public slots: void onOk(); void onCancel(); void setSelectAddress(int imuType); private: void layoutWindow(); void setSelectAddress(int imuType, int slaveAddress); RTIMUSettings *m_settings; QDialogButtonBox *m_buttons; QComboBox *m_selectBus; QComboBox *m_selectIMU; QComboBox *m_selectAddress; }; #endif // SELECTIMUDLG_H rtimulib-7.2.1/Linux/RTIMULibDemo/main.cpp000066400000000000000000000027201254201074400202330ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTIMULibDemo.h" #include // This is standard Qt startup int main(int argc, char *argv[]) { QApplication a(argc, argv); RTIMULibDemo *w = new RTIMULibDemo(); w->show(); return a.exec(); } rtimulib-7.2.1/Linux/RTIMULibDemoGL/000077500000000000000000000000001254201074400170255ustar00rootroot00000000000000rtimulib-7.2.1/Linux/RTIMULibDemoGL/AccelCalDlg.cpp000066400000000000000000000230161254201074400216110ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "AccelCalDlg.h" #include "RTIMUAccelCal.h" #include "RTIMUSettings.h" #include #include #include #include #include AccelCalDlg::AccelCalDlg(QWidget *parent, RTIMUSettings* settings) : QDialog(parent) { m_cal = new RTIMUAccelCal(settings); m_newData = false; m_cal->accelCalInit(); layoutWindow(); setWindowTitle("Accelerometer Calibration"); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); m_timer = startTimer(50); connect(m_checkAllBtn, SIGNAL(clicked()), this, SLOT(onCheckAll())); connect(m_uncheckAllBtn, SIGNAL(clicked()), this, SLOT(onUncheckAll())); connect(m_resetBtn, SIGNAL(clicked()), this, SLOT(onReset())); connect(m_okBtn, SIGNAL(clicked()), this, SLOT(onOk())); connect(m_cancelBtn, SIGNAL(clicked()), this, SLOT(onCancel())); } AccelCalDlg::~AccelCalDlg() { } void AccelCalDlg::newIMUData(const RTIMU_DATA& data) { QMutexLocker lock(&m_refreshMutex); m_currentVal = data.accel; for (int i = 0; i < 3; i++) m_cal->accelCalEnable(i, m_check[i]->checkState() == Qt::Checked); m_cal->newAccelCalData(data.accel); m_newData = true; } void AccelCalDlg::onOk() { killTimer(m_timer); m_cal->accelCalSave(); accept(); } void AccelCalDlg::onCancel() { killTimer(m_timer); reject(); } void AccelCalDlg::onReset() { QMutexLocker lock(&m_refreshMutex); for (int i = 0; i < 3; i++) m_cal->accelCalEnable(i, m_check[i]->checkState() == Qt::Checked); m_cal->accelCalReset(); m_okBtn->setEnabled(m_cal->accelCalValid()); } void AccelCalDlg::onCheckAll() { for (int j = 0; j < 3; j++) m_check[j]->setChecked(true); } void AccelCalDlg::onUncheckAll() { for (int j = 0; j < 3; j++) m_check[j]->setChecked(false); } void AccelCalDlg::timerEvent(QTimerEvent *) { QMutexLocker lock(&m_refreshMutex); m_okBtn->setEnabled(m_cal->accelCalValid()); if (m_newData) updateControls(); m_newData = false; } void AccelCalDlg::updateControls() { for (int i = 0; i < 3; i++) { setRaw(m_raw[i], m_currentVal.data(i)); setRawMinMax(m_rawMin[i], m_cal->m_accelMin.data(i)); setRawMinMax(m_rawMax[i], m_cal->m_accelMax.data(i)); } } void AccelCalDlg::setRaw(QLabel *label, float val) { label->setText(QString::number(val, 'f', 4)); if (val < 0) label->setStyleSheet(m_redStyleSheet); else label->setStyleSheet(m_greenStyleSheet); } void AccelCalDlg::setRawMinMax(QLabel *label, float val) { label->setText(QString::number(val, 'f', 4)); if (val < 0) label->setStyleSheet(m_lightRedStyleSheet); else label->setStyleSheet(m_lightGreenStyleSheet); } void AccelCalDlg::layoutWindow() { QHBoxLayout *hLayout; QHBoxLayout *checkLayout; QLabel *label; QSpacerItem *spacer; int gridRow = 0; m_whiteStyleSheet = QString::fromUtf8("background-color: rgb(255, 255, 255);"); m_lightRedStyleSheet = QString::fromUtf8("background-color: rgb(200, 100, 100);"); m_lightGreenStyleSheet = QString::fromUtf8("background-color: rgb(100, 200, 100);"); m_redStyleSheet = QString::fromUtf8("background-color: rgb(220, 80, 80);"); m_greenStyleSheet = QString::fromUtf8("background-color: rgb(38, 244, 54);"); QVBoxLayout *centralLayout = new QVBoxLayout(this); centralLayout->setSpacing(20); centralLayout->setContentsMargins(6, 6, 6, 6); QGridLayout *gridLayout = new QGridLayout(); gridLayout->setSpacing(6); gridLayout->setContentsMargins(4, 4, 4, 4); hLayout = new QHBoxLayout(); hLayout->setSpacing(4); spacer = new QSpacerItem(80, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); label = getFixedLabel("XMin", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("X", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("XMax", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); label = getFixedLabel("YMin", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("Y", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("YMax", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); label = getFixedLabel("ZMin", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("Z", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("ZMax", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); gridLayout->addLayout(hLayout, gridRow++, 0, 1, 1); spacer = new QSpacerItem(20, 24, QSizePolicy::Minimum, QSizePolicy::Expanding); gridLayout->addItem(spacer, gridRow++, 0, 1, 1); // the old row hLayout = new QHBoxLayout(); hLayout->setSpacing(4); label = getFixedLabel("Old ", 80, 20, Qt::AlignLeft | Qt::AlignVCenter); hLayout->addWidget(label); m_oldMin = m_cal->m_accelMin; m_oldMax = m_cal->m_accelMax; for (int j = 0; j < 3; j++) { m_oldRawMin[j] = getFixedLabel(QString::number(m_oldMin.data(j)), 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_oldRawMin[j]); checkLayout = new QHBoxLayout(); checkLayout->setMargin(0); checkLayout->setAlignment(Qt::AlignCenter); m_check[j] = new QCheckBox(); checkLayout->addWidget(m_check[j]); hLayout->addLayout(checkLayout); m_oldRawMax[j] = getFixedLabel(QString::number(m_oldMax.data(j)), 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_oldRawMax[j]); if (j < 2) { spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); } } gridLayout->addLayout(hLayout, gridRow++, 0, 1, 1); spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding); gridLayout->addItem(spacer, gridRow++, 0, 1, 1); // the current row hLayout = new QHBoxLayout(); hLayout->setSpacing(4); label = getFixedLabel("Current ", 80, 20, Qt::AlignLeft | Qt::AlignVCenter); hLayout->addWidget(label); for (int j = 0; j < 3; j++) { m_rawMin[j] = getFixedLabel("0", 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_rawMin[j]); m_raw[j] = getFixedLabel("0", 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_raw[j]); m_rawMax[j] = getFixedLabel("0", 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_rawMax[j]); if (j < 2) { spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); } } gridLayout->addLayout(hLayout, gridRow++, 0, 1, 1); spacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); gridLayout->addItem(spacer, gridRow++, 0, 1, 1); spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding); gridLayout->addItem(spacer, gridRow++, 0, 1, 1); centralLayout->addLayout(gridLayout); m_checkAllBtn = new QPushButton("Check All"); m_uncheckAllBtn = new QPushButton("Uncheck All"); m_resetBtn = new QPushButton("Reset"); m_resetBtn->setDefault(true); m_okBtn = new QPushButton("OK"); m_okBtn->setEnabled(m_cal->accelCalValid()); m_cancelBtn = new QPushButton("Cancel"); hLayout = new QHBoxLayout(); hLayout->addSpacing(24); hLayout->addWidget(m_checkAllBtn); hLayout->addWidget(m_uncheckAllBtn); hLayout->addWidget(m_resetBtn); hLayout->addStretch(); hLayout->addWidget(m_okBtn); hLayout->addWidget(m_cancelBtn); hLayout->addSpacing(24); centralLayout->addLayout(hLayout); } QLabel* AccelCalDlg::getFixedLabel(QString text, int w, int h, Qt::Alignment alignment, QString styleSheet) { QLabel *label = new QLabel(text); label->setMaximumSize(QSize(w + 10, h)); label->setMinimumSize(QSize(w, h)); label->setAlignment(alignment); if (styleSheet.length() > 0) label->setStyleSheet(styleSheet); return label; } rtimulib-7.2.1/Linux/RTIMULibDemoGL/AccelCalDlg.h000066400000000000000000000054431254201074400212620ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef ACCELCALDLG_H #define ACCELCALDLG_H #include "RTIMULib.h" #include "RTIMUCalDefs.h" #include #include #include #include #include #include #include #include class RTIMUAccelCal; class AccelCalDlg : public QDialog { Q_OBJECT public: AccelCalDlg(QWidget *parent, RTIMUSettings* settings); ~AccelCalDlg(); public slots: void onOk(); void onCancel(); void onReset(); void onCheckAll(); void onUncheckAll(); void newIMUData(const RTIMU_DATA& data); protected: void timerEvent(QTimerEvent *); private: void updateControls(); void setRaw(QLabel *label, float val); void setRawMinMax(QLabel *label, float val); void layoutWindow(); QLabel* getFixedLabel(QString text, int w, int h, Qt::Alignment alignment = Qt::AlignCenter, QString styleSheet = ""); int m_timer; QMutex m_refreshMutex; QString m_whiteStyleSheet; QString m_lightRedStyleSheet; QString m_lightGreenStyleSheet; QString m_redStyleSheet; QString m_greenStyleSheet; RTVector3 m_currentVal; RTVector3 m_oldMin; RTVector3 m_oldMax; QLabel *m_rawMin[3]; QLabel *m_raw[3]; QLabel *m_rawMax[3]; QLabel *m_oldRawMin[3]; QLabel *m_oldRawMax[3]; QCheckBox *m_check[3]; QPushButton *m_okBtn; QPushButton *m_cancelBtn; QPushButton *m_resetBtn; QPushButton *m_checkAllBtn; QPushButton *m_uncheckAllBtn; bool m_newData; RTIMUAccelCal *m_cal; }; #endif // ACCELCALDLG_H rtimulib-7.2.1/Linux/RTIMULibDemoGL/CMakeLists.txt000066400000000000000000000054411254201074400215710ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014, richards-tech #// #// Permission 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. #// The cmake support was based on work by Moritz Fischer at ettus.com. #// Original copyright notice: # # Copyright 2014 Ettus Research LLC # SET(DEMO_SRCS AccelCalDlg.cpp RTIMULibDemoGL.cpp IMUThread.cpp MagCalDlg.cpp SelectIMUDlg.cpp SelectFusionDlg.cpp main.cpp) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) SET(CMAKE_AUTOMOC ON) IF(WIN32) SET(EXTRA_LIBS opengl32) ENDIF(WIN32) IF (UNIX AND (NOT APPLE)) SET(EXTRA_LIBS GL) ENDIF(UNIX AND (NOT APPLE)) IF(DEFINED QT5) FIND_PACKAGE(Qt5Widgets REQUIRED) FIND_PACKAGE(Qt5Core REQUIRED) FIND_PACKAGE(Qt5OpenGL REQUIRED) FIND_PACKAGE(Qt5Gui REQUIRED) qt5_wrap_ui(UI_HEADERS RTIMULibDemoGL.ui) ADD_EXECUTABLE(RTIMULibDemoGL ${DEMO_SRCS} ${UI_HEADERS} ${GL_HEADERS}) TARGET_LINK_LIBRARIES(RTIMULibDemoGL RTIMULib RTIMULibGL ${EXTRA_LIBS}) qt5_use_modules(RTIMULibDemoGL Widgets Gui) INSTALL(TARGETS RTIMULibDemoGL DESTINATION bin) ELSE(DEFINED QT5) FIND_PACKAGE(Qt4) IF(QT4_FOUND) QT4_WRAP_UI(UI_HEADERS RTIMULibDemoGL.ui) SET(QT_USE_QTOPENGL TRUE) INCLUDE(${QT_USE_FILE}) ADD_DEFINITIONS(${QT_DEFINITIONS}) ADD_EXECUTABLE(RTIMULibDemoGL ${DEMO_SRCS} ${UI_HEADERS} ${GL_HEADERS}) TARGET_LINK_LIBRARIES(RTIMULibDemoGL RTIMULib RTIMULibGL ${Qt5Gui_OPENGL_LIBRARIES} ${QT_LIBRARIES} ${EXTRA_LIBS}) INSTALL(TARGETS RTIMULibDemoGL DESTINATION bin) ELSE(QT4_FOUND) MESSAGE(STATUS "Qt4 not found") ENDIF(QT4_FOUND) ENDIF(DEFINED QT5) rtimulib-7.2.1/Linux/RTIMULibDemoGL/IMUThread.cpp000066400000000000000000000116771254201074400213270ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "IMUThread.h" #include "IMUDrivers/RTPressure.h" #include "IMUDrivers/RTHumidity.h" #include IMUThread::IMUThread() : QObject() { m_imu = NULL; m_pressure = NULL; m_humidity = NULL; m_calibrationMode = false; m_settings = new RTIMUSettings(); // just use default name (RTIMULib.ini) for settings file m_timer = -1; } IMUThread::~IMUThread() { } void IMUThread::newIMU() { if (m_imu != NULL) { delete m_imu; m_imu = NULL; } if (m_timer != -1) { killTimer(m_timer); m_timer = -1; } m_imu = RTIMU::createIMU(m_settings); if (m_imu == NULL) return; // set up IMU m_imu->IMUInit(); m_timer = startTimer(m_imu->IMUGetPollInterval()); } void IMUThread::newPressure() { if (m_pressure != NULL) { delete m_pressure; m_pressure = NULL; } m_pressure = RTPressure::createPressure(m_settings); if (m_pressure == NULL) return; // set up pressure sensor m_pressure->pressureInit(); } void IMUThread::newHumidity() { if (m_humidity != NULL) { delete m_humidity; m_humidity = NULL; } m_humidity = RTHumidity::createHumidity(m_settings); if (m_humidity == NULL) return; // set up humidity sensor m_humidity->humidityInit(); } void IMUThread::initThread() { // create IMU. There's a special function call for this // as it makes sure that the required one is created as specified in the settings. m_imu = RTIMU::createIMU(m_settings); if (m_imu == NULL) { qDebug() << "No IMU found."; return; } // set up IMU m_imu->IMUInit(); // create pressure sensor. There's a special function call for this // as it makes sure that the required one is created as specified in the settings. m_pressure = RTPressure::createPressure(m_settings); newPressure(); newHumidity(); // poll at the rate suggested bu the IMU m_timer = startTimer(m_imu->IMUGetPollInterval()); // up the priority in case it's helpful m_thread->setPriority(QThread::TimeCriticalPriority); } void IMUThread::finishThread() { if (m_timer != -1) killTimer(m_timer); m_timer = -1; if (m_imu != NULL) delete m_imu; m_imu = NULL; if (m_pressure != NULL) delete m_pressure; m_pressure = NULL; if (m_humidity != NULL) delete m_humidity; m_humidity = NULL; delete m_settings; } void IMUThread::timerEvent(QTimerEvent * /* event */) { // check for valid IMU if (m_imu == NULL) return; if (m_imu->IMUType() == RTIMU_TYPE_NULL) return; // loop here to clear all samples just in case things aren't keeping up while (m_imu->IMURead()) { if (m_calibrationMode) { emit newCalData(m_imu->getCompass()); } else { RTIMU_DATA data = m_imu->getIMUData(); if (m_pressure != NULL) m_pressure->pressureRead(data); if (m_humidity != NULL) m_humidity->humidityRead(data); emit newIMUData(data); } } } //---------------------------------------------------------- // // The following is some Qt threading stuff void IMUThread::resumeThread() { m_thread = new QThread(); moveToThread(m_thread); connect(m_thread, SIGNAL(started()), this, SLOT(internalRunLoop())); connect(this, SIGNAL(internalEndThread()), this, SLOT(cleanup())); connect(this, SIGNAL(internalKillThread()), m_thread, SLOT(quit())); connect(m_thread, SIGNAL(finished()), m_thread, SLOT(deleteLater())); connect(m_thread, SIGNAL(finished()), this, SLOT(deleteLater())); m_thread->start(); } rtimulib-7.2.1/Linux/RTIMULibDemoGL/IMUThread.h000066400000000000000000000056351254201074400207710ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _IMUTHREAD_H #define _IMUTHREAD_H #include #include "RTIMULib.h" class RTPressure; class RTHumidity; class IMUThread : public QObject { Q_OBJECT public: IMUThread(); virtual ~IMUThread(); // resumeThread() is called when init is complete void resumeThread(); // exitThread is called to terminate and delete the thread void exitThread() { emit internalEndThread(); } RTIMUSettings *getSettings() { return m_settings; } void setCalibrationMode(bool enable); void newCompassCalData(const RTVector3& compassCalMin, const RTVector3& compassCalMax); RTIMU *getIMU() { return m_imu; } RTPressure *getPressure() { return m_pressure; } RTHumidity *getHumidity() { return m_humidity; } public slots: void internalRunLoop() { initThread(); emit running();} void cleanup() {finishThread(); emit internalKillThread(); } void newIMU(); void newPressure(); void newHumidity(); signals: void running(); // emitted when everything set up and thread active void internalEndThread(); // this to end thread void internalKillThread(); // tells the QThread to quit void newCalData(const RTVector3& compass); // this is uncalibrated compass data emitted in cal mode void newIMUData(const RTIMU_DATA& data); // this contains the latest data form the IMU protected: void initThread(); void finishThread(); void timerEvent(QTimerEvent *event); private: int m_timer; RTIMUSettings *m_settings; RTIMU *m_imu; RTPressure *m_pressure; RTHumidity *m_humidity; bool m_calibrationMode; QThread *m_thread; }; #endif // _IMUTHREAD_H rtimulib-7.2.1/Linux/RTIMULibDemoGL/MagCalDlg.cpp000066400000000000000000000255251254201074400213150ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "MagCalDlg.h" #include "RTIMUMagCal.h" #include #include #include #include #include #include #include MagCalDlg::MagCalDlg(QWidget *parent, RTIMUSettings* settings) : QDialog(parent) { m_cal = new RTIMUMagCal(settings); m_newData = false; m_fitDirOptions.append("./RTEllipsoidFit/"); m_fitDirOptions.append("../RTEllipsoidFit/"); m_fitDirOptions.append("../../RTEllipsoidFit/"); findFitDir(); m_cal->magCalInit(); m_minMaxMode = true; layoutWindow(); setButtonEnables(); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); m_timer = startTimer(50); connect(m_cancelBtn, SIGNAL(clicked()), this, SLOT(onCancel())); connect(m_resetBtn, SIGNAL(clicked()), this, SLOT(onReset())); connect(m_saveMinMaxBtn, SIGNAL(clicked()), this, SLOT(onSaveMinMax())); connect(m_processEllipsoidBtn, SIGNAL(clicked()), this, SLOT(onProcess())); } MagCalDlg::~MagCalDlg() { } void MagCalDlg::newIMUData(const RTIMU_DATA& data) { QMutexLocker lock(&m_refreshMutex); m_currentVal = data.compass; if (m_minMaxMode) m_cal->newMinMaxData(data.compass); else m_cal->newEllipsoidData(data.compass); m_newData = true; } void MagCalDlg::findFitDir() { m_fitDir = ""; m_usingEllipsoidFit = false; for (int i = 0; i < m_fitDirOptions.count(); i++) { QFileInfo file(m_fitDirOptions.at(i) + RTIMUCALDEFS_OCTAVE_CODE); if (file.exists()) { m_fitDir = m_fitDirOptions.at(i); m_usingEllipsoidFit = true; return; } } } void MagCalDlg::onCancel() { killTimer(m_timer); reject(); } void MagCalDlg::onReset() { m_cal->magCalReset(); m_minMaxMode = true; setButtonEnables(); } void MagCalDlg::onSaveMinMax() { m_cal->magCalSaveMinMax(); if (m_usingEllipsoidFit) { m_minMaxMode = false; setButtonEnables(); } else { accept(); } } void MagCalDlg::onProcess() { m_cal->magCalSaveRaw(qPrintable(m_fitDir)); QProcess proc; proc.setWorkingDirectory(m_fitDir); proc.start(RTIMUCALDEFS_OCTAVE_COMMAND); proc.waitForFinished(20000); if (proc.exitCode() == 0) { m_cal->magCalSaveCorr(qPrintable(m_fitDir)); } else { QMessageBox::warning(this, "Ellipsoid fit error", "Failed to execute RTEllipsoidFit.m. Only min/max calibration available", QMessageBox::Ok); } accept(); } void MagCalDlg::timerEvent(QTimerEvent *) { QMutexLocker lock(&m_refreshMutex); if (m_newData) updateControls(); m_newData = false; } void MagCalDlg::updateControls() { for (int i = 0; i < 3; i++) { setRaw(m_raw[i], m_currentVal.data(i)); setRawMinMax(m_rawMin[i], m_cal->m_magMin.data(i)); setRawMinMax(m_rawMax[i], m_cal->m_magMax.data(i)); } if (m_usingEllipsoidFit) setOctantCounts(); if (m_minMaxMode) { if (!m_saveMinMaxBtn->isEnabled() && m_cal->magCalValid()) m_saveMinMaxBtn->setEnabled(true); } else { if (!m_processEllipsoidBtn->isEnabled() && m_cal->magCalEllipsoidValid()) m_processEllipsoidBtn->setEnabled(true); } } void MagCalDlg::setRaw(QLabel *label, float val) { label->setText(QString::number(val, 'f', 4)); if (val < 0) label->setStyleSheet(m_redStyleSheet); else label->setStyleSheet(m_greenStyleSheet); } void MagCalDlg::setRawMinMax(QLabel *label, float val) { label->setText(QString::number(val, 'f', 4)); if (val < 0) label->setStyleSheet(m_lightRedStyleSheet); else label->setStyleSheet(m_lightGreenStyleSheet); } void MagCalDlg::setOctantCounts() { int counts[RTIMUCALDEFS_OCTANT_COUNT]; m_cal->magCalOctantCounts(counts); for (int i = 0; i < RTIMUCALDEFS_OCTANT_COUNT; i++) { m_octantCount[i]->setText(QString::number(counts[i])); } } void MagCalDlg::layoutWindow() { QHBoxLayout *hLayout; QLabel *label; QSpacerItem *spacer; int gridRow = 0; QString octantNames[] = {"---", "+--", "-+-", "++-", "--+", "+-+", "-++", "+++"}; m_whiteStyleSheet = QString::fromUtf8("background-color: rgb(255, 255, 255);"); m_lightRedStyleSheet = QString::fromUtf8("background-color: rgb(200, 100, 100);"); m_lightGreenStyleSheet = QString::fromUtf8("background-color: rgb(100, 200, 100);"); m_redStyleSheet = QString::fromUtf8("background-color: rgb(220, 80, 80);"); m_greenStyleSheet = QString::fromUtf8("background-color: rgb(38, 244, 54);"); QVBoxLayout *centralLayout = new QVBoxLayout(this); centralLayout->setSpacing(20); centralLayout->setContentsMargins(6, 6, 6, 6); QGridLayout *gridLayout = new QGridLayout(); gridLayout->setSpacing(6); gridLayout->setContentsMargins(4, 4, 4, 4); hLayout = new QHBoxLayout(); hLayout->setSpacing(4); spacer = new QSpacerItem(80, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); label = getFixedLabel("XMin", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("X", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("XMax", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); label = getFixedLabel("YMin", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("Y", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("YMax", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); label = getFixedLabel("ZMin", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("Z", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("ZMax", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); gridLayout->addLayout(hLayout, gridRow++, 0, 1, 1); spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding); gridLayout->addItem(spacer, gridRow++, 0, 1, 1); // the current row hLayout = new QHBoxLayout(); hLayout->setSpacing(4); label = getFixedLabel("Current ", 80, 20, Qt::AlignLeft | Qt::AlignVCenter); hLayout->addWidget(label); for (int j = 0; j < 3; j++) { m_rawMin[j] = getFixedLabel("0", 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_rawMin[j]); m_raw[j] = getFixedLabel("0", 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_raw[j]); m_rawMax[j] = getFixedLabel("0", 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_rawMax[j]); if (j < 2) { spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); } } gridLayout->addLayout(hLayout, gridRow++, 0, 1, 1); spacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); gridLayout->addItem(spacer, gridRow++, 0, 1, 1); centralLayout->addLayout(gridLayout); if (m_usingEllipsoidFit) { // Do octant displays centralLayout->addWidget(new QLabel("Octant counts:")); QGridLayout *octantLayout = new QGridLayout(); octantLayout->setSpacing(6); octantLayout->setContentsMargins(4, 4, 4, 4); for (int i = 0; i < RTIMUCALDEFS_OCTANT_COUNT; i++) { QHBoxLayout *hOctant = new QHBoxLayout(); QLabel *label = getFixedLabel(octantNames[i], 50, 20, Qt::AlignCenter, m_whiteStyleSheet); m_octantCount[i] = getFixedLabel("0", 50, 20, Qt::AlignCenter, m_whiteStyleSheet); hOctant->addWidget(label); hOctant->addWidget(m_octantCount[i]); octantLayout->addLayout(hOctant, i / 4, i % 4); } centralLayout->addLayout(octantLayout); } QHBoxLayout *hBox = new QHBoxLayout(); centralLayout->addLayout(hBox); QFormLayout *formLayout = new QFormLayout(); hBox->addLayout(formLayout); hBox->setAlignment(Qt::AlignHCenter); QHBoxLayout *buttonLayout = new QHBoxLayout(); m_processEllipsoidBtn = new QPushButton("Process ellipsoid"); m_saveMinMaxBtn = new QPushButton("Save min/max"); m_resetBtn = new QPushButton("Reset"); m_cancelBtn = new QPushButton("Cancel"); buttonLayout->addWidget(m_resetBtn); buttonLayout->addWidget(m_saveMinMaxBtn); if (m_usingEllipsoidFit) buttonLayout->addWidget(m_processEllipsoidBtn); buttonLayout->addWidget(m_cancelBtn); hBox->addLayout(buttonLayout); hLayout = new QHBoxLayout(); hLayout->addSpacing(24); hLayout->addStretch(); centralLayout->addLayout(hLayout); } QLabel* MagCalDlg::getFixedLabel(QString text, int w, int h, Qt::Alignment alignment, QString styleSheet) { QLabel *label = new QLabel(text); label->setMaximumSize(QSize(w + 10, h)); label->setMinimumSize(QSize(w, h)); label->setAlignment(alignment); if (styleSheet.length() > 0) label->setStyleSheet(styleSheet); return label; } void MagCalDlg::setButtonEnables() { m_saveMinMaxBtn->setEnabled(false); if (m_usingEllipsoidFit) m_processEllipsoidBtn->setEnabled(false); if (m_minMaxMode) { setWindowTitle("Magnetomer Calibration - collecting min/max data"); } else { setWindowTitle(QString("Magnetomer Calibration - collecting ellipsoid data (need %1 in each octant)") .arg(RTIMUCALDEFS_OCTANT_MIN_SAMPLES)); } } rtimulib-7.2.1/Linux/RTIMULibDemoGL/MagCalDlg.h000066400000000000000000000055361254201074400207620ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef MAGCALDLG_H #define MAGCALDLG_H #include "RTIMULib.h" #include "RTIMUCalDefs.h" #include #include #include #include #include #include #include #include class RTIMUMagCal; class MagCalDlg : public QDialog { Q_OBJECT public: MagCalDlg(QWidget *parent, RTIMUSettings* settings); ~MagCalDlg(); public slots: void onCancel(); void onReset(); void onSaveMinMax(); void onProcess(); void newIMUData(const RTIMU_DATA& data); protected: void timerEvent(QTimerEvent *); private: void updateControls(); void setRaw(QLabel *label, float val); void setRawMinMax(QLabel *label, float val); void setOctantCounts(); void setButtonEnables(); void findFitDir(); void layoutWindow(); QLabel* getFixedLabel(QString text, int w, int h, Qt::Alignment alignment = Qt::AlignCenter, QString styleSheet = ""); int m_timer; QMutex m_refreshMutex; QString m_whiteStyleSheet; QString m_lightRedStyleSheet; QString m_lightGreenStyleSheet; QString m_redStyleSheet; QString m_greenStyleSheet; RTVector3 m_currentVal; QLabel *m_rawMin[3]; QLabel *m_raw[3]; QLabel *m_rawMax[3]; QLabel *m_octantCount[RTIMUCALDEFS_OCTANT_COUNT]; QPushButton *m_resetBtn; QPushButton *m_saveMinMaxBtn; QPushButton *m_processEllipsoidBtn; QPushButton *m_cancelBtn; bool m_newData; bool m_minMaxMode; RTIMUMagCal *m_cal; QString m_fitDir; QStringList m_fitDirOptions; bool m_usingEllipsoidFit; }; #endif // MAGCALDLG_H rtimulib-7.2.1/Linux/RTIMULibDemoGL/RTIMULibDemoGL.cpp000066400000000000000000000440071254201074400221150ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include #include #include #include "RTIMULibDemoGL.h" #include "SelectIMUDlg.h" #include "SelectFusionDlg.h" #include "IMUThread.h" #include "AccelCalDlg.h" #include "MagCalDlg.h" #include "IMUView.h" #define RATE_TIMER_INTERVAL 2 RTIMULibDemoGL::RTIMULibDemoGL() : QMainWindow() { // This is some normal Qt GUI stuff ui.setupUi(this); layoutWindow(); layoutStatusBar(); // This code connects up signals from the GUI connect(ui.actionExit, SIGNAL(triggered()), this, SLOT(close())); connect(ui.actionSelectFusionAlgorithm, SIGNAL(triggered()), this, SLOT(onSelectFusionAlgorithm())); connect(ui.actionCalibrateAccelerometers, SIGNAL(triggered()), this, SLOT(onCalibrateAccelerometers())); connect(ui.actionCalibrateMagnetometers, SIGNAL(triggered()), this, SLOT(onCalibrateMagnetometers())); connect(ui.actionSelectIMU, SIGNAL(triggered()), this, SLOT(onSelectIMU())); connect(m_enableGyro, SIGNAL(stateChanged(int)), this, SLOT(onEnableGyro(int))); connect(m_enableAccel, SIGNAL(stateChanged(int)), this, SLOT(onEnableAccel(int))); connect(m_enableCompass, SIGNAL(stateChanged(int)), this, SLOT(onEnableCompass(int))); connect(m_enableDebug, SIGNAL(stateChanged(int)), this, SLOT(onEnableDebug(int))); // create the imu thread and connect up the signal m_imuThread = new IMUThread(); connect(m_imuThread, SIGNAL(newIMUData(const RTIMU_DATA&)), this, SLOT(newIMUData(const RTIMU_DATA&)), Qt::DirectConnection); connect(this, SIGNAL(newIMU()), m_imuThread, SLOT(newIMU())); m_imuThread->resumeThread(); // This value allows a sample rate to be calculated m_sampleCount = 0; // start some timers to get things going m_rateTimer = startTimer(RATE_TIMER_INTERVAL * 1000); // Only update the display 10 times per second to keep CPU reasonable m_displayTimer = startTimer(100); m_fusionType->setText(RTFusion::fusionName(m_imuThread->getSettings()->m_fusionType)); } RTIMULibDemoGL::~RTIMULibDemoGL() { } void RTIMULibDemoGL::onSelectFusionAlgorithm() { SelectFusionDlg dlg(m_imuThread->getSettings(), this); if (dlg.exec() == QDialog::Accepted) { emit newIMU(); m_fusionType->setText(RTFusion::fusionName(m_imuThread->getSettings()->m_fusionType)); } } void RTIMULibDemoGL::onSelectIMU() { SelectIMUDlg dlg(m_imuThread->getSettings(), this); if (dlg.exec() == QDialog::Accepted) { emit newIMU(); } } void RTIMULibDemoGL::onCalibrateAccelerometers() { m_imuThread->getIMU()->setAccelCalibrationMode(true); AccelCalDlg dlg(this, m_imuThread->getSettings()); connect(m_imuThread, SIGNAL(newIMUData(const RTIMU_DATA&)), &dlg, SLOT(newIMUData(const RTIMU_DATA&)), Qt::DirectConnection); if (dlg.exec() == QDialog::Accepted) { } disconnect(m_imuThread, SIGNAL(newIMUData(const RTIMU_DATA&)), &dlg, SLOT(newIMUData(const RTIMU_DATA&))); emit newIMU(); } void RTIMULibDemoGL::onCalibrateMagnetometers() { m_imuThread->getIMU()->setCompassCalibrationMode(true); MagCalDlg dlg(this, m_imuThread->getSettings()); connect(m_imuThread, SIGNAL(newIMUData(const RTIMU_DATA&)), &dlg, SLOT(newIMUData(const RTIMU_DATA&)), Qt::DirectConnection); if (dlg.exec() == QDialog::Accepted) { } disconnect(m_imuThread, SIGNAL(newIMUData(const RTIMU_DATA&)), &dlg, SLOT(newIMUData(const RTIMU_DATA&))); emit newIMU(); } void RTIMULibDemoGL::newIMUData(const RTIMU_DATA& data) { m_imuData = data; m_sampleCount++; } void RTIMULibDemoGL::onEnableGyro(int state) { if (m_imuThread->getIMU() != NULL) m_imuThread->getIMU()->setGyroEnable(state == Qt::Checked); } void RTIMULibDemoGL::onEnableAccel(int state) { if (m_imuThread->getIMU() != NULL) m_imuThread->getIMU()->setAccelEnable(state == Qt::Checked); } void RTIMULibDemoGL::onEnableCompass(int state) { if (m_imuThread->getIMU() != NULL) m_imuThread->getIMU()->setCompassEnable(state == Qt::Checked); } void RTIMULibDemoGL::onEnableDebug(int state) { if (m_imuThread->getIMU() != NULL) m_imuThread->getIMU()->setDebugEnable(state == Qt::Checked); } void RTIMULibDemoGL::closeEvent(QCloseEvent *) { killTimer(m_displayTimer); killTimer(m_rateTimer); m_imuThread->exitThread(); } void RTIMULibDemoGL::timerEvent(QTimerEvent *event) { RTVector3 measuredPose; if (event->timerId() == m_displayTimer) { // Update the GUI m_gyroX->setText(QString::number(m_imuData.gyro.x(), 'f', 6)); m_gyroY->setText(QString::number(m_imuData.gyro.y(), 'f', 6)); m_gyroZ->setText(QString::number(m_imuData.gyro.z(), 'f', 6)); m_accelX->setText(QString::number(m_imuData.accel.x(), 'f', 6)); m_accelY->setText(QString::number(m_imuData.accel.y(), 'f', 6)); m_accelZ->setText(QString::number(m_imuData.accel.z(), 'f', 6)); m_compassX->setText(QString::number(m_imuData.compass.x(), 'f', 6)); m_compassY->setText(QString::number(m_imuData.compass.y(), 'f', 6)); m_compassZ->setText(QString::number(m_imuData.compass.z(), 'f', 6)); m_fusionPoseX->setText(QString::number(m_imuData.fusionPose.x() * RTMATH_RAD_TO_DEGREE, 'f', 6)); m_fusionPoseY->setText(QString::number(m_imuData.fusionPose.y() * RTMATH_RAD_TO_DEGREE, 'f', 6)); m_fusionPoseZ->setText(QString::number(m_imuData.fusionPose.z() * RTMATH_RAD_TO_DEGREE, 'f', 6)); m_fusionQPoseScalar->setText(QString::number(m_imuData.fusionQPose.scalar(), 'f', 6)); m_fusionQPoseX->setText(QString::number(m_imuData.fusionQPose.x(), 'f', 6)); m_fusionQPoseY->setText(QString::number(m_imuData.fusionQPose.y(), 'f', 6)); m_fusionQPoseZ->setText(QString::number(m_imuData.fusionQPose.z(), 'f', 6)); m_accelMagnitude->setText(QString::number(m_imuData.accel.length(), 'f', 6)); m_compassMagnitude->setText(QString::number(m_imuData.compass.length(), 'f', 6)); if (m_imuData.pressureValid) { m_pressure->setText(QString::number(m_imuData.pressure, 'f', 2)); m_height->setText(QString::number(RTMath::convertPressureToHeight(m_imuData.pressure), 'f', 2)); } else { m_pressure->setText("0"); m_height->setText("0"); } if (m_imuData.humidityValid) { m_humidity->setText(QString::number(m_imuData.humidity, 'f', 2)); } else { m_humidity->setText("0"); } if (m_imuData.temperatureValid) m_temperature->setText(QString::number(m_imuData.temperature, 'f', 2)); else m_temperature->setText("0"); if (m_imuThread->getIMU() != NULL) { RTVector3 residuals = m_imuThread->getIMU()->getAccelResiduals(); m_accelResidualX->setText(QString::number(residuals.x(), 'f', 6)); m_accelResidualY->setText(QString::number(residuals.y(), 'f', 6)); m_accelResidualZ->setText(QString::number(residuals.z(), 'f', 6)); } int index; RTVector3 vec; if ((index = m_displaySelect->currentIndex()) == -1) { m_view->updateIMU(m_imuData.fusionPose); } else { switch (m_displaySelect->itemData(index).toInt()) { case DISPLAY_MEASURED: if (m_imuThread->getIMU() != NULL) { measuredPose = m_imuThread->getIMU()->getMeasuredPose(); m_view->updateIMU(measuredPose); } break; case DISPLAY_ACCELONLY: m_imuData.accel.accelToEuler(vec); m_view->updateIMU(vec); break; case DISPLAY_COMPASSONLY: vec = RTMath::poseFromAccelMag(m_imuData.accel, m_imuData.compass); vec.setX(0); vec.setY(0); m_view->updateIMU(vec); break; default: m_view->updateIMU(m_imuData.fusionPose); break; } } } else { // Update the sample rate float rate = (float)m_sampleCount / (float(RATE_TIMER_INTERVAL)); m_sampleCount = 0; m_rateStatus->setText(QString("Sample rate: %1 per second").arg(rate)); if (m_imuThread->getIMU() == NULL) { m_calStatus->setText("No IMU found"); } else { m_calStatus->setText(QString("Accel %1 : Compass %2 : Ellipsoid %3") .arg(m_imuThread->getIMU()->getAccelCalibrationValid() ? "calibrated" : "uncalibrated") .arg(m_imuThread->getIMU()->getCompassCalibrationValid() ? "calibrated" : "uncalibrated") .arg(m_imuThread->getIMU()->getCompassCalibrationEllipsoidValid() ? "in use" : "not in use")); } if (m_imuThread->getIMU() != NULL) { m_imuType->setText(m_imuThread->getIMU()->IMUName()); if (!m_imuThread->getIMU()->IMUGyroBiasValid()) m_biasStatus->setText("Gyro bias being calculated - keep IMU still!"); else m_biasStatus->setText("Gyro bias valid"); } } } void RTIMULibDemoGL::layoutWindow() { QHBoxLayout *mainLayout = new QHBoxLayout(); mainLayout->setContentsMargins(3, 3, 3, 3); mainLayout->setSpacing(3); QVBoxLayout *vLayout = new QVBoxLayout(); vLayout->addSpacing(10); QHBoxLayout *imuLayout = new QHBoxLayout(); vLayout->addLayout(imuLayout); imuLayout->addWidget(new QLabel("IMU type: ")); m_imuType = new QLabel(); imuLayout->addWidget(m_imuType); imuLayout->setStretch(1, 1); vLayout->addSpacing(10); QHBoxLayout *biasLayout = new QHBoxLayout(); vLayout->addLayout(biasLayout); biasLayout->addWidget(new QLabel("Gyro bias status: ")); m_biasStatus = new QLabel(); biasLayout->addWidget(m_biasStatus); biasLayout->setStretch(1, 1); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Fusion state (quaternion): ")); QHBoxLayout *dataLayout = new QHBoxLayout(); dataLayout->setAlignment(Qt::AlignLeft); m_fusionQPoseScalar = getFixedPanel("1"); m_fusionQPoseX = getFixedPanel("0"); m_fusionQPoseY = getFixedPanel("0"); m_fusionQPoseZ = getFixedPanel("0"); dataLayout->addSpacing(30); dataLayout->addWidget(m_fusionQPoseScalar); dataLayout->addWidget(m_fusionQPoseX); dataLayout->addWidget(m_fusionQPoseY); dataLayout->addWidget(m_fusionQPoseZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Pose - roll, pitch, yaw (degrees): ")); m_fusionPoseX = getFixedPanel("0"); m_fusionPoseY = getFixedPanel("0"); m_fusionPoseZ = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->setAlignment(Qt::AlignLeft); dataLayout->addSpacing(30); dataLayout->addWidget(m_fusionPoseX); dataLayout->addWidget(m_fusionPoseY); dataLayout->addWidget(m_fusionPoseZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Gyros (radians/s): ")); m_gyroX = getFixedPanel("0"); m_gyroY = getFixedPanel("0"); m_gyroZ = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->setAlignment(Qt::AlignLeft); dataLayout->addSpacing(30); dataLayout->addWidget(m_gyroX); dataLayout->addWidget(m_gyroY); dataLayout->addWidget(m_gyroZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Accelerometers (g): ")); m_accelX = getFixedPanel("0"); m_accelY = getFixedPanel("0"); m_accelZ = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->addSpacing(30); dataLayout->setAlignment(Qt::AlignLeft); dataLayout->addWidget(m_accelX); dataLayout->addWidget(m_accelY); dataLayout->addWidget(m_accelZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Accelerometer magnitude (g): ")); m_accelMagnitude = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->addSpacing(30); dataLayout->addWidget(m_accelMagnitude); dataLayout->setAlignment(Qt::AlignLeft); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Accelerometer residuals (g): ")); m_accelResidualX = getFixedPanel("0"); m_accelResidualY = getFixedPanel("0"); m_accelResidualZ = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->addSpacing(30); dataLayout->setAlignment(Qt::AlignLeft); dataLayout->addWidget(m_accelResidualX); dataLayout->addWidget(m_accelResidualY); dataLayout->addWidget(m_accelResidualZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Magnetometers (uT): ")); m_compassX = getFixedPanel("0"); m_compassY = getFixedPanel("0"); m_compassZ = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->setAlignment(Qt::AlignLeft); dataLayout->addSpacing(30); dataLayout->addWidget(m_compassX); dataLayout->addWidget(m_compassY); dataLayout->addWidget(m_compassZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Compass magnitude (uT): ")); m_compassMagnitude = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->addSpacing(30); dataLayout->addWidget(m_compassMagnitude); dataLayout->setAlignment(Qt::AlignLeft); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Pressure (hPa), height above sea level (m): ")); m_pressure = getFixedPanel("0"); m_height = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->addSpacing(30); dataLayout->addWidget(m_pressure); dataLayout->addWidget(m_height); dataLayout->setAlignment(Qt::AlignLeft); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Temperature (C): ")); m_temperature = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->addSpacing(30); dataLayout->addWidget(m_temperature); dataLayout->setAlignment(Qt::AlignLeft); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Humidity (RH): ")); m_humidity = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->addSpacing(30); dataLayout->addWidget(m_humidity); dataLayout->setAlignment(Qt::AlignLeft); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); QHBoxLayout *fusionBox = new QHBoxLayout(); QLabel *fusionTypeLabel = new QLabel("Fusion algorithm: "); fusionBox->addWidget(fusionTypeLabel); fusionTypeLabel->setMaximumWidth(150); m_fusionType = new QLabel(); fusionBox->addWidget(m_fusionType); vLayout->addLayout(fusionBox); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Fusion controls: ")); m_enableGyro = new QCheckBox("Enable gyros"); m_enableGyro->setChecked(true); vLayout->addWidget(m_enableGyro); m_enableAccel = new QCheckBox("Enable accels"); m_enableAccel->setChecked(true); vLayout->addWidget(m_enableAccel); m_enableCompass = new QCheckBox("Enable compass"); m_enableCompass->setChecked(true); vLayout->addWidget(m_enableCompass); m_enableDebug = new QCheckBox("Enable debug messages"); m_enableDebug->setChecked(false); vLayout->addWidget(m_enableDebug); vLayout->addStretch(1); mainLayout->addLayout(vLayout); vLayout = new QVBoxLayout(); vLayout->setContentsMargins(3, 3, 3, 3); vLayout->setSpacing(3); QHBoxLayout *displayLayout = new QHBoxLayout(); QLabel *displayLabel = new QLabel("Display type: "); displayLayout->addWidget(displayLabel); displayLayout->setAlignment(displayLabel, Qt::AlignRight); m_displaySelect = new QComboBox(); m_displaySelect->addItem("Fusion pose", DISPLAY_FUSION); m_displaySelect->addItem("Measured pose", DISPLAY_MEASURED); m_displaySelect->addItem("Accels only", DISPLAY_ACCELONLY); m_displaySelect->addItem("Compass only", DISPLAY_COMPASSONLY); displayLayout->addWidget(m_displaySelect); vLayout->addLayout(displayLayout); m_view = new IMUView(this); vLayout->addWidget(m_view); mainLayout->addLayout(vLayout, 1); centralWidget()->setLayout(mainLayout); setMinimumWidth(1000); setMinimumHeight(700); } QLabel* RTIMULibDemoGL::getFixedPanel(QString text) { QLabel *label = new QLabel(text); label->setFrameStyle(QFrame::Panel); label->setFixedSize(QSize(100, 16)); return label; } void RTIMULibDemoGL::layoutStatusBar() { m_rateStatus = new QLabel(this); m_rateStatus->setAlignment(Qt::AlignLeft); ui.statusBar->addWidget(m_rateStatus, 1); m_calStatus = new QLabel(this); m_calStatus->setAlignment(Qt::AlignLeft); ui.statusBar->addWidget(m_calStatus, 0); } rtimulib-7.2.1/Linux/RTIMULibDemoGL/RTIMULibDemoGL.h000066400000000000000000000073731254201074400215670ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTIMULIBDEMOGL_H #define _RTIMULIBDEMOGL_H #include #include #include #include #include "ui_RTIMULibDemoGL.h" #include "RTIMULib.h" // Display type codes #define DISPLAY_FUSION 0 // displays fusion algorithm output #define DISPLAY_MEASURED 1 // measured from accels and compass #define DISPLAY_ACCELONLY 2 // just the accel data #define DISPLAY_COMPASSONLY 3 // just the compass data class IMUView; class IMUThread; class RTIMULibDemoGL : public QMainWindow { Q_OBJECT public: RTIMULibDemoGL(); ~RTIMULibDemoGL(); public slots: void onSelectFusionAlgorithm(); void onCalibrateAccelerometers(); void onCalibrateMagnetometers(); void onSelectIMU(); void onEnableGyro(int); void onEnableAccel(int); void onEnableCompass(int); void onEnableDebug(int); void newIMUData(const RTIMU_DATA&); signals: void newIMU(); protected: void timerEvent(QTimerEvent *event); void closeEvent(QCloseEvent *event); private: void layoutStatusBar(); void layoutWindow(); QLabel *getFixedPanel(QString text); IMUThread *m_imuThread; // the thread that operates the imu RTIMU_DATA m_imuData; // this holds the IMU information and funsion output // Qt GUI stuff Ui::RTIMULibDemoGLClass ui; QLabel *m_fusionQPoseScalar; QLabel *m_fusionQPoseX; QLabel *m_fusionQPoseY; QLabel *m_fusionQPoseZ; QLabel *m_fusionPoseX; QLabel *m_fusionPoseY; QLabel *m_fusionPoseZ; QLabel *m_gyroX; QLabel *m_gyroY; QLabel *m_gyroZ; QLabel *m_accelX; QLabel *m_accelY; QLabel *m_accelZ; QLabel *m_accelMagnitude; QLabel *m_accelResidualX; QLabel *m_accelResidualY; QLabel *m_accelResidualZ; QLabel *m_compassX; QLabel *m_compassY; QLabel *m_compassZ; QLabel *m_compassMagnitude; QLabel *m_pressure; QLabel *m_height; QLabel *m_temperature; QLabel *m_humidity; QLabel *m_fusionType; QCheckBox *m_enableGyro; QCheckBox *m_enableAccel; QCheckBox *m_enableCompass; QCheckBox *m_enableDebug; QLabel *m_imuType; QLabel *m_biasStatus; QLabel *m_rateStatus; QLabel *m_calStatus; IMUView *m_view; QComboBox *m_displaySelect; int m_rateTimer; int m_displayTimer; int m_sampleCount; }; #endif // _RTIMULIBDEMOGL_H rtimulib-7.2.1/Linux/RTIMULibDemoGL/RTIMULibDemoGL.pri000066400000000000000000000032171254201074400221230ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014-2015, richards-tech, LLC #// #// Permission 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. INCLUDEPATH += $$PWD DEPENDPATH += $$PWD HEADERS += RTIMULibDemoGL.h \ SelectIMUDlg.h \ SelectFusionDlg.h \ IMUThread.h \ AccelCalDlg.h \ MagCalDlg.h \ SOURCES += main.cpp \ RTIMULibDemoGL.cpp \ SelectIMUDlg.cpp \ SelectFusionDlg.cpp \ IMUThread.cpp \ AccelCalDlg.cpp \ MagCalDlg.cpp \ FORMS += RTIMULibDemoGL.ui rtimulib-7.2.1/Linux/RTIMULibDemoGL/RTIMULibDemoGL.pro000066400000000000000000000034051254201074400221300ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014-2015, richards-tech, LLC #// #// Permission 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. greaterThan(QT_MAJOR_VERSION, 4): cache() TEMPLATE = app TARGET = RTIMULibDemoGL DESTDIR = Output QT += core gui opengl greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += debug_and_release target.path = /usr/local/bin INSTALLS += target DEFINES += QT_NETWORK_LIB INCLUDEPATH += GeneratedFiles MOC_DIR += GeneratedFiles/moc OBJECTS_DIR += objects UI_DIR += GeneratedFiles RCC_DIR += GeneratedFiles include(RTIMULibDemoGL.pri) include(../../RTIMULib/RTIMULib.pri) include(../RTIMULibGL/RTIMULibGL.pri) rtimulib-7.2.1/Linux/RTIMULibDemoGL/RTIMULibDemoGL.ui000066400000000000000000000052711254201074400217500ustar00rootroot00000000000000 RTIMULibDemoGLClass 0 0 658 562 0 0 RTIMULibDemoGL 400 300 0 0 658 25 Actions toolBar TopToolBarArea false Select IMU Select fusion algorithm Calibrate magnetometers Calibrate accelerometers Exit rtimulib-7.2.1/Linux/RTIMULibDemoGL/SelectFusionDlg.cpp000066400000000000000000000053121254201074400225640ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "SelectFusionDlg.h" #include "RTIMUSettings.h" #include "RTFusion.h" #include #include SelectFusionDlg::SelectFusionDlg(RTIMUSettings *settings, QWidget *parent) : QDialog(parent, Qt::WindowCloseButtonHint | Qt::WindowTitleHint) { m_settings = settings; layoutWindow(); setWindowTitle("Select Fusion algorithm"); connect(m_buttons, SIGNAL(accepted()), this, SLOT(onOk())); connect(m_buttons, SIGNAL(rejected()), this, SLOT(onCancel())); } SelectFusionDlg::~SelectFusionDlg() { } void SelectFusionDlg::onOk() { m_settings->m_fusionType = m_selectFusion->currentIndex(); m_settings->saveSettings(); accept(); } void SelectFusionDlg::onCancel() { reject(); } void SelectFusionDlg::layoutWindow() { QVBoxLayout *mainLayout; QFormLayout *form; setModal(true); mainLayout = new QVBoxLayout(this); mainLayout->setSpacing(20); mainLayout->setContentsMargins(11, 11, 11, 11); form = new QFormLayout(); mainLayout->addLayout(form); m_selectFusion = new QComboBox(); for (int i = 0; i < RTFUSION_TYPE_COUNT; i++) m_selectFusion->addItem(RTFusion::fusionName(i)); m_selectFusion->setCurrentIndex(m_settings->m_fusionType); form->addRow("Select Fusion algorithm type: ", m_selectFusion); m_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this); m_buttons->setCenterButtons(true); mainLayout->addWidget(m_buttons); } rtimulib-7.2.1/Linux/RTIMULibDemoGL/SelectFusionDlg.h000066400000000000000000000033471254201074400222370ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef SELECTFUSIONDLG_H #define SELECTFUSIONDLG_H #include #include #include class RTIMUSettings; class SelectFusionDlg : public QDialog { Q_OBJECT public: SelectFusionDlg(RTIMUSettings *settings, QWidget *parent = 0); ~SelectFusionDlg(); public slots: void onOk(); void onCancel(); private: void layoutWindow(); RTIMUSettings *m_settings; QDialogButtonBox *m_buttons; QComboBox *m_selectFusion; }; #endif // SELECTFUSIONDLG_H rtimulib-7.2.1/Linux/RTIMULibDemoGL/SelectIMUDlg.cpp000066400000000000000000000214131254201074400217530ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "SelectIMUDlg.h" #include "RTIMUSettings.h" #include "IMUDrivers/RTIMUDefs.h" #include #include SelectIMUDlg::SelectIMUDlg(RTIMUSettings *settings, QWidget *parent) : QDialog(parent, Qt::WindowCloseButtonHint | Qt::WindowTitleHint) { m_settings = settings; layoutWindow(); setWindowTitle("Select IMU"); connect(m_buttons, SIGNAL(accepted()), this, SLOT(onOk())); connect(m_buttons, SIGNAL(rejected()), this, SLOT(onCancel())); connect(m_selectIMU, SIGNAL(currentIndexChanged(int)), this, SLOT(setSelectAddress(int))); connect(m_selectBus, SIGNAL(currentIndexChanged(int)), this, SLOT(setSelectAddress(int))); } SelectIMUDlg::~SelectIMUDlg() { } void SelectIMUDlg::onOk() { if (m_selectBus->currentIndex() < 8) { // I2C m_settings->m_busIsI2C = true; m_settings->m_I2CBus = m_selectBus->currentIndex(); m_settings->m_I2CSlaveAddress = m_selectAddress->itemData(m_selectAddress->currentIndex()).toInt(); } else { // SPI m_settings->m_busIsI2C = false; m_settings->m_SPIBus = m_selectBus->currentIndex() - 8; } m_settings->m_imuType = m_selectIMU->currentIndex(); m_settings->saveSettings(); accept(); } void SelectIMUDlg::onCancel() { reject(); } void SelectIMUDlg::layoutWindow() { QVBoxLayout *mainLayout; QFormLayout *form; setModal(true); mainLayout = new QVBoxLayout(this); mainLayout->setSpacing(20); mainLayout->setContentsMargins(11, 11, 11, 11); form = new QFormLayout(); mainLayout->addLayout(form); m_selectBus = new QComboBox(); m_selectBus->addItem("I2C bus 0"); m_selectBus->addItem("I2C bus 1"); m_selectBus->addItem("I2C bus 2"); m_selectBus->addItem("I2C bus 3"); m_selectBus->addItem("I2C bus 4"); m_selectBus->addItem("I2C bus 5"); m_selectBus->addItem("I2C bus 6"); m_selectBus->addItem("I2C bus 7"); m_selectBus->addItem("SPI bus 0"); m_selectBus->addItem("SPI bus 1"); m_selectBus->addItem("SPI bus 2"); m_selectBus->addItem("SPI bus 3"); m_selectBus->addItem("SPI bus 4"); m_selectBus->addItem("SPI bus 5"); m_selectBus->addItem("SPI bus 6"); m_selectBus->addItem("SPI bus 7"); if (m_settings->m_busIsI2C) m_selectBus->setCurrentIndex(m_settings->m_I2CBus); else m_selectBus->setCurrentIndex(m_settings->m_SPIBus + 8); form->addRow("select bus type: ", m_selectBus); m_selectIMU = new QComboBox(); m_selectIMU->addItem("Auto detect IMU"); m_selectIMU->addItem("Null IMU"); m_selectIMU->addItem("InvenSense MPU9150"); m_selectIMU->addItem("STM L3GD20H/LSM303D"); m_selectIMU->addItem("STM L3GD20/LSM303DLHC"); m_selectIMU->addItem("STM LSM9DS0"); m_selectIMU->addItem("STM LSM9DS1"); m_selectIMU->addItem("InvenSense MPU9250"); m_selectIMU->addItem("STM L3GD20H/LSM303DLHC"); m_selectIMU->addItem("Bosch BMX055"); m_selectIMU->addItem("Bosch BNO055"); m_selectIMU->setCurrentIndex(m_settings->m_imuType); form->addRow("select IMU type: ", m_selectIMU); m_selectAddress = new QComboBox(); setSelectAddress(m_settings->m_imuType, m_settings->m_I2CSlaveAddress); form->addRow("select I2C address type: ", m_selectAddress); m_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this); m_buttons->setCenterButtons(true); mainLayout->addWidget(m_buttons); } void SelectIMUDlg::setSelectAddress(int) { int imuType = m_selectIMU->currentIndex(); if (imuType == m_settings->m_imuType) setSelectAddress(imuType, m_settings->m_I2CSlaveAddress); else setSelectAddress(imuType, -1); } void SelectIMUDlg::setSelectAddress(int imuType, int slaveAddress) { m_selectAddress->clear(); if (m_selectBus->currentIndex() < 8) { switch (imuType) { case RTIMU_TYPE_MPU9150: m_selectAddress->addItem("Standard (0x68)", MPU9150_ADDRESS0); m_selectAddress->addItem("Option (0x69)", MPU9150_ADDRESS1); if (slaveAddress == MPU9150_ADDRESS1) m_selectAddress->setCurrentIndex(1); else m_selectAddress->setCurrentIndex(0); break; case RTIMU_TYPE_MPU9250: m_selectAddress->addItem("Standard (0x68)", MPU9250_ADDRESS0); m_selectAddress->addItem("Option (0x69)", MPU9250_ADDRESS1); if (slaveAddress == MPU9250_ADDRESS1) m_selectAddress->setCurrentIndex(1); else m_selectAddress->setCurrentIndex(0); break; case RTIMU_TYPE_GD20HM303D: m_selectAddress->addItem("Standard (0x6a)", L3GD20H_ADDRESS0); m_selectAddress->addItem("Option (0x6b)", L3GD20H_ADDRESS1); if (slaveAddress == L3GD20H_ADDRESS1) m_selectAddress->setCurrentIndex(1); else m_selectAddress->setCurrentIndex(0); break; case RTIMU_TYPE_GD20M303DLHC: m_selectAddress->addItem("Standard (0x6a)", L3GD20_ADDRESS0); m_selectAddress->addItem("Option (0x6b)", L3GD20_ADDRESS1); if (slaveAddress == L3GD20_ADDRESS1) m_selectAddress->setCurrentIndex(1); else m_selectAddress->setCurrentIndex(0); break; case RTIMU_TYPE_LSM9DS0: m_selectAddress->addItem("Standard (0x6a)", LSM9DS0_GYRO_ADDRESS0); m_selectAddress->addItem("Option (0x6b)", LSM9DS0_GYRO_ADDRESS1); if (slaveAddress == LSM9DS0_GYRO_ADDRESS1) m_selectAddress->setCurrentIndex(1); else m_selectAddress->setCurrentIndex(0); break; case RTIMU_TYPE_LSM9DS1: m_selectAddress->addItem("Standard (0x6a)", LSM9DS1_ADDRESS0); m_selectAddress->addItem("Option (0x6b)", LSM9DS1_ADDRESS1); if (slaveAddress == LSM9DS1_ADDRESS1) m_selectAddress->setCurrentIndex(1); else m_selectAddress->setCurrentIndex(0); break; case RTIMU_TYPE_GD20HM303DLHC: m_selectAddress->addItem("Standard (0x6a)", L3GD20H_ADDRESS0); m_selectAddress->addItem("Option (0x6b)", L3GD20H_ADDRESS1); if (slaveAddress == L3GD20H_ADDRESS1) m_selectAddress->setCurrentIndex(1); else m_selectAddress->setCurrentIndex(0); break; case RTIMU_TYPE_BMX055: m_selectAddress->addItem("Standard (0x68)", BMX055_GYRO_ADDRESS0); m_selectAddress->addItem("Option (0x69)", BMX055_GYRO_ADDRESS1); if (slaveAddress == BMX055_GYRO_ADDRESS1) m_selectAddress->setCurrentIndex(1); else m_selectAddress->setCurrentIndex(0); break; case RTIMU_TYPE_BNO055: m_selectAddress->addItem("Standard (0x28)", BNO055_ADDRESS0); m_selectAddress->addItem("Option (0x29)", BNO055_ADDRESS1); if (slaveAddress == BNO055_ADDRESS1) m_selectAddress->setCurrentIndex(1); else m_selectAddress->setCurrentIndex(0); break; default: m_selectAddress->addItem("N/A", 0); break; } } else { switch (imuType) { case RTIMU_TYPE_MPU9250: m_selectAddress->addItem("Standard", MPU9250_ADDRESS0); m_selectAddress->setCurrentIndex(0); break; default: m_selectAddress->addItem("N/A", 0); break; } } } rtimulib-7.2.1/Linux/RTIMULibDemoGL/SelectIMUDlg.h000066400000000000000000000035611254201074400214240ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef SELECTIMUDLG_H #define SELECTIMUDLG_H #include #include #include class RTIMUSettings; class SelectIMUDlg : public QDialog { Q_OBJECT public: SelectIMUDlg(RTIMUSettings *settings, QWidget *parent = 0); ~SelectIMUDlg(); public slots: void onOk(); void onCancel(); void setSelectAddress(int imuType); private: void layoutWindow(); void setSelectAddress(int imuType, int slaveAddress); RTIMUSettings *m_settings; QDialogButtonBox *m_buttons; QComboBox *m_selectBus; QComboBox *m_selectIMU; QComboBox *m_selectAddress; }; #endif // SELECTIMUDLG_H rtimulib-7.2.1/Linux/RTIMULibDemoGL/main.cpp000066400000000000000000000027261254201074400204640ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTIMULibDemoGL.h" #include // This is standard Qt startup int main(int argc, char *argv[]) { QApplication a(argc, argv); RTIMULibDemoGL *w = new RTIMULibDemoGL(); w->show(); return a.exec(); } rtimulib-7.2.1/Linux/RTIMULibDrive/000077500000000000000000000000001254201074400167675ustar00rootroot00000000000000rtimulib-7.2.1/Linux/RTIMULibDrive/CMakeLists.txt000066400000000000000000000030731254201074400215320ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014-2015, richards-tech, LLC #// #// Permission 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. #// The cmake support was based on work by Moritz Fischer at ettus.com. #// Original copyright notice: # # Copyright 2014 Ettus Research LLC # SET(DRIVE_SRCS RTIMULibDrive.cpp) ADD_EXECUTABLE(RTIMULibDrive ${DRIVE_SRCS}) TARGET_LINK_LIBRARIES(RTIMULibDrive RTIMULib) INSTALL(TARGETS RTIMULibDrive DESTINATION bin) rtimulib-7.2.1/Linux/RTIMULibDrive/Makefile000066400000000000000000000117271254201074400204370ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014-2015, richards-tech, LLC #// #// Permission 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. # Compiler, tools and options RTIMULIBPATH = ../../RTIMULib CC = gcc CXX = g++ DEFINES = CFLAGS = -pipe -O2 -Wall -W $(DEFINES) CXXFLAGS = -pipe -O2 -Wall -W $(DEFINES) INCPATH = -I. -I$(RTIMULIBPATH) LINK = g++ LFLAGS = -Wl,-O1 LIBS = -L/usr/lib/arm-linux-gnueabihf COPY = cp -f COPY_FILE = $(COPY) COPY_DIR = $(COPY) -r STRIP = strip INSTALL_FILE = install -m 644 -p INSTALL_DIR = $(COPY_DIR) INSTALL_PROGRAM = install -m 755 -p DEL_FILE = rm -f SYMLINK = ln -f -s DEL_DIR = rmdir MOVE = mv -f CHK_DIR_EXISTS = test -d MKDIR = mkdir -p # Output directory OBJECTS_DIR = objects/ # Files DEPS = $(RTIMULIBPATH)/RTMath.h \ $(RTIMULIBPATH)/RTIMULib.h \ $(RTIMULIBPATH)/RTIMULibDefs.h \ $(RTIMULIBPATH)/RTIMUHal.h \ $(RTIMULIBPATH)/RTFusion.h \ $(RTIMULIBPATH)/RTFusionKalman4.h \ $(RTIMULIBPATH)/RTFusionRTQF.h \ $(RTIMULIBPATH)/RTIMUSettings.h \ $(RTIMULIBPATH)/RTIMUAccelCal.h \ $(RTIMULIBPATH)/RTIMUMagCal.h \ $(RTIMULIBPATH)/RTIMUCalDefs.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMU.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUNull.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUMPU9150.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUMPU9250.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUGD20HM303D.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUGD20M303DLHC.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUGD20HM303DLHC.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMULSM9DS0.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMULSM9DS1.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUBMX055.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUBNO055.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressure.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressureBMP180.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressureLPS25H.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressureMS5611.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressureMS5637.h OBJECTS = objects/RTIMULibDrive.o \ objects/RTMath.o \ objects/RTIMUHal.o \ objects/RTFusion.o \ objects/RTFusionKalman4.o \ objects/RTFusionRTQF.o \ objects/RTIMUSettings.o \ objects/RTIMUAccelCal.o \ objects/RTIMUMagCal.o \ objects/RTIMU.o \ objects/RTIMUNull.o \ objects/RTIMUMPU9150.o \ objects/RTIMUMPU9250.o \ objects/RTIMUGD20HM303D.o \ objects/RTIMUGD20M303DLHC.o \ objects/RTIMUGD20HM303DLHC.o \ objects/RTIMULSM9DS0.o \ objects/RTIMULSM9DS1.o \ objects/RTIMUBMX055.o \ objects/RTIMUBNO055.o \ objects/RTPressure.o \ objects/RTPressureBMP180.o \ objects/RTPressureLPS25H.o \ objects/RTPressureMS5611.o \ objects/RTPressureMS5637.o MAKE_TARGET = RTIMULibDrive DESTDIR = Output/ TARGET = Output/$(MAKE_TARGET) # Build rules $(TARGET): $(OBJECTS) @$(CHK_DIR_EXISTS) Output/ || $(MKDIR) Output/ $(LINK) $(LFLAGS) -o $(TARGET) $(OBJECTS) $(LIBS) clean: -$(DEL_FILE) $(OBJECTS) -$(DEL_FILE) *~ core *.core # Compile $(OBJECTS_DIR)%.o : $(RTIMULIBPATH)/%.cpp $(DEPS) @$(CHK_DIR_EXISTS) objects/ || $(MKDIR) objects/ $(CXX) -c -o $@ $< $(CFLAGS) $(INCPATH) $(OBJECTS_DIR)%.o : $(RTIMULIBPATH)/IMUDrivers/%.cpp $(DEPS) @$(CHK_DIR_EXISTS) objects/ || $(MKDIR) objects/ $(CXX) -c -o $@ $< $(CFLAGS) $(INCPATH) $(OBJECTS_DIR)RTIMULibDrive.o : RTIMULibDrive.cpp $(DEPS) @$(CHK_DIR_EXISTS) objects/ || $(MKDIR) objects/ $(CXX) -c -o $@ RTIMULibDrive.cpp $(CFLAGS) $(INCPATH) # Install install_target: FORCE @$(CHK_DIR_EXISTS) $(INSTALL_ROOT)/usr/local/bin/ || $(MKDIR) $(INSTALL_ROOT)/usr/local/bin/ -$(INSTALL_PROGRAM) "Output/$(MAKE_TARGET)" "$(INSTALL_ROOT)/usr/local/bin/$(MAKE_TARGET)" -$(STRIP) "$(INSTALL_ROOT)/usr/local/bin/$(MAKE_TARGET)" uninstall_target: FORCE -$(DEL_FILE) "$(INSTALL_ROOT)/usr/local/bin/$(MAKE_TARGET)" install: install_target FORCE uninstall: uninstall_target FORCE FORCE: rtimulib-7.2.1/Linux/RTIMULibDrive/RTIMULibDrive.cpp000066400000000000000000000062251254201074400220210ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTIMULib.h" int main() { int sampleCount = 0; int sampleRate = 0; uint64_t rateTimer; uint64_t displayTimer; uint64_t now; // Using RTIMULib here allows it to use the .ini file generated by RTIMULibDemo. // Or, you can create the .ini in some other directory by using: // RTIMUSettings *settings = new RTIMUSettings("", "RTIMULib"); // where is the path to where the .ini file is to be loaded/saved RTIMUSettings *settings = new RTIMUSettings("RTIMULib"); RTIMU *imu = RTIMU::createIMU(settings); if ((imu == NULL) || (imu->IMUType() == RTIMU_TYPE_NULL)) { printf("No IMU found\n"); exit(1); } // This is an opportunity to manually override any settings before the call IMUInit // set up IMU imu->IMUInit(); // this is a convenient place to change fusion parameters imu->setSlerpPower(0.02); imu->setGyroEnable(true); imu->setAccelEnable(true); imu->setCompassEnable(true); // set up for rate timer rateTimer = displayTimer = RTMath::currentUSecsSinceEpoch(); // now just process data while (1) { // poll at the rate recommended by the IMU usleep(imu->IMUGetPollInterval() * 1000); while (imu->IMURead()) { RTIMU_DATA imuData = imu->getIMUData(); sampleCount++; now = RTMath::currentUSecsSinceEpoch(); // display 10 times per second if ((now - displayTimer) > 100000) { printf("Sample rate %d: %s\r", sampleRate, RTMath::displayDegrees("", imuData.fusionPose)); fflush(stdout); displayTimer = now; } // update rate every second if ((now - rateTimer) > 1000000) { sampleRate = sampleCount; sampleCount = 0; rateTimer = now; } } } } rtimulib-7.2.1/Linux/RTIMULibDrive10/000077500000000000000000000000001254201074400171305ustar00rootroot00000000000000rtimulib-7.2.1/Linux/RTIMULibDrive10/CMakeLists.txt000066400000000000000000000031031254201074400216650ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014-2015, richards-tech, LLC #// #// Permission 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. #// The cmake support was based on work by Moritz Fischer at ettus.com. #// Original copyright notice: # # Copyright 2014 Ettus Research LLC # SET(DRIVE_SRCS RTIMULibDrive10.cpp) ADD_EXECUTABLE(RTIMULibDrive10 ${DRIVE_SRCS}) TARGET_LINK_LIBRARIES(RTIMULibDrive10 RTIMULib) INSTALL(TARGETS RTIMULibDrive10 DESTINATION bin) rtimulib-7.2.1/Linux/RTIMULibDrive10/Makefile000066400000000000000000000117411254201074400205740ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014-2015, richards-tech, LLC #// #// Permission 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. # Compiler, tools and options RTIMULIBPATH = ../../RTIMULib CC = gcc CXX = g++ DEFINES = CFLAGS = -pipe -O2 -Wall -W $(DEFINES) CXXFLAGS = -pipe -O2 -Wall -W $(DEFINES) INCPATH = -I. -I$(RTIMULIBPATH) LINK = g++ LFLAGS = -Wl,-O1 LIBS = -L/usr/lib/arm-linux-gnueabihf COPY = cp -f COPY_FILE = $(COPY) COPY_DIR = $(COPY) -r STRIP = strip INSTALL_FILE = install -m 644 -p INSTALL_DIR = $(COPY_DIR) INSTALL_PROGRAM = install -m 755 -p DEL_FILE = rm -f SYMLINK = ln -f -s DEL_DIR = rmdir MOVE = mv -f CHK_DIR_EXISTS = test -d MKDIR = mkdir -p # Output directory OBJECTS_DIR = objects/ # Files DEPS = $(RTIMULIBPATH)/RTMath.h \ $(RTIMULIBPATH)/RTIMULib.h \ $(RTIMULIBPATH)/RTIMULibDefs.h \ $(RTIMULIBPATH)/RTIMUHal.h \ $(RTIMULIBPATH)/RTFusion.h \ $(RTIMULIBPATH)/RTFusionKalman4.h \ $(RTIMULIBPATH)/RTFusionRTQF.h \ $(RTIMULIBPATH)/RTIMUSettings.h \ $(RTIMULIBPATH)/RTIMUAccelCal.h \ $(RTIMULIBPATH)/RTIMUMagCal.h \ $(RTIMULIBPATH)/RTIMUCalDefs.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMU.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUNull.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUMPU9150.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUMPU9250.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUGD20HM303D.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUGD20M303DLHC.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUGD20HM303DLHC.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMULSM9DS0.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMULSM9DS1.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUBMX055.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUBNO055.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressure.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressureBMP180.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressureLPS25H.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressureMS5611.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressureMS5637.h OBJECTS = objects/RTIMULibDrive10.o \ objects/RTMath.o \ objects/RTIMUHal.o \ objects/RTFusion.o \ objects/RTFusionKalman4.o \ objects/RTFusionRTQF.o \ objects/RTIMUSettings.o \ objects/RTIMUAccelCal.o \ objects/RTIMUMagCal.o \ objects/RTIMU.o \ objects/RTIMUNull.o \ objects/RTIMUMPU9150.o \ objects/RTIMUMPU9250.o \ objects/RTIMUGD20HM303D.o \ objects/RTIMUGD20M303DLHC.o \ objects/RTIMUGD20HM303DLHC.o \ objects/RTIMULSM9DS0.o \ objects/RTIMULSM9DS1.o \ objects/RTIMUBMX055.o \ objects/RTIMUBNO055.o \ objects/RTPressure.o \ objects/RTPressureBMP180.o \ objects/RTPressureLPS25H.o \ objects/RTPressureMS5611.o \ objects/RTPressureMS5637.o MAKE_TARGET = RTIMULibDrive10 DESTDIR = Output/ TARGET = Output/$(MAKE_TARGET) # Build rules $(TARGET): $(OBJECTS) @$(CHK_DIR_EXISTS) Output/ || $(MKDIR) Output/ $(LINK) $(LFLAGS) -o $(TARGET) $(OBJECTS) $(LIBS) clean: -$(DEL_FILE) $(OBJECTS) -$(DEL_FILE) *~ core *.core # Compile $(OBJECTS_DIR)%.o : $(RTIMULIBPATH)/%.cpp $(DEPS) @$(CHK_DIR_EXISTS) objects/ || $(MKDIR) objects/ $(CXX) -c -o $@ $< $(CFLAGS) $(INCPATH) $(OBJECTS_DIR)%.o : $(RTIMULIBPATH)/IMUDrivers/%.cpp $(DEPS) @$(CHK_DIR_EXISTS) objects/ || $(MKDIR) objects/ $(CXX) -c -o $@ $< $(CFLAGS) $(INCPATH) $(OBJECTS_DIR)RTIMULibDrive10.o : RTIMULibDrive10.cpp $(DEPS) @$(CHK_DIR_EXISTS) objects/ || $(MKDIR) objects/ $(CXX) -c -o $@ RTIMULibDrive10.cpp $(CFLAGS) $(INCPATH) # Install install_target: FORCE @$(CHK_DIR_EXISTS) $(INSTALL_ROOT)/usr/local/bin/ || $(MKDIR) $(INSTALL_ROOT)/usr/local/bin/ -$(INSTALL_PROGRAM) "Output/$(MAKE_TARGET)" "$(INSTALL_ROOT)/usr/local/bin/$(MAKE_TARGET)" -$(STRIP) "$(INSTALL_ROOT)/usr/local/bin/$(MAKE_TARGET)" uninstall_target: FORCE -$(DEL_FILE) "$(INSTALL_ROOT)/usr/local/bin/$(MAKE_TARGET)" install: install_target FORCE uninstall: uninstall_target FORCE FORCE: rtimulib-7.2.1/Linux/RTIMULibDrive10/RTIMULibDrive10.cpp000066400000000000000000000067361254201074400223320ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTIMULib.h" int main() { int sampleCount = 0; int sampleRate = 0; uint64_t rateTimer; uint64_t displayTimer; uint64_t now; // using RTIMULib here allows it to use the .ini file generated by RTIMULibDemo. RTIMUSettings *settings = new RTIMUSettings("RTIMULib"); RTIMU *imu = RTIMU::createIMU(settings); RTPressure *pressure = RTPressure::createPressure(settings); if ((imu == NULL) || (imu->IMUType() == RTIMU_TYPE_NULL)) { printf("No IMU found\n"); exit(1); } // This is an opportunity to manually override any settings before the call IMUInit // set up IMU imu->IMUInit(); // this is a convenient place to change fusion parameters imu->setSlerpPower(0.02); imu->setGyroEnable(true); imu->setAccelEnable(true); imu->setCompassEnable(true); // set up pressure sensor if (pressure != NULL) pressure->pressureInit(); // set up for rate timer rateTimer = displayTimer = RTMath::currentUSecsSinceEpoch(); // now just process data while (1) { // poll at the rate recommended by the IMU usleep(imu->IMUGetPollInterval() * 1000); while (imu->IMURead()) { RTIMU_DATA imuData = imu->getIMUData(); // add the pressure data to the structure if (pressure != NULL) pressure->pressureRead(imuData); sampleCount++; now = RTMath::currentUSecsSinceEpoch(); // display 10 times per second if ((now - displayTimer) > 100000) { printf("Sample rate %d: %s\n", sampleRate, RTMath::displayDegrees("", imuData.fusionPose)); if (pressure != NULL) { printf("Pressure: %4.1f, height above sea level: %4.1f, temperature: %4.1f\n", imuData.pressure, RTMath::convertPressureToHeight(imuData.pressure), imuData.temperature); } fflush(stdout); displayTimer = now; } // update rate every second if ((now - rateTimer) > 1000000) { sampleRate = sampleCount; sampleCount = 0; rateTimer = now; } } } } rtimulib-7.2.1/Linux/RTIMULibDrive11/000077500000000000000000000000001254201074400171315ustar00rootroot00000000000000rtimulib-7.2.1/Linux/RTIMULibDrive11/CMakeLists.txt000066400000000000000000000031031254201074400216660ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014-2015, richards-tech, LLC #// #// Permission 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. #// The cmake support was based on work by Moritz Fischer at ettus.com. #// Original copyright notice: # # Copyright 2014 Ettus Research LLC # SET(DRIVE_SRCS RTIMULibDrive11.cpp) ADD_EXECUTABLE(RTIMULibDrive11 ${DRIVE_SRCS}) TARGET_LINK_LIBRARIES(RTIMULibDrive11 RTIMULib) INSTALL(TARGETS RTIMULibDrive11 DESTINATION bin) rtimulib-7.2.1/Linux/RTIMULibDrive11/Makefile000066400000000000000000000124721254201074400205770ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014-2015, richards-tech, LLC #// #// Permission 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. # Compiler, tools and options RTIMULIBPATH = ../../RTIMULib CC = gcc CXX = g++ DEFINES = CFLAGS = -pipe -O2 -Wall -W $(DEFINES) CXXFLAGS = -pipe -O2 -Wall -W $(DEFINES) INCPATH = -I. -I$(RTIMULIBPATH) LINK = g++ LFLAGS = -Wl,-O1 LIBS = -L/usr/lib/arm-linux-gnueabihf COPY = cp -f COPY_FILE = $(COPY) COPY_DIR = $(COPY) -r STRIP = strip INSTALL_FILE = install -m 644 -p INSTALL_DIR = $(COPY_DIR) INSTALL_PROGRAM = install -m 755 -p DEL_FILE = rm -f SYMLINK = ln -f -s DEL_DIR = rmdir MOVE = mv -f CHK_DIR_EXISTS = test -d MKDIR = mkdir -p # Output directory OBJECTS_DIR = objects/ # Files DEPS = $(RTIMULIBPATH)/RTMath.h \ $(RTIMULIBPATH)/RTIMULib.h \ $(RTIMULIBPATH)/RTIMULibDefs.h \ $(RTIMULIBPATH)/RTIMUHal.h \ $(RTIMULIBPATH)/RTFusion.h \ $(RTIMULIBPATH)/RTFusionKalman4.h \ $(RTIMULIBPATH)/RTFusionRTQF.h \ $(RTIMULIBPATH)/RTIMUSettings.h \ $(RTIMULIBPATH)/RTIMUAccelCal.h \ $(RTIMULIBPATH)/RTIMUMagCal.h \ $(RTIMULIBPATH)/RTIMUCalDefs.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMU.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUNull.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUMPU9150.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUMPU9250.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUGD20HM303D.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUGD20M303DLHC.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUGD20HM303DLHC.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMULSM9DS0.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMULSM9DS1.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUBMX055.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUBNO055.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressure.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressureDefs.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressureBMP180.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressureLPS25H.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressureMS5611.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressureMS5637.h \ $(RTIMULIBPATH)/IMUDrivers/RTHumidity.h \ $(RTIMULIBPATH)/IMUDrivers/RTHumidityDefs.h \ $(RTIMULIBPATH)/IMUDrivers/RTHumidityHTS221.h \ $(RTIMULIBPATH)/IMUDrivers/RTHumidityHTU21D.h \ OBJECTS = objects/RTIMULibDrive11.o \ objects/RTMath.o \ objects/RTIMUHal.o \ objects/RTFusion.o \ objects/RTFusionKalman4.o \ objects/RTFusionRTQF.o \ objects/RTIMUSettings.o \ objects/RTIMUAccelCal.o \ objects/RTIMUMagCal.o \ objects/RTIMU.o \ objects/RTIMUNull.o \ objects/RTIMUMPU9150.o \ objects/RTIMUMPU9250.o \ objects/RTIMUGD20HM303D.o \ objects/RTIMUGD20M303DLHC.o \ objects/RTIMUGD20HM303DLHC.o \ objects/RTIMULSM9DS0.o \ objects/RTIMULSM9DS1.o \ objects/RTIMUBMX055.o \ objects/RTIMUBNO055.o \ objects/RTPressure.o \ objects/RTPressureBMP180.o \ objects/RTPressureLPS25H.o \ objects/RTPressureMS5611.o \ objects/RTPressureMS5637.o \ objects/RTHumidity.o \ objects/RTHumidityHTS221.o \ objects/RTHumidityHTU21D.o \ MAKE_TARGET = RTIMULibDrive11 DESTDIR = Output/ TARGET = Output/$(MAKE_TARGET) # Build rules $(TARGET): $(OBJECTS) @$(CHK_DIR_EXISTS) Output/ || $(MKDIR) Output/ $(LINK) $(LFLAGS) -o $(TARGET) $(OBJECTS) $(LIBS) clean: -$(DEL_FILE) $(OBJECTS) -$(DEL_FILE) *~ core *.core # Compile $(OBJECTS_DIR)%.o : $(RTIMULIBPATH)/%.cpp $(DEPS) @$(CHK_DIR_EXISTS) objects/ || $(MKDIR) objects/ $(CXX) -c -o $@ $< $(CFLAGS) $(INCPATH) $(OBJECTS_DIR)%.o : $(RTIMULIBPATH)/IMUDrivers/%.cpp $(DEPS) @$(CHK_DIR_EXISTS) objects/ || $(MKDIR) objects/ $(CXX) -c -o $@ $< $(CFLAGS) $(INCPATH) $(OBJECTS_DIR)RTIMULibDrive11.o : RTIMULibDrive11.cpp $(DEPS) @$(CHK_DIR_EXISTS) objects/ || $(MKDIR) objects/ $(CXX) -c -o $@ RTIMULibDrive11.cpp $(CFLAGS) $(INCPATH) # Install install_target: FORCE @$(CHK_DIR_EXISTS) $(INSTALL_ROOT)/usr/local/bin/ || $(MKDIR) $(INSTALL_ROOT)/usr/local/bin/ -$(INSTALL_PROGRAM) "Output/$(MAKE_TARGET)" "$(INSTALL_ROOT)/usr/local/bin/$(MAKE_TARGET)" -$(STRIP) "$(INSTALL_ROOT)/usr/local/bin/$(MAKE_TARGET)" uninstall_target: FORCE -$(DEL_FILE) "$(INSTALL_ROOT)/usr/local/bin/$(MAKE_TARGET)" install: install_target FORCE uninstall: uninstall_target FORCE FORCE: rtimulib-7.2.1/Linux/RTIMULibDrive11/RTIMULibDrive11.cpp000066400000000000000000000076731254201074400223350ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTIMULib.h" int main() { int sampleCount = 0; int sampleRate = 0; uint64_t rateTimer; uint64_t displayTimer; uint64_t now; // using RTIMULib here allows it to use the .ini file generated by RTIMULibDemo. RTIMUSettings *settings = new RTIMUSettings("RTIMULib"); RTIMU *imu = RTIMU::createIMU(settings); RTPressure *pressure = RTPressure::createPressure(settings); RTHumidity *humidity = RTHumidity::createHumidity(settings); if ((imu == NULL) || (imu->IMUType() == RTIMU_TYPE_NULL)) { printf("No IMU found\n"); exit(1); } // This is an opportunity to manually override any settings before the call IMUInit // set up IMU imu->IMUInit(); // this is a convenient place to change fusion parameters imu->setSlerpPower(0.02); imu->setGyroEnable(true); imu->setAccelEnable(true); imu->setCompassEnable(true); // set up pressure sensor if (pressure != NULL) pressure->pressureInit(); // set up humidity sensor if (humidity != NULL) humidity->humidityInit(); // set up for rate timer rateTimer = displayTimer = RTMath::currentUSecsSinceEpoch(); // now just process data while (1) { // poll at the rate recommended by the IMU usleep(imu->IMUGetPollInterval() * 1000); while (imu->IMURead()) { RTIMU_DATA imuData = imu->getIMUData(); // add the pressure data to the structure if (pressure != NULL) pressure->pressureRead(imuData); // add the humidity data to the structure if (humidity != NULL) humidity->humidityRead(imuData); sampleCount++; now = RTMath::currentUSecsSinceEpoch(); // display 5 times per second if ((now - displayTimer) > 200000) { printf("Sample rate %d: %s\n", sampleRate, RTMath::displayDegrees("", imuData.fusionPose)); if (pressure != NULL) { printf("Pressure: %4.1f, height above sea level: %4.1f, temperature: %4.1f", imuData.pressure, RTMath::convertPressureToHeight(imuData.pressure), imuData.temperature); } if (humidity != NULL) { printf(", humidity: %4.1f", imuData.humidity); } printf("\n"); fflush(stdout); displayTimer = now; } // update rate every second if ((now - rateTimer) > 1000000) { sampleRate = sampleCount; sampleCount = 0; rateTimer = now; } } } } rtimulib-7.2.1/Linux/RTIMULibGL/000077500000000000000000000000001254201074400162205ustar00rootroot00000000000000rtimulib-7.2.1/Linux/RTIMULibGL/CMakeLists.txt000066400000000000000000000104601254201074400207610ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014, richards-tech #// #// Permission 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. #// The cmake support was based on work by Moritz Fischer at ettus.com. #// Original copyright notice: # # Copyright 2014 Ettus Research LLC # INCLUDE(${CMAKE_CURRENT_SOURCE_DIR}/../../RTIMULibVersion.txt) SET(LIBGL_SRCS IMUView.cpp) SET(GL_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib/QtGLADSShader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib/QtGLADSTextureShader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib/QtGLComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib/QtGL.cpp ${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib/QtGLCylinderComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib/QtGLDiskComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib/QtGLFlatShader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib/QtGLPlaneComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib/QtGLSphereComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib/QtGLTextureShader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib/QtGLWireCubeComponent.cpp) SET(VRIMU_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/VRWidgetLib/VRIMUWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/VRWidgetLib/VRWidget.cpp) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/VRWidgetLib) SET(CMAKE_AUTOMOC ON) IF(WIN32) SET(EXTRA_LIBS opengl32) ENDIF(WIN32) IF (UNIX AND (NOT APPLE)) SET(EXTRA_LIBS GL) ENDIF(UNIX AND (NOT APPLE)) IF(DEFINED QT5) FIND_PACKAGE(Qt5Widgets REQUIRED) FIND_PACKAGE(Qt5Core REQUIRED) FIND_PACKAGE(Qt5OpenGL REQUIRED) FIND_PACKAGE(Qt5Gui REQUIRED) qt5_add_resources(RCC_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/VRWidgetLib/VRWidgetLib.qrc) IF(WIN32) ADD_LIBRARY(RTIMULibGL STATIC ${LIBGL_SRCS} ${GL_SRCS} ${VRIMU_SRCS} ${GL_HEADERS} ${RCC_HEADERS}) ELSE(WIN32) ADD_LIBRARY(RTIMULibGL SHARED ${LIBGL_SRCS} ${GL_SRCS} ${VRIMU_SRCS} ${GL_HEADERS} ${RCC_HEADERS}) SET_PROPERTY(TARGET RTIMULibGL PROPERTY VERSION ${RTIMULIB_VERSION}) SET_PROPERTY(TARGET RTIMULibGL PROPERTY SOVERSION ${RTIMULIB_VERSION_MAJOR}) ENDIF(WIN32) TARGET_LINK_LIBRARIES(RTIMULibGL RTIMULib ${Qt5Gui_OPENGL_LIBRARIES} ${Qt5Core_QTMAIN_LIBRARIES} ${EXTRA_LIBS}) qt5_use_modules(RTIMULibGL Widgets Core OpenGL) INSTALL(TARGETS RTIMULibGL DESTINATION lib) ELSE(DEFINED QT5) FIND_PACKAGE(Qt4) SET(QT_USE_QTOPENGL TRUE) INCLUDE(${QT_USE_FILE}) ADD_DEFINITIONS(${QT_DEFINITIONS}) QT4_ADD_RESOURCES(RCC_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/VRWidgetLib/VRWidgetLib.qrc) IF(WIN32) ADD_LIBRARY(RTIMULibGL STATIC ${LIBGL_SRCS} ${GL_SRCS} ${VRIMU_SRCS} ${GL_HEADERS} ${RCC_HEADERS}) ELSE(WIN32) ADD_LIBRARY(RTIMULibGL SHARED ${LIBGL_SRCS} ${GL_SRCS} ${VRIMU_SRCS} ${GL_HEADERS} ${RCC_HEADERS}) SET_PROPERTY(TARGET RTIMULibGL PROPERTY VERSION ${RTIMULIB_VERSION}) SET_PROPERTY(TARGET RTIMULibGL PROPERTY SOVERSION ${RTIMULIB_VERSION_MAJOR}) ENDIF(WIN32) TARGET_LINK_LIBRARIES(RTIMULibGL RTIMULib ${QT_LIBRARIES} ${EXTRA_LIBS}) INSTALL(TARGETS RTIMULibGL DESTINATION lib) ENDIF(DEFINED QT5) rtimulib-7.2.1/Linux/RTIMULibGL/IMUView.cpp000066400000000000000000000076331254201074400202220ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "IMUView.h" #include #include #include #include IMUView::IMUView(QWidget *parent) : QGLWidget(QGLFormat(QGL::SampleBuffers), parent) { globalGLWidget = this; QGLFormat fmt = context()->format(); m_IMUWidget = NULL; globalTransforms.viewportFieldOfView = 45.0; globalTransforms.tanViewportFOV = tan(globalTransforms.viewportFieldOfView * QTGL_DEGREE_TO_RAD / 2.0); setAutoFillBackground(false); } void IMUView::closeEvent(QCloseEvent *) { if (m_IMUWidget) { delete m_IMUWidget; m_IMUWidget = NULL; } for (int index = 0; index < QTGLSHADER_COUNT; index++) delete globalShader[index]; } void IMUView::updateIMU(RTVector3& pose) { m_IMUWidget->setRotation( QTGL_RAD_TO_DEGREE * pose.x(), -QTGL_RAD_TO_DEGREE * pose.z(), QTGL_RAD_TO_DEGREE * pose.y()); updateGL(); } void IMUView::initializeGL() { initializeGLFunctions(); QtGLInit(this); QtGLAddLight(QVector4D(20.0f, 5.0f, 0.0f, 1.0f), QVector3D(0.2f, 0.2f, 0.2f), QVector3D(0.2f, 0.2f, 0.2f), QVector3D(0.4f, 0.4f, 0.4f)); QtGLAddLight(QVector4D(-20.0f, 5.0f, 0.0f, 1.0f), QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.2f, 0.2f, 0.2f), QVector3D(0.8f, 0.8f, 0.8f)); QtGLAddLight(QVector4D(0.0f, 0.0f, 0.0f, 1.0f), QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.2f, 0.2f, 0.2f), QVector3D(0.5f, 0.5f, 0.5f)); m_IMUWidget = new VRIMUWidget(globalGLWidget); if (m_IMUWidget) { m_IMUWidget->setRotation(QVector3D()); m_IMUWidget->VRWidgetInit(); } } void IMUView::paintGL() { QMutexLocker lock(&m_lock); qglClearColor(QColor(20, 40, 80)); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); m_IMUWidget->VRWidgetRender(); } void IMUView::resizeGL(int width, int height) { setupViewport(width, height); } void IMUView::setupViewport(int width, int height) { float w2 = width / 2; float tanFOV = globalTransforms.tanViewportFOV; globalTransforms.width = width; globalTransforms.height = height; globalTransforms.nearPlane = 1.0f; globalTransforms.farPlane = w2 / tanFOV; globalTransforms.halfWidth = width / 2.0f; globalTransforms.halfHeight = height / 2.0f; globalTransforms.viewportAspect = (float)width / (float)height; glViewport(0, 0, width, height); globalTransforms.projectionMatrix.setToIdentity(); globalTransforms.modelViewMatrix.setToIdentity(); globalTransforms.projectionMatrix.frustum(-tanFOV, +tanFOV, -tanFOV/globalTransforms.viewportAspect, tanFOV/globalTransforms.viewportAspect, globalTransforms.nearPlane, globalTransforms.farPlane); m_IMUWidget->setCenter(0, 0, IMUVIEW_DEPTH); } rtimulib-7.2.1/Linux/RTIMULibGL/IMUView.h000066400000000000000000000036531254201074400176650ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef IMUVIEW_H #define IMUVIEW_H #include #include #include #include "VRIMUWidget.h" #include "RTMath.h" #define IMUVIEW_DEPTH -15 // normal IMU position #define IMUVIEW_RESTART_INTERVAL 1000 // length of time in restart interval class VRIMUWidget; class IMUView : public QGLWidget, protected QGLFunctions { Q_OBJECT public: IMUView(QWidget *parent = NULL); void updateIMU(RTVector3& pose); protected: void initializeGL(); void resizeGL(int width, int height); void paintGL(); void closeEvent(QCloseEvent *); private: void setupViewport(int width, int height); VRIMUWidget *m_IMUWidget; QMutex m_lock; }; #endif // IMUVIEW_H rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/000077500000000000000000000000001254201074400174565ustar00rootroot00000000000000rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QTGLLib.sln000066400000000000000000000024301254201074400213710ustar00rootroot00000000000000 Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "QTGLLib", "QTGLLib.vcxproj", "{88277939-80B3-4156-9CD7-D2DE7A339BFB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 Release|Win32 = Release|Win32 StaticDebug|Win32 = StaticDebug|Win32 StaticRelease|Win32 = StaticRelease|Win32 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {88277939-80B3-4156-9CD7-D2DE7A339BFB}.Debug|Win32.ActiveCfg = Debug|Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB}.Debug|Win32.Build.0 = Debug|Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB}.Release|Win32.ActiveCfg = Release|Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB}.Release|Win32.Build.0 = Release|Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB}.StaticDebug|Win32.ActiveCfg = StaticDebug|Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB}.StaticDebug|Win32.Build.0 = StaticDebug|Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB}.StaticRelease|Win32.ActiveCfg = StaticRelease|Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB}.StaticRelease|Win32.Build.0 = StaticRelease|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QTGLLib.vcxproj000066400000000000000000000221451254201074400222750ustar00rootroot00000000000000 Debug Win32 Release Win32 StaticDebug Win32 StaticRelease Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB} Qt4VSv1.0 StaticLibrary StaticLibrary StaticLibrary StaticLibrary <_ProjectFileVersion>10.0.40219.1 $(SolutionDir)$(Platform)\$(Configuration)\ $(Configuration)\ $(SolutionDir)$(Platform)\$(Configuration)\ $(Configuration)\ $(ProjectName)d UNICODE;WIN32;QT_LARGEFILE_SUPPORT;QT_CORE_LIB;QT_OPENGL_LIB;QTGLLIB_LIB;%(PreprocessorDefinitions) .\GeneratedFiles;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtOpenGL;.;%(AdditionalIncludeDirectories) Disabled ProgramDatabase MultiThreadedDebugDLL false $(OutDir)\$(ProjectName).lib $(QTDIR)\lib;%(AdditionalLibraryDirectories) UNICODE;WIN32;QT_LARGEFILE_SUPPORT;QT_CORE_LIB;QT_OPENGL_LIB;QTGLLIB_LIB;%(PreprocessorDefinitions) .\GeneratedFiles;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtOpenGL;.;%(AdditionalIncludeDirectories) Disabled ProgramDatabase MultiThreadedDebugDLL false $(OutDir)\$(ProjectName)d.lib $(QTDIR)\lib;%(AdditionalLibraryDirectories) UNICODE;WIN32;QT_LARGEFILE_SUPPORT;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_OPENGL_LIB;QTGLLIB_LIB;%(PreprocessorDefinitions) .\GeneratedFiles;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtOpenGL;.;%(AdditionalIncludeDirectories) MultiThreadedDLL false $(OutDir)\$(ProjectName).lib $(QTDIR)\lib;%(AdditionalLibraryDirectories) UNICODE;WIN32;QT_LARGEFILE_SUPPORT;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_OPENGL_LIB;QTGLLIB_LIB;%(PreprocessorDefinitions) .\GeneratedFiles;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtOpenGL;.;%(AdditionalIncludeDirectories) MultiThreadedDLL false $(OutDir)\$(ProjectName).lib $(QTDIR)\lib;%(AdditionalLibraryDirectories) rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QTGLLib.vcxproj.filters000066400000000000000000000065631254201074400237520ustar00rootroot00000000000000 {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;cxx;c;def {93995380-89BD-4b04-88EB-625FBE52EBFB} h {99349809-55BA-4b9d-BF79-8FDBB0286EB3} ui {D9D6E242-F8AF-46E4-B9FD-80ECBC20BA3E} qrc;* false {71ED8ED8-ACB9-4CE9-BBE1-E00B30144E11} moc;h;cpp False Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGL.cpp000066400000000000000000000101341254201074400207700ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // This file contains OpenGL and Qt includes. This file should be included first // and the order of includes is very important (especially on Linux). #include QGLWidget *globalGLWidget; // the global GL widget TRANSFORM_MATRICES globalTransforms; // the various viewport transforms LIGHT_SOURCES globalLights; // the lights QtGLShader *globalShader[QTGLSHADER_COUNT]; // the shaders void QtGLInit(QGLWidget *widget) { globalLights.lightCount = 0; // no lights globalShader[QTGLSHADER_FLAT] = new QtGLFlatShader(widget); globalShader[QTGLSHADER_TEXTURE] = new QtGLTextureShader(widget); globalShader[QTGLSHADER_ADS] = new QtGLADSShader(widget); globalShader[QTGLSHADER_ADSTEXTURE] = new QtGLADSTextureShader(widget); } void QtGLAddLight(const QVector4D& position, const QVector3D& ambient, const QVector3D& diffuse, const QVector3D& specular) { int count; if ((count = globalLights.lightCount) == QTGL_MAX_LIGHTS) { qDebug() << "Too many lights"; return; } globalLights.ambient[count] = ambient; globalLights.diffuse[count] = diffuse; globalLights.specular[count] = specular; globalLights.position[count] = position; globalLights.lightCount++; } bool QtGLRayRectangleIntersection(QVector3D& intersection, const QVector3D& ray0, const QVector3D& ray1, const QVector3D& plane0, const QVector3D& plane1, const QVector3D& plane2, const QVector3D& plane3, bool checkInside) { QVector3D normal; QVector3D test; float dist0, dist1; // Compute normal to plane normal = QVector3D::crossProduct(plane1 - plane0, plane2 - plane0); normal.normalize(); // find distance from points defining line to plane dist0 = QVector3D::dotProduct(ray0 - plane0, normal); dist1 = QVector3D::dotProduct(ray1 - plane0, normal); if (qFuzzyCompare(dist0, dist1)) return false; // line and plane are parallel intersection = ray0 + (ray1 - ray0) * (-dist0 / (dist1 - dist0)); if (!checkInside) return true; // check if intersection point lies within the rectangle test = QVector3D::crossProduct(normal, plane1 - plane0); if (QVector3D::dotProduct(test, intersection - plane0) < 0.0f) return false; test = QVector3D::crossProduct(normal, plane2 - plane1); if (QVector3D::dotProduct(test, intersection - plane1) < 0.0f) return false; test = QVector3D::crossProduct(normal, plane3 - plane2); if (QVector3D::dotProduct(test, intersection - plane2) < 0.0f) return false; test = QVector3D::crossProduct(normal, plane0 - plane3); if (QVector3D::dotProduct(test, intersection - plane3) < 0.0f) return false; return true; } rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGL.h000066400000000000000000000100611254201074400204340ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef QTGL_H #define QTGL_H #include #define QTGL_PI 3.1415926535 #define QTGL_DEGREE_TO_RAD (QTGL_PI / 180.0) #define QTGL_RAD_TO_DEGREE (180.0 / QTGL_PI) #define QTGL_MAX_LIGHTS 4 typedef struct { QMatrix4x4 modelViewMatrix; // the model view matrix QMatrix4x4 projectionMatrix; // the projection matrix QMatrix4x4 modelViewProjectionMatrix; // the model view projection matrix QMatrix3x3 normalMatrix; // the normal matrix float viewportAspect; // the viewport aspect ratio float viewportFieldOfView; // angular field of view in x direction float tanViewportFOV; // tan of the field of view float width; // viewport width float height; // viewport height float halfWidth; // half viewport width float halfHeight; // half viewport height float nearPlane; // where the near plane is float farPlane; // viewport distance } TRANSFORM_MATRICES; typedef struct { int lightCount; QVector4D position[QTGL_MAX_LIGHTS]; QVector3D ambient[QTGL_MAX_LIGHTS]; QVector3D diffuse[QTGL_MAX_LIGHTS]; QVector3D specular[QTGL_MAX_LIGHTS]; } LIGHT_SOURCES; typedef struct { QVector3D ambientReflectivity; QVector3D diffuseReflectivity; QVector3D specularReflectivity; float shininess; } COMPONENT_MATERIAL; #include "QtGLShader.h" #include "QtGLTextureShader.h" #include "QtGLFlatShader.h" #include "QtGLADSShader.h" #include "QtGLADSTextureShader.h" #include "QtGLComponent.h" #include "QtGLPlaneComponent.h" #include "QtGLCylinderComponent.h" #include "QtGLDiskComponent.h" #include "QtGLWireCubeComponent.h" #include "QtGLSphereComponent.h" extern QGLWidget *globalGLWidget; // the global GL widget extern TRANSFORM_MATRICES globalTransforms; // the various viewport transforms extern LIGHT_SOURCES globalLights; // the lights extern QtGLShader *globalShader[QTGLSHADER_COUNT]; // the shaders void QtGLInit(QGLWidget *widget); void QtGLAddLight(const QVector4D& position, const QVector3D& ambient, const QVector3D& diffuse, const QVector3D& specular); bool QtGLRayRectangleIntersection(QVector3D& intersection, const QVector3D& ray0, const QVector3D& ray1, const QVector3D& plane0, const QVector3D& plane1, const QVector3D& plane2, const QVector3D& plane3, bool checkInside); #endif // QTGL_H rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGLADSShader.cpp000066400000000000000000000115171254201074400224550ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "QtGL.h" QtGLADSShader::QtGLADSShader(QObject *parent) : QtGLShader(parent) { #define PROGRAM_VERTEX_ATTRIBUTE 0 #define PROGRAM_NORMAL_ATTRIBUTE 1 m_type = QTGLSHADER_ADS; m_vshader = new QGLShader(QGLShader::Vertex, parent); const char *vsrc = "attribute highp vec3 vertex;\n" "attribute mediump vec3 normal;\n" "varying mediump vec3 lightI;\n" "uniform mediump mat4 MV;\n" "uniform mediump mat4 MVP\n;" "uniform mediump mat3 normalMatrix;\n" "uniform mediump int lightCount;\n" "uniform mediump vec4 posL[4];\n" "uniform mediump vec3 ambientL[4];\n" "uniform mediump vec3 diffuseL[4];\n" "uniform mediump vec3 specularL[4];\n" "uniform mediump vec3 ambientM;\n" "uniform mediump vec3 diffuseM;\n" "uniform mediump vec3 specularM;\n" "uniform mediump float shininess;\n" "void main(void)\n" "{\n" " gl_Position = MVP * vec4(vertex, 1.0);\n" " vec3 tnorm = normalize(normalMatrix * normal);\n" " vec4 eyeCoords = MV * vec4(vertex, 1.0);\n" " vec3 v = normalize(-eyeCoords.xyz);\n" " lightI = vec3(0.0, 0.0, 0.0);\n" " for (int i = 0; i < lightCount; i++) {\n" " vec3 s = normalize(vec3(posL[i] - eyeCoords));\n" " float sDotN = max(dot(s, tnorm), 0.0);\n" " vec3 r = reflect(-s, tnorm);\n" " vec3 ambient = ambientL[i] * ambientM;\n" " vec3 diffuse = diffuseL[i] * diffuseM * sDotN;\n" " vec3 spec = vec3(0.0);\n" " if (sDotN > 0.0) spec = specularL[i] * specularM * pow(max(dot(r,v), 0.0), shininess);\n" " lightI += ambient + diffuse + spec;\n" " }\n" "}\n"; if (!m_vshader->compileSourceCode(vsrc)) qDebug() << "Failed to compile ADS shader"; m_fshader = new QGLShader(QGLShader::Fragment, parent); const char *fsrc = "varying mediump vec3 lightI;\n" "void main(void)\n" "{\n" " gl_FragColor = vec4(lightI, 1.0);\n" "}\n"; if (!m_fshader->compileSourceCode(fsrc)) qDebug() << "Failed to compile ADS fragment shader"; addShader(m_vshader); addShader(m_fshader); bindAttributeLocation("vertex", PROGRAM_VERTEX_ATTRIBUTE); bindAttributeLocation("normal", PROGRAM_NORMAL_ATTRIBUTE); link(); } QtGLADSShader::~QtGLADSShader() { } void QtGLADSShader::load(const QVector3D *vertices, const QVector3D *normals, const COMPONENT_MATERIAL& material) { bind(); setUniformValue("MV", globalTransforms.modelViewMatrix); setUniformValue("MVP", globalTransforms.modelViewProjectionMatrix); setUniformValue("normalMatrix", globalTransforms.normalMatrix); setUniformValue("lightCount", globalLights.lightCount); setUniformValueArray("posL", globalLights.position, globalLights.lightCount); setUniformValueArray("ambientL", globalLights.ambient, globalLights.lightCount); setUniformValueArray("diffuseL", globalLights.diffuse, globalLights.lightCount); setUniformValueArray("specularL", globalLights.specular, globalLights.lightCount); setUniformValue("ambientM", material.ambientReflectivity); setUniformValue("diffuseM", material.diffuseReflectivity); setUniformValue("specularM", material.specularReflectivity); setUniformValue("shininess", material.shininess); enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); enableAttributeArray(PROGRAM_NORMAL_ATTRIBUTE); setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, vertices); setAttributeArray(PROGRAM_NORMAL_ATTRIBUTE, normals); } rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGLADSShader.h000066400000000000000000000030111254201074400221100ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef QTGLADSSHADER_H #define QTGLADSSHADER_H class QtGLADSShader : public QtGLShader { public: QtGLADSShader(QObject *parent); ~QtGLADSShader(); void load(const QVector3D *vertices, const QVector3D *normals, const COMPONENT_MATERIAL& material); private: }; #endif // QTGLADSSHADER_H rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGLADSTextureShader.cpp000066400000000000000000000126151254201074400240360ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "QtGL.h" QtGLADSTextureShader::QtGLADSTextureShader(QObject *parent) : QtGLShader(parent) { #define PROGRAM_VERTEX_ATTRIBUTE 0 #define PROGRAM_NORMAL_ATTRIBUTE 1 #define PROGRAM_TEXTURE_ATTRIBUTE 2 m_type = QTGLSHADER_ADSTEXTURE; m_vshader = new QGLShader(QGLShader::Vertex, parent); const char *vsrc = "attribute highp vec3 vertex;\n" "attribute mediump vec3 normal;\n" "attribute mediump vec2 texCoord;\n" "varying mediump vec2 texc;\n" "varying mediump vec3 lightI;\n" "uniform mediump mat4 MV;\n" "uniform mediump mat4 MVP\n;" "uniform mediump mat3 normalMatrix;\n" "uniform mediump int lightCount;\n" "uniform mediump vec4 posL[4];\n" "uniform mediump vec3 ambientL[4];\n" "uniform mediump vec3 diffuseL[4];\n" "uniform mediump vec3 specularL[4];\n" "uniform mediump vec3 ambientM;\n" "uniform mediump vec3 diffuseM;\n" "uniform mediump vec3 specularM;\n" "uniform mediump float shininess;\n" "void main(void)\n" "{\n" " gl_Position = MVP * vec4(vertex, 1.0);\n" " texc = texCoord;\n" " vec3 tnorm = normalize(normalMatrix * normal);\n" " vec4 eyeCoords = MV * vec4(vertex, 1.0);\n" " vec3 v = normalize(-eyeCoords.xyz);\n" " lightI = vec3(0.0, 0.0, 0.0);\n" " for (int i = 0; i < lightCount; i++) {\n" " vec3 s = normalize(vec3(posL[i] - eyeCoords));\n" " float sDotN = max(dot(s, tnorm), 0.0);\n" " vec3 r = reflect(-s, tnorm);\n" " vec3 ambient = ambientL[i] * ambientM;\n" " vec3 diffuse = diffuseL[i] * diffuseM * sDotN;\n" " vec3 spec = vec3(0.0);\n" " if (sDotN > 0.0) spec = specularL[i] * specularM * pow(max(dot(r,v), 0.0), shininess);\n" " lightI += ambient + diffuse + spec;\n" " }\n" "}\n"; if (!m_vshader->compileSourceCode(vsrc)) qDebug() << "Failed to compile ADS shader"; m_fshader = new QGLShader(QGLShader::Fragment, parent); const char *fsrc = "uniform sampler2D texture;\n" "varying mediump vec2 texc;\n" "varying mediump vec3 lightI;\n" "void main(void)\n" "{\n" " gl_FragColor = texture2D(texture, texc.st) * vec4(lightI, 1.0);\n" "}\n"; if (!m_fshader->compileSourceCode(fsrc)) qDebug() << "Failed to compile ADS fragment shader"; addShader(m_vshader); addShader(m_fshader); bindAttributeLocation("vertex", PROGRAM_VERTEX_ATTRIBUTE); bindAttributeLocation("normal", PROGRAM_NORMAL_ATTRIBUTE); bindAttributeLocation("texCoord", PROGRAM_TEXTURE_ATTRIBUTE); link(); setUniformValue("texture", 0); } QtGLADSTextureShader::~QtGLADSTextureShader() { } void QtGLADSTextureShader::load(const QVector3D *vertices, const QVector3D *normals, const QVector2D* textureCoords, const COMPONENT_MATERIAL& material) { bind(); setUniformValue("MV", globalTransforms.modelViewMatrix); setUniformValue("MVP", globalTransforms.modelViewProjectionMatrix); setUniformValue("normalMatrix", globalTransforms.normalMatrix); setUniformValue("lightCount", globalLights.lightCount); setUniformValueArray("posL", globalLights.position, globalLights.lightCount); setUniformValueArray("ambientL", globalLights.ambient, globalLights.lightCount); setUniformValueArray("diffuseL", globalLights.diffuse, globalLights.lightCount); setUniformValueArray("specularL", globalLights.specular, globalLights.lightCount); setUniformValue("ambientM", material.ambientReflectivity); setUniformValue("diffuseM", material.diffuseReflectivity); setUniformValue("specularM", material.specularReflectivity); setUniformValue("shininess", material.shininess); enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); enableAttributeArray(PROGRAM_NORMAL_ATTRIBUTE); enableAttributeArray(PROGRAM_TEXTURE_ATTRIBUTE); setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, vertices); setAttributeArray(PROGRAM_NORMAL_ATTRIBUTE, normals); setAttributeArray(PROGRAM_TEXTURE_ATTRIBUTE, textureCoords); } rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGLADSTextureShader.h000066400000000000000000000031371254201074400235020ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef QTGLADSTEXTURESHADER_H #define QTGLADSTEXTURESHADER_H class QtGLADSTextureShader : public QtGLShader { public: QtGLADSTextureShader(QObject *parent); ~QtGLADSTextureShader(); void load(const QVector3D *vertices, const QVector3D *normals, const QVector2D *textureCoords, const COMPONENT_MATERIAL& material); private: }; #endif // QTGLADSTEXTURESHADER_H rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGLComponent.cpp000066400000000000000000000232511254201074400226570ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // Based on code from the OpenGL SuperBible: /* * OpenGL SuperBible * Copyright (c) 2007-2009, Richard S. Wright Jr. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Richard S. Wright Jr. nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "QtGL.h" QtGLComponent::QtGLComponent() { m_shader = NULL; m_texture = -1; m_indexBuffer = NULL; m_normalBuffer = NULL; m_useIndexBuffer = false; m_useNormalBuffer = false; } QtGLComponent::~QtGLComponent() { reset(); } void QtGLComponent::reset() { if (m_texture != -1) globalGLWidget->deleteTexture(m_texture); if (m_indexBuffer != NULL) delete m_indexBuffer; m_indexBuffer = NULL; if (m_normalBuffer != NULL) delete m_normalBuffer; m_normalBuffer = NULL; m_vertices.clear(); m_normals.clear(); m_textureCoords.clear(); m_indices.clear(); m_boundMinus = QVector3D(10000.0f, 10000.0f, 10000.0f); m_boundPlus = QVector3D(-10000.0f, -10000.0f, -10000.0f); } void QtGLComponent::addVertex(const QVector3D& vertex) { m_vertices.append(vertex); updateBoundingBox(vertex); } void QtGLComponent::addNormal(const QVector3D& normal) { m_normals.append(normal); } void QtGLComponent::addTextureCoord(const QVector2D& textureCoord) { m_textureCoords.append(textureCoord); } void QtGLComponent::addIndex(GLushort index) { m_indices.append(index); } void QtGLComponent::setShader(QtGLShader *shader) { m_shader = shader; } void QtGLComponent::setTexture(GLuint texture) { if (m_texture != -1) globalGLWidget->deleteTexture(m_texture); m_texture = texture; } void QtGLComponent::setTexture(const QImage& image) { int width, height; int newWidth, newHeight; width = image.width(); height = image.height(); newWidth = nearestPOT(width); // find nearest power of two newHeight = nearestPOT(height); if ((width == newWidth) && (height = newHeight)) { setTexture(globalGLWidget->bindTexture(image)); } else { QImage newImage = image.scaled(newWidth, newHeight, Qt::IgnoreAspectRatio); setTexture(globalGLWidget->bindTexture(newImage)); qDebug() << "New size " << newImage.width() << " " << newImage.height(); } } void QtGLComponent::setMaterial(const COMPONENT_MATERIAL& material) { m_material = material; } void QtGLComponent::setMaterial(const QVector3D& ambient, const QVector3D& diffuse, const QVector3D& specular, float shininess) { m_material.ambientReflectivity = ambient; m_material.diffuseReflectivity = diffuse; m_material.specularReflectivity = specular; m_material.shininess = shininess; } int QtGLComponent::nearestPOT(int val) { if (val <= 3) return 2; if (val <= 6) return 4; if (val <= 12) return 8; if (val <= 24) return 16; if (val <= 48) return 32; if (val <= 96) return 64; if (val <= 192) return 128; if (val <= 384) return 256; if (val <= 768) return 512; if (val <= 1536) return 1024; return 2048; } void QtGLComponent::useIndexBuffer(bool use) { if (m_indexBuffer != NULL) delete m_indexBuffer; m_indexBuffer = NULL; m_useIndexBuffer = use; if (!use) return; m_indexBuffer = new QGLBuffer(QGLBuffer::IndexBuffer); m_indexBuffer->create(); m_indexBuffer->bind(); m_indexBuffer->setUsagePattern(QGLBuffer::StaticDraw); m_indexBuffer->allocate(m_indices.constData(), m_indices.size() * sizeof(GLushort)); m_indexBuffer->release(); } void QtGLComponent::useNormalBuffer(bool use) { if (m_normalBuffer != NULL) delete m_normalBuffer; m_normalBuffer = NULL; m_useNormalBuffer = use; if (!use) return; m_normalBuffer = new QGLBuffer(QGLBuffer::VertexBuffer); m_normalBuffer->create(); m_normalBuffer->bind(); m_normalBuffer->setUsagePattern(QGLBuffer::StaticDraw); m_normalBuffer->allocate(m_normals.constData(), m_normals.size() * sizeof(QVector3D)); m_normalBuffer->release(); } void QtGLComponent::addTriangle(QVector3D *verts, QVector3D *norms, QVector2D *texCoords) { // Search for match - triangle consists of three verts for(GLuint iVertex = 0; iVertex < 3; iVertex++){ GLuint iMatch = 0; for(iMatch = 0; iMatch < (GLuint)m_vertices.size(); iMatch++) { if (!qFuzzyCompare(m_vertices.at(iMatch), verts[iVertex])) continue; if (!qFuzzyCompare(m_normals.at(iMatch), norms[iVertex])) continue; if (!qFuzzyCompare(m_textureCoords.at(iMatch), texCoords[iVertex])) continue; // Then add the index only addIndex(iMatch); break; } // No match for this vertex, add to end of list if((int)iMatch == m_vertices.size()) { addIndex(iMatch); addVertex(verts[iVertex]); addNormal(norms[iVertex]); addTextureCoord(texCoords[iVertex]); } } } void QtGLComponent::setColor(const QColor& color) { m_color = color; } void QtGLComponent::draw(GLenum drawMode) { if (m_shader == NULL) return; switch (m_shader->getType()) { case QTGLSHADER_FLAT: m_shader->load(m_vertices.constData(), m_color); break; case QTGLSHADER_TEXTURE: m_shader->load(m_vertices.constData(), m_textureCoords.constData()); break; case QTGLSHADER_ADS: m_shader->load(m_vertices.constData(), m_normals.constData(), m_material); break; case QTGLSHADER_ADSTEXTURE: m_shader->load(m_vertices.constData(), m_normals.constData(), m_textureCoords.constData(), m_material); break; default: qDebug() << "Invalidate shader type " << m_shader->getType(); return; } if (m_texture != -1) glBindTexture(GL_TEXTURE_2D, m_texture); if (m_useIndexBuffer) m_indexBuffer->bind(); if (m_useNormalBuffer) m_normalBuffer->bind(); switch (drawMode) { case GL_TRIANGLES: glDrawElements(GL_TRIANGLES, m_indices.size(), GL_UNSIGNED_SHORT, 0); break; case GL_TRIANGLE_FAN: glDrawArrays(GL_TRIANGLE_FAN, 0, m_vertices.size()); break; case GL_LINES: glDrawArrays(GL_LINES, 0, m_vertices.size()); break; default: qDebug() << "Invalid draw mode " << drawMode; break; } m_shader->release(); if (m_useIndexBuffer) m_indexBuffer->release(); if (m_useNormalBuffer) m_normalBuffer->release(); } void QtGLComponent::updateBoundingBox(const QVector3D& vert) { if (vert.x() < m_boundMinus.x()) m_boundMinus.setX(vert.x()); if (vert.y() < m_boundMinus.y()) m_boundMinus.setY(vert.y()); if (vert.z() < m_boundMinus.z()) m_boundMinus.setZ(vert.z()); if (vert.x() > m_boundPlus.x()) m_boundPlus.setX(vert.x()); if (vert.y() > m_boundPlus.y()) m_boundPlus.setY(vert.y()); if (vert.z() > m_boundPlus.z()) m_boundPlus.setZ(vert.z()); } rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGLComponent.h000066400000000000000000000120111254201074400223140ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // Based on code from the OpenGL SuperBible: /* * OpenGL SuperBible * Copyright (c) 2007-2009, Richard S. Wright Jr. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Richard S. Wright Jr. nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef QTGLCOMPONENT_H #define QTGLCOMPONENT_H class QtGLComponent { public: QtGLComponent(); ~QtGLComponent(); virtual void setShader(QtGLShader *shader); virtual void setTexture(GLuint texture); virtual void setTexture(const QImage& image); virtual void setMaterial(const COMPONENT_MATERIAL& material); virtual void setMaterial(const QVector3D& ambient, const QVector3D& diffuse, const QVector3D& specular, float shininess); virtual void draw(GLenum drawMode); virtual void setColor(const QColor& color); // sets color for flat shader inline QVector3D& getBoundMinus() { return m_boundMinus; }; inline QVector3D& getBoundPlus() { return m_boundPlus; }; protected: void reset(); // clears everything and starts again int nearestPOT(int val); // returns nearest power of 2 void addTriangle(QVector3D *verts, QVector3D *norms, QVector2D *texCoords); // adds a triangle void addVertex(const QVector3D& vertex); // adds a vertex to the component void addNormal(const QVector3D& normal); // adds a normal to the component void addTextureCoord(const QVector2D& textureCoord); // adds a texture coord to the component void addIndex(GLushort index); // adds an index to the component void useIndexBuffer(bool use); // says that index buffer is to be used in draw void useNormalBuffer(bool use); // says that normal buffer is to be used in draw void updateBoundingBox(const QVector3D& vert); // update the component bounding box private: QtGLShader *m_shader; int m_texture; QColor m_color; COMPONENT_MATERIAL m_material; QVector m_vertices; QVector m_normals; QVector m_textureCoords; QVector m_indices; QGLBuffer *m_indexBuffer; QGLBuffer *m_normalBuffer; bool m_useIndexBuffer; bool m_useNormalBuffer; QVector3D m_boundMinus; // lowest in each coord for box QVector3D m_boundPlus; // highest in each coord for box }; #endif // QTGLCOMPONENT_H rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGLCylinderComponent.cpp000066400000000000000000000175531254201074400243610ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // Based on code from the OpenGL SuperBible: /* * OpenGL SuperBible * Copyright (c) 2007-2009, Richard S. Wright Jr. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Richard S. Wright Jr. nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "QtGL.h" QtGLCylinderComponent::QtGLCylinderComponent() { } QtGLCylinderComponent::~QtGLCylinderComponent() { } void QtGLCylinderComponent::generate(GLfloat baseRadius, GLfloat topRadius, GLfloat length, GLint numSlices, GLint numStacks) { float radiusStep = (topRadius - baseRadius) / float(numStacks); GLfloat stepSizeSlice = (3.1415926536f * 2.0f) / float(numSlices); QVector3D vertex[4]; QVector3D normal[4]; QVector2D texture[4]; reset(); GLfloat ds = 1.0f / float(numSlices); GLfloat dt = 1.0f / float(numStacks); GLfloat s; GLfloat t; int vertA = 0; int vertB = 1; int vertC = 2; int vertD = 3; for (int i = 0; i < numStacks; i++) { if(i == 0) t = 1.0f; else t = 1.0f - float(i) * dt; float tNext; if(i == (numStacks - 1)) tNext = 0.0f; else tNext = 1.0f - float(i+1) * dt; float currentRadius = baseRadius + (radiusStep * float(i)); float nextRadius = baseRadius + (radiusStep * float(i+1)); float theta; float thetaNext; float currentZ = float(i) * (length / float(numStacks)); float nextZ = float(i+1) * (length / float(numStacks)); float zNormal = 0.0f; if(!qFuzzyCompare(baseRadius - topRadius, 0.0f)) { // Rise over run... zNormal = (baseRadius - topRadius); } for (int j = 0; j < numSlices; j++) { if(j == 0) s = 1.0f; else s = 1.0f - float(j) * ds; float sNext; if(j == (numSlices -1)) sNext = 0.0f; else sNext = 1.0f - float(j+1) * ds; theta = stepSizeSlice * float(j); if(j == (numSlices - 1)) thetaNext = 0.0f; else thetaNext = stepSizeSlice * (float(j+1)); // Inner First vertex[vertB].setX(cos(theta) * currentRadius); // X vertex[vertB].setY(sin(theta) * currentRadius); // Y vertex[vertB].setZ(currentZ); // Z normal[vertB].setX(vertex[vertB].x()); // Surface Normal, same for everybody normal[vertB].setY(vertex[vertB].y()); normal[vertB].setZ(zNormal); normal[vertB].normalize(); texture[vertB].setX(s); // Texture Coordinates, I have no idea... texture[vertB].setY(t); // Outer First vertex[vertA].setX(cos(theta) * nextRadius); // X vertex[vertA].setY(sin(theta) * nextRadius); // Y vertex[vertA].setZ(nextZ); // Z if(!qFuzzyCompare(nextRadius, 0.0f)) { normal[vertA].setX(vertex[vertA].x()); // Surface Normal, same for everybody normal[vertA].setY(vertex[vertA].y()); // For cones, tip is tricky normal[vertA].setZ(zNormal); normal[vertA].normalize(); } else { normal[vertA] = normal[vertB]; } texture[vertA].setX(s); // Texture Coordinates, I have no idea... texture[vertA].setY(tNext); // Inner second vertex[vertD].setX(cos(thetaNext) * currentRadius); // X vertex[vertD].setY(sin(thetaNext) * currentRadius); // Y vertex[vertD].setZ(currentZ); // Z normal[vertD].setX(vertex[vertD].x()); // Surface Normal, same for everybody normal[vertD].setY(vertex[vertD].y()); normal[vertD].setZ(zNormal); normal[vertD].normalize(); texture[vertD].setX(sNext); // Texture Coordinates, I have no idea... texture[vertD].setY(t); // Outer second vertex[vertC].setX(cos(thetaNext) * nextRadius); // X vertex[vertC].setY(sin(thetaNext) * nextRadius); // Y vertex[vertC].setZ(nextZ); // Z if(!qFuzzyCompare(nextRadius, 0.0f)) { normal[vertC].setX(vertex[vertC].x()); // Surface Normal, same for everybody normal[vertC].setY(vertex[vertC].y()); normal[vertC].setZ(zNormal); normal[vertC].normalize(); } else { normal[vertC] = normal[vertD]; } texture[vertC].setX(sNext); // Texture Coordinates, I have no idea... texture[vertC].setY(tNext); addTriangle(vertex, normal, texture); // Rearrange for next triangle vertex[vertA] = vertex[vertB]; normal[vertA] = normal[vertB]; texture[vertA] = texture[vertB]; vertex[vertB] = vertex[vertD]; normal[vertB] = normal[vertD]; texture[vertB] = texture[vertD]; addTriangle(vertex, normal, texture); } } useIndexBuffer(true); useNormalBuffer(true); } void QtGLCylinderComponent::draw() { QtGLComponent::draw(GL_TRIANGLES); } rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGLCylinderComponent.h000066400000000000000000000061241254201074400240160ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // Based on code from the OpenGL SuperBible: /* * OpenGL SuperBible * Copyright (c) 2007-2009, Richard S. Wright Jr. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Richard S. Wright Jr. nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef QTGLCYLINDERCOMPONENT #define QTGLCYLINDERCOMPONENT class QtGLCylinderComponent : public QtGLComponent { public: QtGLCylinderComponent(); ~QtGLCylinderComponent(); void generate(GLfloat baseRadius, GLfloat topRadius, GLfloat length, GLint numSlices, GLint numStacks); void draw(); }; #endif // QTGLCYLINDERCOMPONENT_H rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGLDiskComponent.cpp000066400000000000000000000147471254201074400235040ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // Based on code from the OpenGL SuperBible: /* * OpenGL SuperBible * Copyright (c) 2007-2009, Richard S. Wright Jr. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Richard S. Wright Jr. nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "QtGL.h" QtGLDiskComponent::QtGLDiskComponent() { } QtGLDiskComponent::~QtGLDiskComponent() { } void QtGLDiskComponent::generate(GLfloat innerRadius, GLfloat outerRadius, GLint numSlices, GLint numStacks) { // How much to step out each stack GLfloat stepSizeRadial = outerRadius - innerRadius; if(stepSizeRadial < 0.0f) // Dum dum... stepSizeRadial *= -1.0f; stepSizeRadial /= float(numStacks); GLfloat stepSizeSlice = (3.1415926536f * 2.0f) / float(numSlices); reset(); QVector3D vertex[4]; QVector3D normal[4]; QVector2D texture[4]; float radialScale = 1.0f / outerRadius; for(GLint i = 0; i < numStacks; i++) { float theta; float thetaNext; for (GLint j = 0; j < numSlices; j++) { float inner = innerRadius + (float(i)) * stepSizeRadial; float outer = innerRadius + (float(i+1)) * stepSizeRadial; theta = stepSizeSlice * float(j); if(j == (numSlices - 1)) thetaNext = 0.0f; else thetaNext = stepSizeSlice * (float(j+1)); // Inner First vertex[0].setX(cos(theta) * inner); // X vertex[0].setY(sin(theta) * inner); // Y vertex[0].setZ(0.0f); // Z normal[0].setX(0.0f); // Surface Normal, same for everybody normal[0].setY(0.0f); normal[0].setZ(1.0f); texture[0].setX(((vertex[0].x() * radialScale) + 1.0f) * 0.5f); texture[0].setY(((vertex[0].y() * radialScale) + 1.0f) * 0.5f); // Outer First vertex[1].setX(cos(theta) * outer); // X vertex[1].setY(sin(theta) * outer); // Y vertex[1].setZ(0.0f); // Z normal[1].setX(0.0f); // Surface Normal, same for everybody normal[1].setY(0.0f); normal[1].setZ(1.0f); texture[1].setX(((vertex[1].x() * radialScale) + 1.0f) * 0.5f); texture[1].setY(((vertex[1].y() * radialScale) + 1.0f) * 0.5f); // Inner Second vertex[2].setX(cos(thetaNext) * inner); // X vertex[2].setY(sin(thetaNext) * inner); // Y vertex[2].setZ(0.0f); // Z normal[2].setX(0.0f); // Surface Normal, same for everybody normal[2].setY(0.0f); normal[2].setZ(1.0f); texture[2].setX(((vertex[2].x() * radialScale) + 1.0f) * 0.5f); texture[2].setY(((vertex[2].y() * radialScale) + 1.0f) * 0.5f); // Outer Second vertex[3].setX(cos(thetaNext) * outer); // X vertex[3].setY(sin(thetaNext) * outer); // Y vertex[3].setZ(0.0f); // Z normal[3].setX(0.0f); // Surface Normal, same for everybody normal[3].setY(0.0f); normal[3].setZ(1.0f); texture[3].setX(((vertex[3].x() * radialScale) + 1.0f) * 0.5f); texture[3].setY(((vertex[3].y() * radialScale) + 1.0f) * 0.5f); addTriangle(vertex, normal, texture); // Rearrange for next triangle vertex[0] = vertex[1]; normal[0] = normal[1]; texture[0] = texture[1]; vertex[1] = vertex[3]; normal[1] = normal[3]; texture[1] = texture[3]; addTriangle(vertex, normal, texture); } } useIndexBuffer(true); useNormalBuffer(true); } void QtGLDiskComponent::draw() { QtGLComponent::draw(GL_TRIANGLES); } rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGLDiskComponent.h000066400000000000000000000060571254201074400231440ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // Based on code from the OpenGL SuperBible: /* * OpenGL SuperBible * Copyright (c) 2007-2009, Richard S. Wright Jr. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Richard S. Wright Jr. nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef QTGLDISKCOMPONENT #define QTGLDISKCOMPONENT class QtGLDiskComponent : public QtGLComponent { public: QtGLDiskComponent(); ~QtGLDiskComponent(); void generate(GLfloat innerRadius, GLfloat outerRadius, GLint numSlices, GLint numStacks); void draw(); }; #endif // QTGLDISKCOMPONENT_H rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGLFlatShader.cpp000066400000000000000000000047111254201074400227320ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "QtGL.h" QtGLFlatShader::QtGLFlatShader(QObject *parent) : QtGLShader(parent) { #define PROGRAM_VERTEX_ATTRIBUTE 0 m_type = QTGLSHADER_FLAT; m_vshader = new QGLShader(QGLShader::Vertex, parent); const char *vsrc = "attribute highp vec4 vertex;\n" "uniform mediump mat4 matrix;\n" "void main(void)\n" "{\n" " gl_Position = matrix * vertex;\n" "}\n"; m_vshader->compileSourceCode(vsrc); m_fshader = new QGLShader(QGLShader::Fragment, parent); const char *fsrc = "uniform mediump vec4 color;\n" "void main(void)\n" "{\n" " gl_FragColor = color;\n" "}\n"; m_fshader->compileSourceCode(fsrc); addShader(m_vshader); addShader(m_fshader); bindAttributeLocation("vertex", PROGRAM_VERTEX_ATTRIBUTE); link(); setUniformValue("color", QVector4D(1.0f, 0, 0, 1.0f)); } QtGLFlatShader::~QtGLFlatShader() { } void QtGLFlatShader::load(const QVector3D *vertices, const QColor& color) { bind(); setUniformValue("matrix", globalTransforms.modelViewProjectionMatrix); setUniformValue("color", color); enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, vertices); } rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGLFlatShader.h000066400000000000000000000027461254201074400224050ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef QTGLFLATSHADER_H #define QTGLFLATSHADER_H class QtGLFlatShader : public QtGLShader { public: QtGLFlatShader(QObject *parent); ~QtGLFlatShader(); void load(const QVector3D *vertices, const QColor& color); private: }; #endif // QTGLFLATSHADER_H rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGLLib.pri000066400000000000000000000036731254201074400214410ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014, richards-tech #// #// Permission 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. INCLUDEPATH += $$PWD DEPENDPATH += $$PWD HEADERS += $$PWD/QtGL.h \ $$PWD/QtGLShader.h \ $$PWD/QtGLTextureShader.h \ $$PWD/QtGLFlatShader.h \ $$PWD/QtGLADSShader.h \ $$PWD/QtGLADSTextureShader.h \ $$PWD/QtGLComponent.h \ $$PWD/QtGLPlaneComponent.h \ $$PWD/QtGLCylinderComponent.h \ $$PWD/QtGLWireCubeComponent.h \ $$PWD/QtGLDiskComponent.h SOURCES += $$PWD/QtGL.cpp \ $$PWD/QtGLTextureShader.cpp \ $$PWD/QtGLFlatShader.cpp \ $$PWD/QtGLADSShader.cpp \ $$PWD/QtGLADSTextureShader.cpp \ $$PWD/QtGLComponent.cpp \ $$PWD/QtGLPlaneComponent.cpp \ $$PWD/QtGLCylinderComponent.cpp \ $$PWD/QtGLWireCubeComponent.cpp \ $$PWD/QtGLDiskComponent.cpp rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGLPlaneComponent.cpp000066400000000000000000000034521254201074400236400ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "QtGL.h" QtGLPlaneComponent::QtGLPlaneComponent() { } QtGLPlaneComponent::~QtGLPlaneComponent() { } void QtGLPlaneComponent::generate(float width, float height) { static const float coords[4][2] = {{+0.5f, +0.5f}, {-0.5f, +0.5f}, {-0.5f, -0.5f}, {+0.5f, -0.5f}}; reset(); for (int vert = 0; vert < 4; vert++) { addTextureCoord(QVector2D(vert == 0 || vert == 3, vert == 0 || vert == 1)); addVertex(QVector3D(width * coords[vert][0], height * coords[vert][1], 0)); } } void QtGLPlaneComponent::draw() { QtGLComponent::draw(GL_TRIANGLE_FAN); } rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGLPlaneComponent.h000066400000000000000000000032031254201074400232770ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // // This file contains OpenGL and Qt includes. This file should be included first // and the order of includes is very important (especially on Linux). #ifndef QTGLPLANECOMPONENT_H #define QTGLPLANECOMPONENT_H class QtGLPlaneComponent : public QtGLComponent { public: QtGLPlaneComponent(); ~QtGLPlaneComponent(); void generate(float width, float height); void draw(); }; #endif // QTGLPLANECOMPONENT_H rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGLShader.h000066400000000000000000000053441254201074400215730ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // This file contains OpenGL and Qt includes. This file should be included first // and the order of includes is very important (especially on Linux). #ifndef QTGLSHADER_H #define QTGLSHADER_H typedef enum { QTGLSHADER_FLAT, QTGLSHADER_TEXTURE, QTGLSHADER_ADS, QTGLSHADER_ADSTEXTURE, QTGLSHADER_COUNT } QTGLSHADER_TYPE; class QtGLShader : public QGLShaderProgram { public: QtGLShader(QObject *parent) : QGLShaderProgram(parent) {}; virtual ~QtGLShader() { removeAllShaders(); }; inline QTGLSHADER_TYPE getType() {return m_type;}; // virtual void load(const QVector3D *vertices, const QVector2D *textureCoords); virtual void load(const QVector3D *, const QVector2D *) { qDebug() << "No shader load";}; // virtual void load(const QVector3D *vertices, const QColor& color); virtual void load(const QVector3D *, const QColor&) {qDebug() << "No shader load";}; // virtual void load(const QVector3D *vertices, const QVector3D *normals, const COMPONENT_MATERIAL& material); virtual void load(const QVector3D *, const QVector3D *, const COMPONENT_MATERIAL& ) {qDebug() << "No shader load";}; // virtual void load(const QVector3D *vertices, const QVector3D *normals, // const QVector2D *textureCoords, const COMPONENT_MATERIAL& material); virtual void load(const QVector3D *, const QVector3D *, const QVector2D*, const COMPONENT_MATERIAL& ) {qDebug() << "No shader load";}; protected: QGLShader *m_vshader; QGLShader *m_fshader; QTGLSHADER_TYPE m_type; }; #endif // QTGLSHADER_H rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGLSphereComponent.cpp000066400000000000000000000145301254201074400240260ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // Based on code from the OpenGL SuperBible: /* * OpenGL SuperBible * Copyright (c) 2007-2009, Richard S. Wright Jr. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Richard S. Wright Jr. nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "QtGL.h" QtGLSphereComponent::QtGLSphereComponent() { } QtGLSphereComponent::~QtGLSphereComponent() { } void QtGLSphereComponent::generate(GLfloat radius, GLint numSlices, GLint numStacks) { GLfloat drho = (GLfloat)(3.141592653589) / (GLfloat) numStacks; GLfloat dtheta = 2.0f * (GLfloat)(3.141592653589) / (GLfloat) numSlices; GLfloat ds = 1.0f / (GLfloat) numSlices; GLfloat dt = 1.0f / (GLfloat) numStacks; GLfloat t = 1.0f; GLfloat s = 0.0f; GLint i, j; // Looping variables GLfloat stepSizeSlice = (3.1415926536f * 2.0f) / float(numSlices); QVector3D vertex[4]; QVector3D normal[4]; QVector2D texture[4]; reset(); int vertA = 0; int vertB = 1; int vertC = 2; int vertD = 3; for (i = 0; i < numStacks; i++) { GLfloat rho = (GLfloat)i * drho; GLfloat srho = (GLfloat)(sin(rho)); GLfloat crho = (GLfloat)(cos(rho)); GLfloat srhodrho = (GLfloat)(sin(rho + drho)); GLfloat crhodrho = (GLfloat)(cos(rho + drho)); // Many sources of OpenGL sphere drawing code uses a triangle fan // for the caps of the sphere. This however introduces texturing // artifacts at the poles on some OpenGL implementations s = 0.0f; for ( j = 0; j < numSlices; j++) { GLfloat theta = (j == numSlices) ? 0.0f : j * dtheta; GLfloat stheta = (GLfloat)(-sin(theta)); GLfloat ctheta = (GLfloat)(cos(theta)); GLfloat x = stheta * srho; GLfloat y = ctheta * srho; GLfloat z = crho; texture[vertA].setX(s); texture[vertA].setY(t); normal[vertA].setX(x); normal[vertA].setY(y); normal[vertA].setZ(z); vertex[vertA].setX(x * radius); vertex[vertA].setY(y * radius); vertex[vertA].setZ(z * radius); x = stheta * srhodrho; y = ctheta * srhodrho; z = crhodrho; texture[vertB].setX(s); texture[vertB].setY(t - dt); normal[vertB].setX(x); normal[vertB].setY(y); normal[vertB].setZ(z); vertex[vertB].setX(x * radius); vertex[vertB].setY(y * radius); vertex[vertB].setZ(z * radius); theta = ((j+1) == numSlices) ? 0.0f : (j+1) * dtheta; stheta = (GLfloat)(-sin(theta)); ctheta = (GLfloat)(cos(theta)); x = stheta * srho; y = ctheta * srho; z = crho; s += ds; texture[vertC].setX(s); texture[vertC].setY(t); normal[vertC].setX(x); normal[vertC].setY(y); normal[vertC].setZ(z); vertex[vertC].setX(x * radius); vertex[vertC].setY(y * radius); vertex[vertC].setZ(z * radius); x = stheta * srhodrho; y = ctheta * srhodrho; z = crhodrho; texture[vertD].setX(s); texture[vertD].setY(t - dt); normal[vertD].setX(x); normal[vertD].setY(y); normal[vertD].setZ(z); vertex[vertD].setX(x * radius); vertex[vertD].setY(y * radius); vertex[vertD].setZ(z * radius); addTriangle(vertex, normal, texture); // Rearrange for next triangle vertex[vertA] = vertex[vertB]; normal[vertA] = normal[vertB]; texture[vertA] = texture[vertB]; vertex[vertB] = vertex[vertD]; normal[vertB] = normal[vertD]; texture[vertB] = texture[vertD]; addTriangle(vertex, normal, texture); } t -= dt; } useIndexBuffer(true); useNormalBuffer(true); } void QtGLSphereComponent::draw() { QtGLComponent::draw(GL_TRIANGLES); } rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGLSphereComponent.h000066400000000000000000000060511254201074400234720ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // Based on code from the OpenGL SuperBible: /* * OpenGL SuperBible * Copyright (c) 2007-2009, Richard S. Wright Jr. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Richard S. Wright Jr. nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef QTGLSPHERECOMPONENT #define QTGLSPHERECOMPONENT class QtGLSphereComponent : public QtGLComponent { public: QtGLSphereComponent(); ~QtGLSphereComponent(); void generate(GLfloat radius, GLint numSlices, GLint numStacks); void draw(); }; #endif // QTGLSPHERECOMPONENT_H rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGLTextureShader.cpp000066400000000000000000000055241254201074400235070ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "QtGL.h" QtGLTextureShader::QtGLTextureShader(QObject *parent) : QtGLShader(parent) { #define PROGRAM_VERTEX_ATTRIBUTE 0 #define PROGRAM_TEXCOORD_ATTRIBUTE 1 m_type = QTGLSHADER_TEXTURE; m_vshader = new QGLShader(QGLShader::Vertex, parent); const char *vsrc = "attribute highp vec3 vertex;\n" "attribute mediump vec2 texCoord;\n" "varying mediump vec2 texc;\n" "uniform mediump mat4 matrix;\n" "void main(void)\n" "{\n" " gl_Position = matrix * vec4(vertex, 1.0);\n" " texc = texCoord;\n" "}\n"; m_vshader->compileSourceCode(vsrc); m_fshader = new QGLShader(QGLShader::Fragment, parent); const char *fsrc = "uniform sampler2D texture;\n" "varying mediump vec2 texc;\n" "void main(void)\n" "{\n" " gl_FragColor = texture2D(texture, texc.st);\n" "}\n"; m_fshader->compileSourceCode(fsrc); addShader(m_vshader); addShader(m_fshader); bindAttributeLocation("vertex", PROGRAM_VERTEX_ATTRIBUTE); bindAttributeLocation("texCoord", PROGRAM_TEXCOORD_ATTRIBUTE); link(); setUniformValue("texture", 0); } QtGLTextureShader::~QtGLTextureShader() { } void QtGLTextureShader::load(const QVector3D *vertices, const QVector2D *textureCoords) { bind(); setUniformValue("matrix", globalTransforms.modelViewProjectionMatrix); enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, vertices); setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, textureCoords); } rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGLTextureShader.h000066400000000000000000000030031254201074400231420ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef QTGLTEXTURESHADER_H #define QTGLTEXTURESHADER_H class QtGLTextureShader : public QtGLShader { public: QtGLTextureShader(QObject *parent); ~QtGLTextureShader(); void load(const QVector3D *vertices, const QVector2D *textureCoords); private: }; #endif // QTGLTEXTURESHADER_H rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGLWireCubeComponent.cpp000066400000000000000000000050241254201074400243030ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "QtGL.h" QtGLWireCubeComponent::QtGLWireCubeComponent() { } QtGLWireCubeComponent::~QtGLWireCubeComponent() { } void QtGLWireCubeComponent::generate(float width, float height, float depth) { float w2 = width / 2.0f; float h2 = height / 2.0f; float d2 = depth / 2.0f; reset(); addVertex(QVector3D(-w2, h2, d2)); addVertex(QVector3D(w2, h2, d2)); addVertex(QVector3D(-w2, -h2, d2)); addVertex(QVector3D(w2, -h2, d2)); addVertex(QVector3D(w2, -h2, d2)); addVertex(QVector3D(w2, h2, d2)); addVertex(QVector3D(-w2, -h2, d2)); addVertex(QVector3D(-w2, h2, d2)); addVertex(QVector3D(-w2, h2, -d2)); addVertex(QVector3D(w2, h2, -d2)); addVertex(QVector3D(-w2, -h2, -d2)); addVertex(QVector3D(w2, -h2, -d2)); addVertex(QVector3D(w2, -h2, -d2)); addVertex(QVector3D(w2, h2, -d2)); addVertex(QVector3D(-w2, -h2, -d2)); addVertex(QVector3D(-w2, h2, -d2)); addVertex(QVector3D(-w2, -h2, -d2)); addVertex(QVector3D(-w2, -h2, d2)); addVertex(QVector3D(w2, -h2, -d2)); addVertex(QVector3D(w2, -h2, d2)); addVertex(QVector3D(-w2, h2, -d2)); addVertex(QVector3D(-w2, h2, d2)); addVertex(QVector3D(w2, h2, -d2)); addVertex(QVector3D(w2, h2, d2)); } void QtGLWireCubeComponent::draw() { QtGLComponent::draw(GL_LINES); } rtimulib-7.2.1/Linux/RTIMULibGL/QtGLLib/QtGLWireCubeComponent.h000066400000000000000000000032371254201074400237540ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // This file contains OpenGL and Qt includes. This file should be included first // and the order of includes is very important (especially on Linux). #ifndef QTGLWIRECUBECOMPONENT_H #define QTGLWIRECUBECOMPONENT_H class QtGLWireCubeComponent : public QtGLComponent { public: QtGLWireCubeComponent(); ~QtGLWireCubeComponent(); void generate(float width, float height, float depth); void draw(); }; #endif // QTGLWIRECUBECOMPONENT_H rtimulib-7.2.1/Linux/RTIMULibGL/RTIMULibGL.pri000066400000000000000000000026301254201074400205070ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014, richards-tech #// #// Permission 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. INCLUDEPATH += $$PWD DEPENDPATH += $$PWD include(QtGLLib/QtGLLib.pri) include(VRWidgetLib/VRWidgetLib.pri) HEADERS += $$PWD/IMUView.h SOURCES += $$PWD/IMUView.cpp rtimulib-7.2.1/Linux/RTIMULibGL/VRWidgetLib/000077500000000000000000000000001254201074400203425ustar00rootroot00000000000000rtimulib-7.2.1/Linux/RTIMULibGL/VRWidgetLib/Images/000077500000000000000000000000001254201074400215475ustar00rootroot00000000000000rtimulib-7.2.1/Linux/RTIMULibGL/VRWidgetLib/Images/BlueShade.png000066400000000000000000000042111254201074400241070ustar00rootroot00000000000000PNG  IHDR?1 pHYsnu> IDATx=r=aA`b ۠  @AU@* 2/h㙞V+'{~θΕn z0|] !+0,VѵZOB6zcZW­[;uNj^7oq_Vk`VkkjuMi`XMϭ joMC''!`@3V+٦]cЎco Z٧O7-~cނߖ8w|pqpŽO5 Po ƴ~cQ7~cF\O4qHg7k18q Q q]7_;;~ݾ6~c:uxh~cztxh>> ڿ>/_=} nGGv>sA(իe ?>O7M 1C)HH'@;>s`?zDOEbٙ[`Oh 'OhQ=QC'@OO0 0 Oh6؂O1?3}l9`=,BkR /| 'e{q8ޯO _F\"O;/O5گOЯ[5FS{??OУؽgOН '{} z]U'G>'h_O'h>A8z}6~' D'h wE)}v~ A}'h[[qeg~ xUM'p[OW66D}Ohs ܠ-'3Oo>Ah F[OP/}J>ԈwC:'u'>A-bt&T!GGz\1}17}%l> :];>`wמ?SnI1'(}Bh?'(c}즷7}o{F >3'%7n38},h?'Hc2}h?bDI ڏxi~̢O\K~,O2~,O0G s~$Os<>A~$O#5}h?2'#}h?'؂#'}1lD>zEAQ>h? '`o$}s(NLl_g!O`퇄:퇔9Q~Aş&e2>JEVbQޏe{h<.G>p"ÃÏԏYnp$Ŷz8%z R< [óepnk=фYwhE}8 {?sk~4gM9-v;D&܈#k- /:ph ~E}4]\.}'Α:_h?8 ] h?Eѳ}~ۅ}`ϙhώm,}< _n-i]z jxz ~#zTXtCompanyxs,JO+QHK/=aIENDB`rtimulib-7.2.1/Linux/RTIMULibGL/VRWidgetLib/Images/Compass.png000066400000000000000000000251611254201074400236670ustar00rootroot00000000000000PNG  IHDR  pHYsnu> IDATxw\G玣H({!#Q5FblFE-`Ĩ kW4(( `FGDAz{~;;<D_5.mڴYxqrr 8cƌy:tܜ[.66ƍ111G\KK-Z9nݺ.]lڴi^^^A/ӦM\4RF޽{111>>>lVf"""Ξ=BO9tPLLL\\\RRRDDD&MެY3g-Z8|JJJRRR\\oLsW^`ar֖*s-,,DRYk?)? ,Ƶ U333 ۹}<;884hРvڜ,BT~}N!>֬Yj*"cիw _rZj0cqvv޲eݻwccc[6i۶mnZbP ,,,._M6mJwtt?~ҥKNdɒݻ\BD"qtt|5{oϳӟIsrr Tm۶7ue6LJm RԬY? .]dddE{6kѢEl4kÆ 4=11Q" ^m4^TTFFFO?[QQg}&hz+++l2͛c٬krJraaa?w޷n²ԩ&&$$@ii)}W+PNƎ?rtt4hЊ+'^po߾`ggӠAN;J>"J]\\\˱\UϞ=RIlܸwKi߾NNN>>> KQ-nll̦dnݺyzz .5hРaÆMGƍoҤ𠒝f8pv=c ??=zhPDboo_\++l~m[ K,1O<atQIQF޽(۷/GСٳOi(gϞ=cƌ)SM4? `ܹAAA+VX|y@@_U!W^wDDDY=oݺu#@FFСCU\3rHdW\Ie2&ӧUVиqcBƶnz׮]zj +%%!Kx۶mеkWBHn`׮]#GӧExxxjj*YK.E#c+qUcǎl0`9r'_xѣO>]p*I&-믿޽{wѢE[vuu2e t&| 3gجÇ.XXXBڴi>|ѣGnӧԩ[-ZįG0|v& &tvwmcOq0c٧@ǚUO̼y)^^^OڵkYϞ=cӯ_Y>䓩S.\pΜ9ӧO(g3f5j6o޼ٳgÆ ZzիWZj*2<++ ϘPv=tvMGųΟ?iii?޷o&cPR9{l%.]Js;۷o&&&* _@JN$%%Am-[rdWGwa۷izjj* L/--U9;R~89}4MYp!{ʽ{ԪUkʕ/^ ;rCBBL?]vG~͛7֯__^=!ūI&P}Ν;O^|vgwgooo_XXh\CMӧ@ZGK?Hxܹsǎ#}[lI1'Oӧ8`kky^zBի/^,/\0gΜCjn2fǾC?Ξ=&NJ9kwTlذ!!244~Pp%KYC ٹs'/:yƍ9*'7oDvjsCョ'cD cmmhѢ_~8R)V`ᜳg͚5n8M~%jHxy\\\zz:غu٭_pa6ԩSP>~g> 4O:Wͷ7999`رc 0/b;88D墝*Qvm,h˖-t8vS@jjj``s/㈈Ǐ +ի/F+GUjժt ӃQʡC/},5LJ H xҥxyy +{wΝ;gϞY2 Y+*VZyf={AT{hѢɓ'kh~H$hxܸq̙3Qx999K,I2ݴi8qbݺuxzM839|#996&Կ(..>{#G6l7R^VVj*:tN:5m4P/vܹuV011pל3gJr O=uThh3N!u֥.7={TyK4}flYYYwXзo?{KC}i\H@ܹsi:{^Ҋdɒ=zM`\SDP=7Æ :m߾=IIIݾ)͚5އseggg++ڵkٹ;L4I;SA <!!!l޽{ihqJLL$pIMMe+V%׮]K1*D>sCO:  fff<9@6m!F:<=rss7 ׭[G R~p<8iذ!0J  8JuӦM߼y ? ZqtD?x̘11 s@7FΝ[2!D*^r5,͛7]v޽{H{){Gj׮liif8F5DBBO<a5433C=UjՊz 99ysuttVnzгG.HpqL'O~ ua oooO?G量ϙCCCnݪɻO!2͍fQ9X"|B:88K. 6oAJ34} **:=z4l۶ K ""Çsjii&*O>':6?3{;3x`hhȦܼys̙k׮ `5 !(<)J`515a?U)dff&~!Gec4d .RI&_~%d*V턓~8*'֭[ };pĦ#&~Y9u>}:J]QQ*'NY;vHOOf͚Қ Yc4) }gQ ߽{w,*enݺu)SX`ͱM6_}03=B{!Mt#?/א~ ?~\XJ'Niaak֬k=T8hݺ Mc"ׯ_m͚5 DaG84r)`аaCٖ/_)G{̝;wb#FTlٲsǎcݻ/zRiggg`zM~9Q^ܹy(K )..)PwqJPƎXTs'(~b``gϨ)wp܆)?cnE+4mOW^x֋ѬSRR"ɰ  t9Sl@+88/O<XVVV\\c`=[n 3gb*o߮ Ȝ9sT RGy| +( 8ORWhBpݚ={6'8 l+WBrp fF#YY/_ tȨR4aϞ=1j?R娡v94PhcDzoDE6ṋ~/\:#*;44hǝ6ldU4)%V[?ؚ9sf۷G/Rp8c9ضuJJ kJ]Ԅ`RJ@8Ңݺuk۶-!} Ҭ A;\-[:v9 4 C?c_vE/ \@YpPGIY=z tصkמ6mZZZG ۷V '7@bՊ%ݾ 9~焐gsӸqcظq]]]qǎAqBu1/^d{TMӦMƧ~Z1h/h7n8fw9 A/>"4Ϫ~TqQ"fzSa4+prK^~re1Aꘪ98b~O%III׶Cڢ>i!ڷoI???(a.X#l*WkRne?]Ci֬deeiZ$sJlzjj*f"77gӦMC"^:th„ 8JP**"7???99ʕ+#թS5jB͛7 SVG~J޽{nBwօWLj+گ6mڼ~?_>r @TTU׮]رct.X.uEp̮PwsD+ٳ޼ypDCmAg>l*윖_ZZIǡ'ycٳgf xQ:tSYo,(%%;gšBZZZtt4](SӂW1 طod:@BB•+Wl۶mJǥP\\{͛7SEZ˖-׬Y'_f 52rܹ+V2.WTi=}wfjjwݺujb!;uDC1cM_‚ q6k֬;vPsaA`Ȑ!aQtxwnܸqÆ {&dÇg8'ܧO>x@p(bh5P8>c"|RXXK4MaaaTTH͞111YYY/_υ*IѦMovvq0ǏDK4T5oޜ.TE100x9/EN:,'QX,,,޾}KJRד}1 q6ekklٲcm׮-V\lԣѵn@}#Wh۶-vuuuuvvfz[n=qoYf-{4씎.+,, .޻w={XkÄ """tS^=;+*Lo.:t(Aݾ}Çњ.aL8ҍ7*ꤢV:tԩtןCÆ 5j_t_&iw4|A&iSlllnz̙3Ǐ笱7o^\\\LLȑ#iȑ#RSSoݺU^0l,2 Rvz3gΜ>}СC4z"""ovZJJJTTCY 1115sLT*Eu#05C(_,\B4F,==\iR a#3n3&K)Tq}wNW͛7 iAX;:\z#1W@Sŋ)N.'N8qѣG8N'j֬u֘ӧOOiӦOII 0 F͚5q x*hnjjG@WVV}ر#fQ/#4^rss뎀nnnD,))?''ӧ4lmm@ZYaycN4s|||AA'O^zUy)jƝ;wW^M6MII tƌCbu@캋\\\J%gޞ(氿K+񣛉Tjdddcc @XX:888"WXl/$_ G*bݺu\ C?w[ ٔ?OT.K366611ѵpp5wfe8w:fEDǏ7^-gZ IDATU,uXXX,Z.SV8*?ա7?fwfxtňHґ LX]jF600aԫWqf\\ s/_?!$ ]ۅ ZZZJ$ pbBT^GR%z*;޽!CNQ qƝ;w,D1KDDZ8!w-::z?M֭/--UyyyW0aD")aˌh8_)**LWrt@rX1;B 2RIC2`) X*JRD+W|r*KǑΝ;t(###\.+j> c 0ꨩ) vM HApu2Kf͚a{d&&&r"*u*<?RԿw򟛛[\\,J7nL/褬L"} b```hhhddK?!dڵk׮Uy/K.]t hHVBHEpr俸v%A !r8LJJ͞ !&&& #N@ee*ٞ'77ruO`;J{? XQ5x`(͛7`7n#e2BP(DK>,?RsZR%JKKsssJu#""P0R)a444d7+  ;w=_B8{sbiܹs= .]:ydV6 BB\.pQqB%N>y_(8755ڵ+_v-''_.؃_:$W% ..J 8rN}@vù}L/((/|埽MztH&1"x"~% ʿ1-6L&300F(..Vw AhYMtP'J/ 2DuѾR:!+[Jqӧ3VN_'Z`g_3v1uq"?~ȬRhт?122[szeD]ZVd*U>|kTkԴ3˲qjFgNϪٶ:%%Ewa_zY>fKvaQ~Ig$&&rڵk|g4n߮EDh\{@ϊܹsII R=K9~5Q%납u+Јo|*;s~T3*_oooÇǏEzoCW[K,aUW8;`h#8V@Ю];?VZ*iڴ)wD;{hQM>Sֿ۲eˀׯ_?//e˖ӝE>;&k׮e1t zo#ׄp7o:hOmBV`˖-ZлJTED_w_ |Ɋ}^^Dr Ӡ D_/OcFFFVtBrJ]TCQ5 ֭;zhv/'***11~77)SBbbbWH}*c59*WhHBB¤I =g0!ӈ:rx;]%+%%~nnn}uwwp4ԣGEGGzo#?f0`AHy #//OGh"NWsG]#yjt|zoW@|/R޾(HG{k^@/Q`?h XZt;TAQRQEK>D_E玓&M4hÇ_zUjժ8Ǐ^ !/^8vBV8/ʿ(DQQE?tGX:!A ,UG(̈/nj(VZ+!o5#zTXtCompanyxs,JO+QHK/=aIENDB`rtimulib-7.2.1/Linux/RTIMULibGL/VRWidgetLib/Images/CompassNeedle.png000066400000000000000000000005671254201074400250070ustar00rootroot00000000000000PNG  IHDR  pHYsnu>IDATxױ 0A-I܇=$:xyXv}߽J)0վzRj}8D@AD@AD@AD@AD@AD@AD@Ar#zTXtCompanyxs,JO+QHK/=aIENDB`rtimulib-7.2.1/Linux/RTIMULibGL/VRWidgetLib/Images/GreenShade.png000066400000000000000000000113401254201074400242610ustar00rootroot00000000000000PNG  IHDR?1 pHYsnu>bIDATxwxUohQ@"*8p"**Cl a%8 Yƶ'{Զnm "v㴔 7s}| &II")F?Z~v5qP 7!ˈ87)񽍈z{ KᘽE( @\h.֏ w-ei:g5 lG X >ԋ^!Bi+s~PG:ڞ|5yYI08|$/_?yfՏ >Q{~ փ!Bs0HE^/ja=&|'؞<`h]raQ OiIQ @ D'ۓVh1aM:ԏ ҝal{r kڏi^CIq:WنoUSlOZbU ҕ5Ԙ$Oi"em& @ܣ+]ÄZfOj gpFj{zR?^CIyY+Y/HET&x tPsٞ֏ qtʭ&c]YXH|ʩoGBjNtۓXM<h.t[ ۓ_E6ُH.߰,bZ?8N+]mO:[4qyYC<4qUi{r=~8dN6?GX;k9lO~ɗ>ȃj/ @b$N 6?x[KXF7ۓ|h#Vgsɯ*߁[O;=Q?Dy_u.q: @3CG(L0s87|3r щN!B{8p+[DA': ˹'%H !Bb V8p+C &܃'nneN3F3mH!B/g uaٞtshRq\POzڞ<|{Xh8.Lؤo6˵HD:Dhb#Ä|ۓVwrGCrlI٘O'GVe{ҪDhbB.ۓ81Xv;{~4E:,e\h{r#2sX_E'7i,co6Տ t ,aG1KYz1۞Ħqhh 7KXbR8?z~4ٗUoz۞GhUox_@\hڼ?ԏ>8wUz x߼} ЬmOnf&P'@"kJ2~4ՈF߇>'Äwہ9,9jլՈF hTl.OԈFԿ-~4DӐp#76q',qh ! (LoԿHH i87sm=iKX$IL{ p';snnYeyy}˷%4߲꿅[Ф{sw哟hUd&Կym`3ws ` cĬ 0"D g H$@ "+X1Qs3 '+XGDHHo&?c4I&9HpsLUina_Bad~\4WH&94÷%+RQğU %׶W2̵ub@dSI5 PѥS2#HdRJPOd&K(@|XQfX?48_*8-븮=Ud|ʧWLpQfRS`;$v4C,6Uhqof*oX.,nG;ۓg4.j O#-rVD%\b[iɛJKnO{ۓo@G:SnX*o%~:$bg}5՗pwx'4c ЎvYlX*xI]5՗rwy74{ 48j1W 2.=i*:p+U؞Ħ|UE,2+['1^pVx_ V Tb'U?ٞ_b՟h|;#9r! 뿑Ubv! /rۓv7-bOL>@/dOX~@[.`\a{Ҫ T"Jۓv37~ưlWbPf>vObRUzOִ*=_~hM̿mO_Ⱦ<<7nyS<_jXdɝ, /TĂ7Ip+ZUR9A'ws S)Wbh5yl{RK>8rG.&oZۓ;9ie~{?Zr.s/TK\:rCz-LWR7n_Bꗺq2԰9QRg@KZVPaX f~' %-)0/q3ܤ̜Üb-hQF?dlf~8Ó.%<ΤY̚,/QE)fLf~ P-zG~PuZJF~8 9K(1s3]K8ǡ|<ߤJTĔ?i3w*K)UМp5W?QN z /t*SwӁ|h6`~qRlЌf daSI1@DSy@SP0A&WPQLdMiZH`~qzZ25DLDy?!&WRYD8߼LV_QUP3?c7|1,Ob2~q(qh76nl:3vnJMܣ^!\WRWB G0°U6u|RI = *{>N"zm۹~Ed_F~q>ФoRJ˙~4D&fрJ M )4U~XoY&+l`bxHmrLᅢ;f2St_C'\l3])sOQ2|g0cbs="Ӷ5f@cR8 iXL?oiIDATxMr;q^ cưPW*P n[ѽ~'Թ]&XbfZ6}x?mM kulׯݻ] X֩'5:O +"'ve?1+/nBts,\_]^?ڧO:.Pѱdߒ<1PK(uhU7ylݮWWAmn1FKo l~cLOjѠ674k1]5n1WAui1ng};T?so׶jw xP|8Z}# $Y@lۙ۩~L~c SJyM9~c }9? fk1E(Đ7=~c7m~c7xh1hI@}~o *s~cPxw:,'@}E k}`+17o1X 0g:lOg~cP \@.'Ao m1XRW&h18mi1xig&k1x~?U)o ,>BHJ׶{׻>dBjH?%c&E7>rO-' ?E=3 ?OmM|O}44}>9 ϠO&xGl'@M> P_E @m ^Q'ݲ>V 7O,Eo ]`iIpoLg=}F' }ߗ> p+'''W'>h fy oO`:E'pcs'H= }YpÐ @GO>Ap36}ȸO ,N4@ &>A@}hh+p7qpCApI >{3}hs~ !D'3'pk,p (j?['|'pǥO0:O04>h y>s'QѾ^ D`,2DOI`h2+}=>ퟛ>ퟞ> Ǽ@M: zxB+ڏ xEN)=pey>S'h}H!b>A+B}&x<~d'w>(OPG!}j~dq3>Aklʼn&@`ڏm +ˍ}5AQ>A1ڏz ~TOP6}\jGY }ek'X-\Bј>YA{ ]ܰ>K逎 K;ѝ> }K2BRҗPӽ6D/L1ūr%9 #zߏt|1ڏz%ڏ!uyڏQ ڏ5~ڏ|vڏ5{j#U1h@ @KwsVDDyGT7~ĵ]2GtF13_év~է?~xw>|@1:|0d]Wi?cNo,]^C%ަNYp}e #zTXtCompanyxs,JO+QHK/=aIENDB`rtimulib-7.2.1/Linux/RTIMULibGL/VRWidgetLib/VRIMUWidget.cpp000066400000000000000000000125321254201074400231170ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "VRIMUWidget.h" // Do simpler shading on ARMs #if defined(__arm__) || defined(__ARMEL__) #define VRIMUWIDGET_SHADER QTGLSHADER_TEXTURE #else #define VRIMUWIDGET_SHADER QTGLSHADER_ADSTEXTURE #endif VRIMUWidget::VRIMUWidget(QObject *parent, VRWIDGET_TYPE widgetType) : VRWidget(parent, widgetType) { } VRIMUWidget::~VRIMUWidget() { } void VRIMUWidget::VRWidgetInit() { m_IMULength = m_width; m_IMURadius = m_IMULength / 10.0; m_IMUXCylinder.generate(m_IMURadius / 100.0, m_IMURadius, m_IMULength, 50, 1); m_IMUXCylinder.setShader(globalShader[VRIMUWIDGET_SHADER]); m_IMUXCylinder.setMaterial(QVector3D(1.0, 1.0, 1.0), QVector3D(0.8, 0.8, 0.8), QVector3D(1.0, 1.0, 1.0), 3.0); m_IMUXCylinder.setTexture(QPixmap(":/Images/RedShade.png").toImage()); m_IMUXTop.generate(0, m_IMURadius, 50, 1); m_IMUXTop.setShader(globalShader[VRIMUWIDGET_SHADER]); m_IMUXTop.setMaterial(QVector3D(1.0, 1.0, 1.0), QVector3D(0.8, 0.8, 0.8), QVector3D(1.0, 1.0, 1.0), 3.0); m_IMUXTop.setTexture(QPixmap(":/Images/RedShade.png").toImage()); m_IMUYCylinder.generate(m_IMURadius / 100.0, m_IMURadius, m_IMULength, 50, 1); m_IMUYCylinder.setShader(globalShader[VRIMUWIDGET_SHADER]); m_IMUYCylinder.setMaterial(QVector3D(1.0, 1.0, 1.0), QVector3D(0.8, 0.8, 0.8), QVector3D(1.0, 1.0, 1.0), 3.0); m_IMUYCylinder.setTexture(QPixmap(":/Images/GreenShade.png").toImage()); m_IMUYTop.generate(0, m_IMURadius, 50, 1); m_IMUYTop.setShader(globalShader[VRIMUWIDGET_SHADER]); m_IMUYTop.setMaterial(QVector3D(1.0, 1.0, 1.0), QVector3D(0.8, 0.8, 0.8), QVector3D(1.0, 1.0, 1.0), 3.0); m_IMUYTop.setTexture(QPixmap(":/Images/GreenShade.png").toImage()); m_IMUZCylinder.generate(m_IMURadius / 100.0, m_IMURadius, m_IMULength, 50, 1); m_IMUZCylinder.setShader(globalShader[VRIMUWIDGET_SHADER]); m_IMUZCylinder.setMaterial(QVector3D(1.0, 1.0, 1.0), QVector3D(0.8, 0.8, 0.8), QVector3D(1.0, 1.0, 1.0), 3.0); m_IMUZCylinder.setTexture(QPixmap(":/Images/BlueShade.png").toImage()); m_IMUZTop.generate(0, m_IMURadius, 50, 1); m_IMUZTop.setShader(globalShader[VRIMUWIDGET_SHADER]); m_IMUZTop.setMaterial(QVector3D(1.0, 1.0, 1.0), QVector3D(0.8, 0.8, 0.8), QVector3D(1.0, 1.0, 1.0), 3.0); m_IMUZTop.setTexture(QPixmap(":/Images/BlueShade.png").toImage()); m_cube.generate(m_IMULength / 2.0, m_IMULength / 2.0, m_IMULength / 2.0); m_cube.setShader(globalShader[QTGLSHADER_FLAT]); m_cube.setMaterial(QVector3D(1.0, 1.0, 1.0), QVector3D(0.8, 0.8, 0.8), QVector3D(1.0, 1.0, 1.0), 6.0); m_cube.setColor(QColor(255, 255, 0)); setRotationOrder(VRWidgetRotationXZY); // so that roll/pitch/yaw works } void VRIMUWidget::VRWidgetRender() { startWidgetRender(); // do cube startComponentRender(0, 0, 0, 0, 0, 0); m_cube.draw(); endComponentRender(m_IMUXCylinder.getBoundMinus(), m_IMUXCylinder.getBoundPlus()); // do X IMU cylinder startComponentRender(0, 0, 0, 0, 90, 0); m_IMUXCylinder.draw(); endComponentRender(m_IMUXCylinder.getBoundMinus(), m_IMUXCylinder.getBoundPlus()); // do X IMU top startComponentRender(m_IMULength, 0, 0, 0, 90, 0); m_IMUXTop.draw(); endComponentRender(m_IMUXTop.getBoundMinus(), m_IMUXTop.getBoundPlus()); // do Y IMU cylinder startComponentRender(0, 0, 0, 0, 0, 0); m_IMUYCylinder.draw(); endComponentRender(m_IMUYCylinder.getBoundMinus(), m_IMUYCylinder.getBoundPlus()); // do Y IMU top startComponentRender(0, 0, m_IMULength, 0, 0, -90); m_IMUYTop.draw(); endComponentRender(m_IMUYTop.getBoundMinus(), m_IMUYTop.getBoundPlus()); // do Z IMU cylinder startComponentRender(0, 0, 0, 90, 0, 0); m_IMUZCylinder.draw(); endComponentRender(m_IMUZCylinder.getBoundMinus(), m_IMUZCylinder.getBoundPlus()); // do Z IMU top startComponentRender(0, -m_IMULength, 0, 90, 0, 0); m_IMUZTop.draw(); endComponentRender(m_IMUZTop.getBoundMinus(), m_IMUZTop.getBoundPlus()); endWidgetRender(); } rtimulib-7.2.1/Linux/RTIMULibGL/VRWidgetLib/VRIMUWidget.h000066400000000000000000000035141254201074400225640ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef VRIMUWIDGET_H #define VRIMUWIDGET_H #include "VRWidget.h" class VRIMUWidget : public VRWidget { Q_OBJECT public: VRIMUWidget(QObject *parent, VRWIDGET_TYPE = VRWIDGET_IMU); ~VRIMUWidget(); virtual void VRWidgetInit(); virtual void VRWidgetRender(); private: QtGLCylinderComponent m_IMUXCylinder; QtGLCylinderComponent m_IMUYCylinder; QtGLCylinderComponent m_IMUZCylinder; QtGLDiskComponent m_IMUXTop; QtGLDiskComponent m_IMUYTop; QtGLDiskComponent m_IMUZTop; QtGLWireCubeComponent m_cube; qreal m_IMURadius; qreal m_IMULength; }; #endif // VRIMUWIDGET_H rtimulib-7.2.1/Linux/RTIMULibGL/VRWidgetLib/VRWidget.cpp000066400000000000000000000607541254201074400225550ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "VRWidget.h" #include VRWidget::VRWidget(QObject *parent, VRWIDGET_TYPE widgetType) : QObject(parent) { m_widgetType = widgetType; m_selected = false; m_center = QVector3D(VRWIDGET_DEFAULT_X, VRWIDGET_DEFAULT_Y, VRWIDGET_DEFAULT_Z / globalTransforms.tanViewportFOV); m_rotation = QVector3D(0, 0, 0); m_width = VRWIDGET_DEFAULT_WIDTH; m_height = VRWIDGET_DEFAULT_HEIGHT; m_depth = VRWIDGET_DEFAULT_DEPTH; setModelMatrix(); m_icon = "IconDefault.png"; m_enableMoveX = m_enableMoveY = m_enableMoveZ = true; m_enableRotX = m_enableRotY = m_enableRotZ = true; m_drawSelectBox = true; m_rotationOrder = VRWidgetRotationZYX; } VRWidget::~VRWidget() { } void VRWidget::startWidgetRender() { pushModelMatrix(); globalTransforms.modelViewMatrix = m_modelMatrix * globalTransforms.modelViewMatrix; m_boundMinus = QVector3D(10000.0f, 10000.0f, 10000.0f); m_boundPlus = QVector3D(-10000.0f, -10000.0f, -10000.0f); m_offsetMatrix.setToIdentity(); } void VRWidget::startCompositeRender(const QVector3D& posOffset, const QVector3D& angleOffset) { startComponentRender(posOffset.x(), posOffset.y(), posOffset.z(), angleOffset.x(), angleOffset.y(), angleOffset.z()); } void VRWidget::startCompositeRender(float offsetX, float offsetY, float offsetZ, float rotOffsetX, float rotOffsetY, float rotOffsetZ) { startComponentRender(offsetX, offsetY, offsetZ, rotOffsetX, rotOffsetY, rotOffsetZ); } void VRWidget::startComponentRender(const QVector3D& posOffset, const QVector3D& angleOffset) { startComponentRender(posOffset.x(), posOffset.y(), posOffset.z(), angleOffset.x(), angleOffset.y(), angleOffset.z()); } void VRWidget::startComponentRender(float offsetX, float offsetY, float offsetZ, float rotOffsetX, float rotOffsetY, float rotOffsetZ) { pushModelMatrix(); pushOffsetMatrix(); globalTransforms.modelViewMatrix.translate(offsetX, offsetY, offsetZ); m_offsetMatrix.translate(offsetX, offsetY, offsetZ); switch (m_rotationOrder) { case VRWidgetRotationXYZ: globalTransforms.modelViewMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); m_offsetMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); break; case VRWidgetRotationXZY: globalTransforms.modelViewMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); m_offsetMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); break; case VRWidgetRotationYXZ: globalTransforms.modelViewMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); m_offsetMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); break; case VRWidgetRotationYZX: globalTransforms.modelViewMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); m_offsetMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); break; case VRWidgetRotationZXY: globalTransforms.modelViewMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); m_offsetMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); break; case VRWidgetRotationZYX: globalTransforms.modelViewMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); m_offsetMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); break; } globalTransforms.modelViewProjectionMatrix = globalTransforms.projectionMatrix * globalTransforms.modelViewMatrix; globalTransforms.normalMatrix = globalTransforms.modelViewMatrix.normalMatrix(); } void VRWidget::endComponentRender(const QVector3D& boundMinus, const QVector3D& boundPlus) { QVector3D mappedMinus, mappedPlus; mappedMinus = m_offsetMatrix.map(boundMinus); mappedPlus = m_offsetMatrix.map(boundPlus); if (mappedMinus.x() < m_boundMinus.x()) m_boundMinus.setX(mappedMinus.x()); if (mappedMinus.y() < m_boundMinus.y()) m_boundMinus.setY(mappedMinus.y()); if (mappedMinus.z() < m_boundMinus.z()) m_boundMinus.setZ(mappedMinus.z()); if (mappedPlus.x() > m_boundPlus.x()) m_boundPlus.setX(mappedPlus.x()); if (mappedPlus.y() > m_boundPlus.y()) m_boundPlus.setY(mappedPlus.y()); if (mappedPlus.z() > m_boundPlus.z()) m_boundPlus.setZ(mappedPlus.z()); popOffsetMatrix(); popModelMatrix(); } void VRWidget::endCompositeRender() { popOffsetMatrix(); popModelMatrix(); } void VRWidget::endWidgetRender() { float border = 0.1f; if (m_selected && m_drawSelectBox) { QtGLWireCubeComponent box; box.generate( m_boundPlus.x() - m_boundMinus.x() + border, m_boundPlus.y() - m_boundMinus.y() + border, m_boundPlus.z() - m_boundMinus.z() + border); globalTransforms.modelViewMatrix.translate((m_boundPlus.x() + m_boundMinus.x()) / 2.0f, (m_boundPlus.y() + m_boundMinus.y()) / 2.0f, (m_boundPlus.z() + m_boundMinus.z()) / 2.0f); box.setShader(globalShader[QTGLSHADER_FLAT]); box.setColor(Qt::red); globalTransforms.modelViewProjectionMatrix = globalTransforms.projectionMatrix * globalTransforms.modelViewMatrix; box.draw(); } ARCustomSelectBox(); popModelMatrix(); if (m_modelMatrixStack.size() != 0) { qDebug() << m_widgetType << " leaving with non empty model stack"; return; } if (m_offsetMatrixStack.size() != 0) { qDebug() << m_widgetType << " leaving with non empty offset stack"; return; } } void VRWidget::pushModelMatrix() { m_modelMatrixStack.push(globalTransforms.modelViewMatrix); } void VRWidget::popModelMatrix() { if (m_modelMatrixStack.size() == 0) { qDebug() << m_widgetType << " tried to pop empty matrix stack"; return; } globalTransforms.modelViewMatrix = m_modelMatrixStack.pop(); } void VRWidget::pushOffsetMatrix() { m_offsetMatrixStack.push(m_offsetMatrix); } void VRWidget::popOffsetMatrix() { if (m_offsetMatrixStack.size() == 0) { qDebug() << m_widgetType << " tried to pop empty offset matrix stack"; return; } m_offsetMatrix = m_offsetMatrixStack.pop(); } void VRWidget::setCenter(float x, float y, float z, bool unconditional) { QVector3D newCenter(x, y, z); conditionalMove(newCenter, unconditional); } void VRWidget::setCenter(const QVector3D center, bool unconditional) { conditionalMove(center, unconditional); } void VRWidget::getCenter(float& x, float& y, float& z) { x = m_center.x(); y = m_center.y(); z = m_center.z(); } void VRWidget::getCenter(QVector3D& center) { center = m_center; } void VRWidget::moveCenter(float deltaX, float deltaY, float deltaZ, bool unconditional) { QVector3D newCenter = m_center + QVector3D(deltaX, deltaY, deltaZ); conditionalMove(newCenter, unconditional); } void VRWidget::moveCenter(const QVector3D delta, bool unconditional) { QVector3D newCenter = m_center + delta; conditionalMove(newCenter, unconditional); } void VRWidget::moveCenterTransform(const QVector3D& intersection, const QVector3D& newIntersection) { QVector3D mappedIntersection; QVector3D mappedNewIntersection; mappedIntersection = m_modelMatrix.map(intersection); mappedNewIntersection = m_modelMatrix.map(newIntersection); QVector3D newCenter = m_center + mappedNewIntersection - mappedIntersection; conditionalMove(newCenter, false); // qDebug() << "MCT " << mappedIntersection.x() << " -> " << mappedNewIntersection.x() << " " // << mappedIntersection.y() << " -> " << mappedNewIntersection.y() << " " // << mappedIntersection.z() << " -> " << mappedNewIntersection.z() << " "; moveTowardOrigin(mappedNewIntersection.length() - mappedIntersection.length()); } void VRWidget::moveTowardOrigin(float distance) { // qDebug() << "MTO " << distance; QVector3D origin = -m_center; origin.normalize(); origin *= distance; QVector3D newCenter = m_center + origin; conditionalMove(newCenter, false); } void VRWidget::setRotationOrder(VRWidgetRotationOrder order) { m_rotationOrder = order; } void VRWidget::setRotation(float x, float y, float z, bool unconditional) { QVector3D newRotation(x, y, z); conditionalRotate(newRotation, unconditional); } void VRWidget::setRotation(const QVector3D rotation, bool unconditional) { conditionalRotate(rotation, unconditional); } void VRWidget::getRotation(float& x, float& y, float& z) { x = m_rotation.x(); y = m_rotation.y(); z = m_rotation.z(); } void VRWidget::getRotation(QVector3D& rotation) { rotation = m_rotation; } void VRWidget::moveRotation(float deltaX, float deltaY, float deltaZ, bool unconditional) { QVector3D rotation; rotation.setX(m_rotation.x() + deltaX); rotation.setY(m_rotation.y() + deltaY); rotation.setZ(m_rotation.z() + deltaZ); conditionalRotate(rotation, unconditional); } void VRWidget::moveRotation(const QVector3D delta, bool unconditional) { QVector3D rotation(m_rotation); rotation += delta; conditionalRotate(rotation, unconditional); } void VRWidget::setSize(float width, float height, float depth) { m_width = width; m_height = height; m_depth = depth; } void VRWidget::getSize(float& width, float& height, float& depth) { width = m_width; height = m_height; depth = m_depth; } void VRWidget::setMoveMask(bool enableX, bool enableY, bool enableZ) { m_enableMoveX = enableX; m_enableMoveY = enableY; m_enableMoveZ = enableZ; } void VRWidget::setRotationMask(bool enableX, bool enableY, bool enableZ) { m_enableRotX = enableX; m_enableRotY = enableY; m_enableRotZ = enableZ; } void VRWidget::updateViewportBox() { // bottom left front m_viewportBoundBox[0] = m_modelMatrix.map(QVector3D(m_boundMinus.x() - VRWIDGET_BOUNDBOX_BORDER, m_boundMinus.y() - VRWIDGET_BOUNDBOX_BORDER, m_boundPlus.z() + VRWIDGET_BOUNDBOX_BORDER)); // top left front m_viewportBoundBox[1] = m_modelMatrix.map(QVector3D(m_boundMinus.x() - VRWIDGET_BOUNDBOX_BORDER, m_boundPlus.y() + VRWIDGET_BOUNDBOX_BORDER, m_boundPlus.z() + VRWIDGET_BOUNDBOX_BORDER)); // top right front m_viewportBoundBox[2] = m_modelMatrix.map(QVector3D(m_boundPlus.x() + VRWIDGET_BOUNDBOX_BORDER, m_boundPlus.y() + VRWIDGET_BOUNDBOX_BORDER, m_boundPlus.z() + VRWIDGET_BOUNDBOX_BORDER)); // bottom right front m_viewportBoundBox[3] = m_modelMatrix.map(QVector3D(m_boundPlus.x() + VRWIDGET_BOUNDBOX_BORDER, m_boundMinus.y() - VRWIDGET_BOUNDBOX_BORDER, m_boundPlus.z() + VRWIDGET_BOUNDBOX_BORDER)); // bottom left back m_viewportBoundBox[4] = m_modelMatrix.map(QVector3D(m_boundMinus.x() - VRWIDGET_BOUNDBOX_BORDER, m_boundMinus.y() - VRWIDGET_BOUNDBOX_BORDER, m_boundMinus.z() - VRWIDGET_BOUNDBOX_BORDER)); // top left back m_viewportBoundBox[5] = m_modelMatrix.map(QVector3D(m_boundMinus.x() - VRWIDGET_BOUNDBOX_BORDER, m_boundPlus.y() + VRWIDGET_BOUNDBOX_BORDER, m_boundMinus.z() - VRWIDGET_BOUNDBOX_BORDER)); // top right back m_viewportBoundBox[6] = m_modelMatrix.map(QVector3D(m_boundPlus.x() + VRWIDGET_BOUNDBOX_BORDER, m_boundPlus.y() + VRWIDGET_BOUNDBOX_BORDER, m_boundMinus.z() - VRWIDGET_BOUNDBOX_BORDER)); // bottom right back m_viewportBoundBox[7] = m_modelMatrix.map(QVector3D(m_boundPlus.x() + VRWIDGET_BOUNDBOX_BORDER, m_boundMinus.y() - VRWIDGET_BOUNDBOX_BORDER, m_boundMinus.z() - VRWIDGET_BOUNDBOX_BORDER)); } bool VRWidget::boundingBoxIntersection(QVector3D& intersection, int& rectIndex, const QVector3D& ray0, const QVector3D& ray1) { QVector3D bestIntersection; int bestFace = -1; float bestDistance = 100000000000; float length; updateViewportBox(); // Nearest face rectIndex = 0; if (QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[0], m_viewportBoundBox[1], m_viewportBoundBox[2], m_viewportBoundBox[3], true)) { bestIntersection = intersection; bestDistance = bestIntersection.length(); bestFace = rectIndex; } // Furthest face rectIndex++; if (QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[4], m_viewportBoundBox[5], m_viewportBoundBox[6], m_viewportBoundBox[7], true)) { if ((length = intersection.length()) < bestDistance) { bestIntersection = intersection; bestDistance = length; bestFace = rectIndex; } } // Top face rectIndex++; if (QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[1], m_viewportBoundBox[5], m_viewportBoundBox[6], m_viewportBoundBox[2], true)) { if ((length = intersection.length()) < bestDistance) { bestIntersection = intersection; bestDistance = length; bestFace = rectIndex; } } // Bottom face rectIndex++; if (QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[0], m_viewportBoundBox[4], m_viewportBoundBox[7], m_viewportBoundBox[3], true)) { if ((length = intersection.length()) < bestDistance) { bestIntersection = intersection; bestDistance = length; bestFace = rectIndex; } } // Left side face rectIndex++; if (QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[0], m_viewportBoundBox[4], m_viewportBoundBox[5], m_viewportBoundBox[1], true)) { if ((length = intersection.length()) < bestDistance) { bestIntersection = intersection; bestDistance = length; bestFace = rectIndex; } } // Right side face rectIndex++; if (QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[3], m_viewportBoundBox[2], m_viewportBoundBox[6], m_viewportBoundBox[7], true)) { if ((length = intersection.length()) < bestDistance) { bestIntersection = intersection; bestDistance = length; bestFace = rectIndex; } } if (bestFace == -1) return false; intersection = m_inverseModelMatrix.map(bestIntersection); rectIndex = bestFace; qDebug() << "Hit face " << rectIndex << " raw inter " << bestIntersection.x() << " " << bestIntersection.y() << " " << bestIntersection.z(); return true; } bool VRWidget::boundingRectIntersection(QVector3D& intersection, const int rectIndex, const QVector3D& ray0, const QVector3D& ray1) { bool success; updateViewportBox(); switch (rectIndex) { // Nearest face case 0: success = QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[0], m_viewportBoundBox[1], m_viewportBoundBox[2], m_viewportBoundBox[3], false); break; // Furthest face case 1: success = QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[4], m_viewportBoundBox[5], m_viewportBoundBox[6], m_viewportBoundBox[7], false); break; // Top face case 2: success = QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[1], m_viewportBoundBox[5], m_viewportBoundBox[6], m_viewportBoundBox[2], false); break; // Bottom face case 3: success = QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[0], m_viewportBoundBox[4], m_viewportBoundBox[7], m_viewportBoundBox[3], false); break; // Left side face case 4: success = QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[0], m_viewportBoundBox[4], m_viewportBoundBox[5], m_viewportBoundBox[1], false); break; // Right side face case 5: success = QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[3], m_viewportBoundBox[2], m_viewportBoundBox[6], m_viewportBoundBox[7], false); break; default: success = false; } if (success) { // qDebug() << "raw inter " << intersection.x() << " " << intersection.y() << " " << intersection.z(); intersection = m_inverseModelMatrix.map(intersection); } return success; } void VRWidget::setModelMatrix() { m_modelMatrix.setToIdentity(); m_inverseModelMatrix.setToIdentity(); m_modelMatrix.translate(m_center); switch (m_rotationOrder) { case VRWidgetRotationXYZ: m_modelMatrix.rotate(m_rotation.z(), 0.0f, 0.0f, 1.0f); m_modelMatrix.rotate(m_rotation.y(), 0.0f, 1.0f, 0.0f); m_modelMatrix.rotate(m_rotation.x(), 1.0f, 0.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.x(), 1.0f, 0.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.y(), 0.0f, 1.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.z(), 0.0f, 0.0f, 1.0f); break; case VRWidgetRotationXZY: m_modelMatrix.rotate(m_rotation.y(), 0.0f, 1.0f, 0.0f); m_modelMatrix.rotate(m_rotation.z(), 0.0f, 0.0f, 1.0f); m_modelMatrix.rotate(m_rotation.x(), 1.0f, 0.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.x(), 1.0f, 0.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.z(), 0.0f, 0.0f, 1.0f); m_inverseModelMatrix.rotate(-m_rotation.y(), 0.0f, 1.0f, 0.0f); break; case VRWidgetRotationYXZ: m_modelMatrix.rotate(m_rotation.z(), 0.0f, 0.0f, 1.0f); m_modelMatrix.rotate(m_rotation.x(), 1.0f, 0.0f, 0.0f); m_modelMatrix.rotate(m_rotation.y(), 0.0f, 1.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.y(), 0.0f, 1.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.x(), 1.0f, 0.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.z(), 0.0f, 0.0f, 1.0f); break; case VRWidgetRotationYZX: m_modelMatrix.rotate(m_rotation.x(), 1.0f, 0.0f, 0.0f); m_modelMatrix.rotate(m_rotation.z(), 0.0f, 0.0f, 1.0f); m_modelMatrix.rotate(m_rotation.y(), 0.0f, 1.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.y(), 0.0f, 1.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.z(), 0.0f, 0.0f, 1.0f); m_inverseModelMatrix.rotate(-m_rotation.x(), 1.0f, 0.0f, 0.0f); break; case VRWidgetRotationZXY: m_modelMatrix.rotate(m_rotation.y(), 0.0f, 1.0f, 0.0f); m_modelMatrix.rotate(m_rotation.x(), 1.0f, 0.0f, 0.0f); m_modelMatrix.rotate(m_rotation.z(), 0.0f, 0.0f, 1.0f); m_inverseModelMatrix.rotate(-m_rotation.z(), 0.0f, 0.0f, 1.0f); m_inverseModelMatrix.rotate(-m_rotation.x(), 1.0f, 0.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.y(), 0.0f, 1.0f, 0.0f); break; case VRWidgetRotationZYX: m_modelMatrix.rotate(m_rotation.x(), 1.0f, 0.0f, 0.0f); m_modelMatrix.rotate(m_rotation.y(), 0.0f, 1.0f, 0.0f); m_modelMatrix.rotate(m_rotation.z(), 0.0f, 0.0f, 1.0f); m_inverseModelMatrix.rotate(-m_rotation.z(), 0.0f, 0.0f, 1.0f); m_inverseModelMatrix.rotate(-m_rotation.y(), 0.0f, 1.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.x(), 1.0f, 0.0f, 0.0f); break; } m_inverseModelMatrix.translate(-m_center); } void VRWidget::conditionalMove(const QVector3D& newCenter, bool unconditional) { QVector3D oldCenter; bool visible = true; oldCenter = m_center; if (m_enableMoveX) m_center.setX(newCenter.x()); if (m_enableMoveY) m_center.setY(newCenter.y()); if (m_enableMoveZ) m_center.setZ(newCenter.z()); setModelMatrix(); if (unconditional) return; updateViewportBox(); for (int i = 0; i < 8; i++) { if ((m_viewportBoundBox[i].z() >= -globalTransforms.nearPlane) || (m_viewportBoundBox[i].z() <= -globalTransforms.farPlane)) { visible = false; break; } } if (!visible) { m_center = oldCenter; setModelMatrix(); } } void VRWidget::conditionalRotate(const QVector3D& newRotation, bool unconditional) { QVector3D oldRotation; bool visible = true; oldRotation = m_rotation; if (m_enableRotX) m_rotation.setX(newRotation.x()); if (m_enableRotY) m_rotation.setY(newRotation.y()); if (m_enableRotZ) m_rotation.setZ(newRotation.z()); setModelMatrix(); if (unconditional) return; updateViewportBox(); for (int i = 0; i < 8; i++) { if ((m_viewportBoundBox[i].z() >= -globalTransforms.nearPlane) || (m_viewportBoundBox[i].z() <= -globalTransforms.farPlane)) { visible = false; break; } } if (!visible) { m_rotation = oldRotation; setModelMatrix(); } } rtimulib-7.2.1/Linux/RTIMULibGL/VRWidgetLib/VRWidget.h000066400000000000000000000246351254201074400222200ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef VRWIDGET_H #define VRWIDGET_H #include #include #include #include typedef enum { VRWIDGET_WIDGET, VRWIDGET_POINTER, VRWIDGET_SELECTOR, VRWIDGET_STATUS, VRWIDGET_PLANE, VRWIDGET_WEB, VRWIDGET_WEBPAGE, VRWIDGET_MAP, VRWIDGET_COMPASS, VRWIDGET_IMU, VRWIDGET_SPHERE } VRWIDGET_TYPE; // Default used for the default render #define VRWIDGET_DEFAULT_X 0.0f #define VRWIDGET_DEFAULT_Y 0.0f #define VRWIDGET_DEFAULT_Z -20.0f #define VRWIDGET_DEFAULT_RADIUS 1.0f #define VRWIDGET_DEFAULT_WIDTH 5.0f #define VRWIDGET_DEFAULT_HEIGHT 5.0f #define VRWIDGET_DEFAULT_DEPTH 5.0f #define VRWIDGET_BOUNDBOX_BORDER 0.1f typedef enum { VRWidgetRotationXYZ = 0, VRWidgetRotationXZY, VRWidgetRotationYXZ, VRWidgetRotationYZX, VRWidgetRotationZXY, VRWidgetRotationZYX } VRWidgetRotationOrder; class VRWidget : public QObject { Q_OBJECT public: VRWidget(QObject *parent, VRWIDGET_TYPE = VRWIDGET_WIDGET); ~VRWidget(); // VRWidgetInit() called to init OpenGL stuff - must be provided virtual void VRWidgetInit() = 0; // VRWidgetRender() called to render the widget - must be provided virtual void VRWidgetRender() = 0; // function to render the widget // ARHandleSingleClick() can be overriden to process single click events // virtual void ARHandleSingleClick(int x, int y, Qt::MouseButton button) virtual void ARHandleSingleClick(int , int , Qt::MouseButton ) {}; // ARHandleDoubleClick() can be overriden to process double click events // virtual void ARHandleDoubleClick(int x, int y, Qt::MouseButton button) virtual void ARHandleDoubleClick(int , int , Qt::MouseButton ) {}; // ARCustomSelectBox() allows a widget to draw a custom select box virtual void ARCustomSelectBox() {}; // enableSelectBox() controls whether select box is drawn inline void enableSelectBox(bool enable) {m_drawSelectBox = enable;}; // setIcon() sets the filename of the widget icon inline void setIcon(const char *icon) { m_icon = icon; }; // getIcon() sets the filename of the widget icon inline const QString& getIcon() { return m_icon; }; // getWidgetType() returns the type of the widget inline VRWIDGET_TYPE getWidgetType() { return m_widgetType; }; // setCenter() sets the position of the center of the object virtual void setCenter(float x, float y, float z, bool unconditional = false); virtual void setCenter(const QVector3D center, bool unconditional = false); // getCenter() sets the position of the center of the object virtual void getCenter(float& x, float& y, float& z); virtual void getCenter(QVector3D& center); // setSize() sets the width and height and depth of the object virtual void setSize(float width, float height, float depth); // getSize() gets the width and height and depth of the object virtual void getSize(float& width, float& height, float& depth); // moveCenter() moves the center of the widget virtual void moveCenter(float deltaX, float deltaY, float deltaZ, bool unconditional = false); virtual void moveCenter(const QVector3D delta, bool unconditional = false); // moveCenterTransform() moves the center of the widget using intersections // with the bounding box so as to maintain a constant intersection point virtual void moveCenterTransform(const QVector3D& intersection, const QVector3D& newIntersection); // moveTowardOrigin() moves object towards the eye point origin virtual void moveTowardOrigin(float distance); // setMoveMask() allows individual move directions to be controlled virtual void setMoveMask(bool enableX, bool enableY, bool enableZ); // setRotationOrder() sets the order of rotation virtual void setRotationOrder(VRWidgetRotationOrder order); // setRotation() sets the orientation of the object virtual void setRotation(float x, float y, float z, bool unconditional = false); virtual void setRotation(const QVector3D rotation, bool unconditional = false); // getRotation() sets the orientation of the object virtual void getRotation(float& x, float& y, float& z); virtual void getRotation(QVector3D& rotation); // moveRotation() changes the rotation angles of the widget virtual void moveRotation(float deltaPhi, float deltaTheta, float deltaPsi, bool unconditional = false); virtual void moveRotation(const QVector3D delta, bool unconditional = false); // setRotationMask() allows individual rotations to be controlled virtual void setRotationMask(bool enableX, bool enableY, bool enableZ); // setSelected() tell the widget to be in selected state if true inline void setSelected(bool selected) {m_selected = selected;} // selected() returns true if the widget is selected inline bool selected() { return m_selected; } // boundingBoxIntersection() returns the point where the ray defined by points ray0 and ray1 // intersect with the bounding box and which rectangle index. If it doesn't, the function returns false bool boundingBoxIntersection(QVector3D& intersection, int& rectIndex, const QVector3D& ray0, const QVector3D& ray1); // boundingBoxRectIntersection() looks for an intersection with a specific // rectangle in the bounding box bool boundingRectIntersection(QVector3D& intersection, const int rectIndex, const QVector3D& ray0, const QVector3D& ray1); protected: // startWidgetRender() is called at start of widget's render function void startWidgetRender(); // endWidgetRender() is called at end of widget's render function void endWidgetRender(); // startCompositeRender() is called at start of composite component render sequence // parameters are offset from widget center for this composite component void startCompositeRender(const QVector3D& posOffset, const QVector3D& angleOffset); void startCompositeRender(float offsetX = 0.0f, float offsetY = 0.0f, float offsetZ = 0.0f, float rotOffsetX = 0.0f, float rotOffsetY = 0.0f, float rotOffsetZ = 0.0f); // endCompositeRender() is called at end of a composite component render sequence void endCompositeRender(); // startComponentRender() is called at start of component render sequence // parameters are offset from widget center for this component void startComponentRender(const QVector3D& posOffset, const QVector3D& angleOffset); void startComponentRender(float offsetX = 0.0f, float offsetY = 0.0f, float offsetZ = 0.0f, float rotOffsetX = 0.0f, float rotOffsetY = 0.0f, float rotOffsetZ = 0.0f); // endComponentRender() is called at end of a component render sequence void endComponentRender(const QVector3D& boundMinus, const QVector3D& boundPlus); // pushModelMatrix() push the current globalModelMatrix on the stack void pushModelMatrix(); // popModelMatrix() pops the current globalModelMatrix from the stack void popModelMatrix(); // pushOffsetMatrix() push the current offset matrix on the stack void pushOffsetMatrix(); // popOffsetMatrix() pops the current offset matrix from the stack void popOffsetMatrix(); QVector3D m_center; QVector3D m_rotation; float m_width; float m_height; float m_depth; private: void updateViewportBox(); // update the viewport bounding box void conditionalMove(const QVector3D& newCenter, bool unconditional); // performs move if allowed void conditionalRotate(const QVector3D& newRotation, bool unconditional); // performs rotate if allowed bool m_enableMoveX; bool m_enableMoveY; bool m_enableMoveZ; bool m_enableRotX; bool m_enableRotY; bool m_enableRotZ; bool m_saveState; // true if save settings, false if not void setModelMatrix(); // sets the model matrix for the widget bool m_drawSelectBox; // true if draw select box automatically QString m_icon; // the icon filename VRWIDGET_TYPE m_widgetType; // type of widget QVector3D m_boundMinus; // lowest in each coord for box in widget coords QVector3D m_boundPlus; // highest in each coord for box in widget coords QVector3D m_viewportBoundBox[8]; // the bounding box in viewport coords QStack m_modelMatrixStack; // the model matrix stack QStack m_offsetMatrixStack; // the offset matrix stack QMatrix4x4 m_offsetMatrix; // the current offset matrix QMatrix4x4 m_modelMatrix; // the current model matrix QMatrix4x4 m_inverseModelMatrix; // the reverse transformation bool m_selected; // if in selected state VRWidgetRotationOrder m_rotationOrder; }; #endif // VRWIDGET_H rtimulib-7.2.1/Linux/RTIMULibGL/VRWidgetLib/VRWidgetLib.pri000066400000000000000000000026601254201074400232040ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014, richards-tech #// #// Permission 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. INCLUDEPATH += $$PWD DEPENDPATH += $$PWD HEADERS += $$PWD/VRWidget.h \ $$PWD/VRIMUWidget.h SOURCES += $$PWD/VRWidget.cpp \ $$PWD/VRIMUWidget.cpp RESOURCES += $$PWD/VRWidgetLib.qrc rtimulib-7.2.1/Linux/RTIMULibGL/VRWidgetLib/VRWidgetLib.qrc000066400000000000000000000004151254201074400231730ustar00rootroot00000000000000 Images/Compass.png Images/CompassNeedle.png Images/RedShade.png Images/BlueShade.png Images/GreenShade.png rtimulib-7.2.1/Linux/RTIMULibGL/VRWidgetLib/VRWidgetLib.sln000066400000000000000000000015441254201074400232060ustar00rootroot00000000000000 Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VRWidgetLib", "VRWidgetLib.vcxproj", "{183C43F1-DC99-467E-8364-AA7E69E8E1A8}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 Release|Win32 = Release|Win32 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {183C43F1-DC99-467E-8364-AA7E69E8E1A8}.Debug|Win32.ActiveCfg = Debug|Win32 {183C43F1-DC99-467E-8364-AA7E69E8E1A8}.Debug|Win32.Build.0 = Debug|Win32 {183C43F1-DC99-467E-8364-AA7E69E8E1A8}.Release|Win32.ActiveCfg = Release|Win32 {183C43F1-DC99-467E-8364-AA7E69E8E1A8}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal rtimulib-7.2.1/Linux/RTIMULibGL/VRWidgetLib/VRWidgetLib.vcxproj000066400000000000000000000275371254201074400241170ustar00rootroot00000000000000 Debug Win32 Release Win32 true true true true $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing VRIMUWidget.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_OPENGL_LIB -DVRWIDGETLIB_LIB "-I.\..\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtOpenGL" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing VRIMUWidget.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_OPENGL_LIB -DVRWIDGETLIB_LIB "-I.\..\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtOpenGL" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing VRWidget.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_OPENGL_LIB -DVRWIDGETLIB_LIB "-I.\..\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtOpenGL" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing VRWidget.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_OPENGL_LIB -DVRWIDGETLIB_LIB "-I.\..\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtOpenGL" Document %(FullPath);.\Images\Compass.png;.\Images\CompassNeedle.png;.\Images\RedShade.png;.\Images\BlueShade.png;.\Images\GreenShade.png;%(AdditionalInputs) Rcc%27ing %(Identity)... .\GeneratedFiles\qrc_%(Filename).cpp;%(Outputs) "$(QTDIR)\bin\rcc.exe" -name "%(Filename)" -no-compress "%(FullPath)" -o .\GeneratedFiles\qrc_%(Filename).cpp %(FullPath);.\Images\Compass.png;.\Images\CompassNeedle.png;.\Images\RedShade.png;.\Images\BlueShade.png;.\Images\GreenShade.png;%(AdditionalInputs) Rcc%27ing %(Identity)... .\GeneratedFiles\qrc_%(Filename).cpp;%(Outputs) "$(QTDIR)\bin\rcc.exe" -name "%(Filename)" -no-compress "%(FullPath)" -o .\GeneratedFiles\qrc_%(Filename).cpp true true true true true true true true true true {183C43F1-DC99-467E-8364-AA7E69E8E1A8} Qt4VSv1.0 StaticLibrary StaticLibrary <_ProjectFileVersion>10.0.40219.1 $(SolutionDir)$(Platform)\$(Configuration)\ $(SolutionDir)$(Platform)\$(Configuration)\ UNICODE;WIN32;WIN64;QT_DLL;QT_CORE_LIB;QT_OPENGL_LIB;VRWIDGETLIB_LIB;%(PreprocessorDefinitions) ..\QtGLLib;.\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtOpenGL;%(AdditionalIncludeDirectories) Disabled ProgramDatabase MultiThreadedDebugDLL false $(OutDir)\$(ProjectName).lib $(QTDIR)\lib;%(AdditionalLibraryDirectories) UNICODE;WIN32;WIN64;QT_DLL;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_OPENGL_LIB;VRWIDGETLIB_LIB;%(PreprocessorDefinitions) ..\QtGLLib;.\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtOpenGL;%(AdditionalIncludeDirectories) MultiThreadedDLL false $(OutDir)\$(ProjectName).lib $(QTDIR)\lib;%(AdditionalLibraryDirectories) rtimulib-7.2.1/Linux/RTIMULibGL/VRWidgetLib/VRWidgetLib.vcxproj.filters000066400000000000000000000062501254201074400255530ustar00rootroot00000000000000 {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;cxx;c;def {93995380-89BD-4b04-88EB-625FBE52EBFB} h {99349809-55BA-4b9d-BF79-8FDBB0286EB3} ui {D9D6E242-F8AF-46E4-B9FD-80ECBC20BA3E} qrc;* false {71ED8ED8-ACB9-4CE9-BBE1-E00B30144E11} moc;h;cpp False {e723335c-a449-4511-a0d2-9f617a161185} cpp;moc False {652d0b73-0946-4070-967c-d9ff11ba96ac} cpp;moc False Source Files Source Files Generated Files\Debug Generated Files\Release Generated Files\Debug Generated Files\Release Generated Files Header Files Header Files Form Files Resource Files Resource Files Resource Files Resource Files Resource Files rtimulib-7.2.1/Linux/RTIMULibvrpn/000077500000000000000000000000001254201074400167035ustar00rootroot00000000000000rtimulib-7.2.1/Linux/RTIMULibvrpn/Makefile000066400000000000000000000121071254201074400203440ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014, richards-tech #// #// Permission 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. # Compiler, tools and options RTIMULIBPATH = ../../RTIMULib CC = gcc CXX = g++ DEFINES = CFLAGS = -pipe -O2 -Wall -W $(DEFINES) CXXFLAGS = -pipe -O2 -Wall -W $(DEFINES) INCPATH = -I. -I$(RTIMULIBPATH) LINK = g++ LFLAGS = -Wl,-O1 LIBS = -L/usr/lib/arm-linux-gnueabihf -lvrpn -lrt -lpthread COPY = cp -f COPY_FILE = $(COPY) COPY_DIR = $(COPY) -r STRIP = strip INSTALL_FILE = install -m 644 -p INSTALL_DIR = $(COPY_DIR) INSTALL_PROGRAM = install -m 755 -p DEL_FILE = rm -f SYMLINK = ln -f -s DEL_DIR = rmdir MOVE = mv -f CHK_DIR_EXISTS = test -d MKDIR = mkdir -p # Output directory OBJECTS_DIR = objects/ # Files DEPS = $(RTIMULIBPATH)/RTMath.h \ $(RTIMULIBPATH)/RTIMULib.h \ $(RTIMULIBPATH)/RTIMULibDefs.h \ $(RTIMULIBPATH)/RTIMUHal.h \ $(RTIMULIBPATH)/RTFusion.h \ $(RTIMULIBPATH)/RTFusionKalman4.h \ $(RTIMULIBPATH)/RTFusionRTQF.h \ $(RTIMULIBPATH)/RTIMUSettings.h \ $(RTIMULIBPATH)/RTIMUAccelCal.h \ $(RTIMULIBPATH)/RTIMUMagCal.h \ $(RTIMULIBPATH)/RTIMUCalDefs.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMU.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUNull.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUMPU9150.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUMPU9250.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUGD20HM303D.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUGD20M303DLHC.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUGD20HM303DLHC.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMULSM9DS0.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUBMX055.h \ $(RTIMULIBPATH)/IMUDrivers/RTIMUBNO055.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressure.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressureBMP180.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressureLPS25H.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressureMS5611.h \ $(RTIMULIBPATH)/IMUDrivers/RTPressureMS5637.h OBJECTS = objects/RTIMULibvrpn.o \ objects/vrpnServer.o \ objects/RTMath.o \ objects/RTIMUHal.o \ objects/RTFusion.o \ objects/RTFusionKalman4.o \ objects/RTFusionRTQF.o \ objects/RTIMUSettings.o \ objects/RTIMUAccelCal.o \ objects/RTIMUMagCal.o \ objects/RTIMU.o \ objects/RTIMUNull.o \ objects/RTIMUMPU9150.o \ objects/RTIMUMPU9250.o \ objects/RTIMUGD20HM303D.o \ objects/RTIMUGD20M303DLHC.o \ objects/RTIMUGD20HM303DLHC.o \ objects/RTIMULSM9DS0.o \ objects/RTIMUBMX055.o \ objects/RTIMUBNO055.o \ objects/RTPressure.o \ objects/RTPressureBMP180.o \ objects/RTPressureLPS25H.o \ objects/RTPressureMS5611.o \ objects/RTPressureMS5637.o MAKE_TARGET = RTIMULibvrpn DESTDIR = Output/ TARGET = Output/$(MAKE_TARGET) # Build rules $(TARGET): $(OBJECTS) @$(CHK_DIR_EXISTS) Output/ || $(MKDIR) Output/ $(LINK) $(LFLAGS) -o $(TARGET) $(OBJECTS) $(LIBS) clean: -$(DEL_FILE) $(OBJECTS) -$(DEL_FILE) *~ core *.core # Compile $(OBJECTS_DIR)%.o : $(RTIMULIBPATH)/%.cpp $(DEPS) @$(CHK_DIR_EXISTS) objects/ || $(MKDIR) objects/ $(CXX) -c -o $@ $< $(CFLAGS) $(INCPATH) $(OBJECTS_DIR)%.o : $(RTIMULIBPATH)/IMUDrivers/%.cpp $(DEPS) @$(CHK_DIR_EXISTS) objects/ || $(MKDIR) objects/ $(CXX) -c -o $@ $< $(CFLAGS) $(INCPATH) $(OBJECTS_DIR)RTIMULibvrpn.o : RTIMULibvrpn.cpp $(DEPS) @$(CHK_DIR_EXISTS) objects/ || $(MKDIR) objects/ $(CXX) -c -o $@ RTIMULibvrpn.cpp $(CFLAGS) $(INCPATH) $(OBJECTS_DIR)vrpnServer.o : vrpnServer.cpp $(DEPS) @$(CHK_DIR_EXISTS) objects/ || $(MKDIR) objects/ $(CXX) -c -o $@ vrpnServer.cpp $(CFLAGS) $(INCPATH) # Install install_target: FORCE @$(CHK_DIR_EXISTS) $(INSTALL_ROOT)/usr/local/bin/ || $(MKDIR) $(INSTALL_ROOT)/usr/local/bin/ -$(INSTALL_PROGRAM) "Output/$(MAKE_TARGET)" "$(INSTALL_ROOT)/usr/local/bin/$(MAKE_TARGET)" -$(STRIP) "$(INSTALL_ROOT)/usr/local/bin/$(MAKE_TARGET)" uninstall_target: FORCE -$(DEL_FILE) "$(INSTALL_ROOT)/usr/local/bin/$(MAKE_TARGET)" install: install_target FORCE uninstall: uninstall_target FORCE FORCE: rtimulib-7.2.1/Linux/RTIMULibvrpn/README.md000066400000000000000000000021031254201074400201560ustar00rootroot00000000000000# RTIMULibvrpn - RTIMULib with vrpn integration RTIMULibvrpn shows how to integrate RTIMULib with vrpn (http://www.cs.unc.edu/Research/vrpn/). The integration code was generously supplied by Lucas Matias Angarola (lucasangarola@gmail.com). ### Setting up vrpn Download vrpn from: http://www.cs.unc.edu/Research/vrpn/downloads/ Unzip the file into some directory. Navigate to that directory and enter: mkdir build cd build cmake .. make sudo make install This will create and install the vrpn libraries. ### Build and run RTIMULibvrpn Navigate to the RTIMULibvrpn directory and enter: make sudo make install RTIMULibvrpn This will start the vrpn and RTIMULib. To view the output, enter: .//client_src/vrpn_print_devices Tracker0@ where is the directory where vrpn was built and is the address of the system running RTIMULibvrpn. If this is run on the same machine, then: .//client_src/vrpn_print_devices Tracker0@localhost works fine. rtimulib-7.2.1/Linux/RTIMULibvrpn/RTIMULibvrpn.cpp000066400000000000000000000062711254201074400216520ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "RTIMULib.h" #include "vrpnServer.h" vrpnServer *vrpn; int main() { int sampleCount = 0; int sampleRate = 0; uint64_t rateTimer; uint64_t displayTimer; uint64_t now; // using RTIMULib here allows it to use the .ini file generated by RTIMULibDemo. RTIMUSettings *settings = new RTIMUSettings("RTIMULib"); RTIMU *imu = RTIMU::createIMU(settings); if ((imu == NULL) || (imu->IMUType() == RTIMU_TYPE_NULL)) { printf("No IMU found\n"); exit(1); } // This is an opportunity to manually override any settings before the call IMUInit // set up IMU imu->IMUInit(); // set up for rate timer rateTimer = displayTimer = RTMath::currentUSecsSinceEpoch(); // create the vrpn servee vrpn = new vrpnServer(); // now just process data while (1) { // poll at the rate recommended by the IMU usleep(imu->IMUGetPollInterval() * 1000); // call the vrpn main loop vrpn->mainloop(); // this function can be placed inside a new thread, which should run faster than // the loop that calls the update_tracking function. while (imu->IMURead()) { RTIMU_DATA imuData = imu->getIMUData(); vrpn->serverTracker->update_tracking(RTVector3(), imuData.fusionQPose); sampleCount++; now = RTMath::currentUSecsSinceEpoch(); // display 10 times per second if ((now - displayTimer) > 100000) { printf("Sample rate %d: %s\r", sampleRate, RTMath::displayDegrees("", imuData.fusionPose)); fflush(stdout); displayTimer = now; } // update rate every second if ((now - rateTimer) > 1000000) { sampleRate = sampleCount; sampleCount = 0; rateTimer = now; } } } } rtimulib-7.2.1/Linux/RTIMULibvrpn/vrpnServer.cpp000066400000000000000000000103721254201074400215660ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // Original code by Lucas Matias Angarola (lucasangarola@gmail.com): /** ****************************************************************************** * @file vrpnServer.c * @author Lucas Matías Angarola * @version V1.0.0 * @date 23-May-2014 * @brief ****************************************************************************** * @copyright * * Permission 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. * * ****************************************************************************** */ #include "vrpnServer.h" #include void trackerVrpnServer::mainloop() { vrpn_gettimeofday(&_timestamp, NULL); vrpn_Tracker::timestamp = _timestamp; char msgbuf[1000]; d_sensor = 0; int len = vrpn_Tracker::encode_to(msgbuf); d_connection->pack_message(len, _timestamp, position_m_id, d_sender_id, msgbuf,vrpn_CONNECTION_LOW_LATENCY); server_mainloop(); } void trackerVrpnServer::update_tracking(const RTVector3& position, const RTQuaternion& quaternion) { //Quaternions update d_quat[0] = quaternion.x(); d_quat[1] = quaternion.y(); d_quat[2] = quaternion.z(); d_quat[3] = quaternion.scalar(); //Location update pos[0] = position.x(); pos[1] = position.y(); pos[2] = position.z(); } vrpnServer::vrpnServer() { m_Connection = NULL; serverTracker = NULL; m_Connection = new vrpn_Connection_IP(); if(m_Connection == NULL) { std::cout << "vrpnServer() Error: vrpn_Connection_IP could not be created" << std::endl; return; } serverTracker = new trackerVrpnServer(this->m_Connection); if(serverTracker == NULL) { std::cout << "vrpnServer() Error: trackerVrpnServer could not be created" << std::endl; delete m_Connection; return; } } void vrpnServer::mainloop() { serverTracker->mainloop(); m_Connection->mainloop(); } vrpnServer::~vrpnServer() { if(serverTracker != NULL) delete serverTracker; if(m_Connection != NULL) delete m_Connection; } rtimulib-7.2.1/Linux/RTIMULibvrpn/vrpnServer.h000066400000000000000000000072401254201074400212330ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // Original code by Lucas Matias Angarola (lucasangarola@gmail.com): /** ****************************************************************************** * @file vrpnServer.h * @author Lucas Matías Angarola * @version V1.0.0 * @date 23-May-2014 * @brief ****************************************************************************** * @copyright * * Permission 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. * * ****************************************************************************** */ #ifndef _VRPNSERVER_H #define _VRPNSERVER_H #include "vrpn_Connection.h" #include "vrpn_Text.h" #include "vrpn_Tracker.h" #include "RTIMULib.h" class trackerVrpnServer : public vrpn_Tracker { public: // Tracker0 is the name of the device. Any string could be used here to name the device. // You can connect through a VRPN Client using the followign address: // Tracker0@Device_IP_number or Tracker0@localhost. trackerVrpnServer( vrpn_Connection *c = 0 ) : vrpn_Tracker( "Tracker0", c ){}; virtual ~trackerVrpnServer() {}; virtual void mainloop(); void update_tracking(const RTVector3& position, const RTQuaternion& quaternion); protected: struct timeval _timestamp; double value; }; class vrpnServer { public: vrpnServer(); ~vrpnServer(); void mainloop(); trackerVrpnServer *serverTracker; protected: vrpn_Connection_IP *m_Connection; }; #endif // _VRPNSERVER_H rtimulib-7.2.1/Linux/python/000077500000000000000000000000001254201074400157675ustar00rootroot00000000000000rtimulib-7.2.1/Linux/python/PyRTHumidity.h000066400000000000000000000030021254201074400205060ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, avishorp // Copyright (c) 2014, richards-tech // // Permission 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. // Python binding for RTIMULib #include #include "structmember.h" #include "RTIMULib.h" // RTHumidity Type struct RTIMU_RTHumidity { PyObject_HEAD RTHumidity* val; }; // Create the RTHumidity type int RTIMU_RTHumidity_create(PyObject* module); rtimulib-7.2.1/Linux/python/PyRTIMU.cpp000066400000000000000000000057671254201074400177230ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech // Copyright (c) 2014, avishorp // // Permission 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. // RTIUMULib Python Module main file //////////////////////////////////// #include "PyRTIMU.h" #include "PyRTPressure.h" #include "PyRTHumidity.h" // RTIMU Method Table ///////////////////// static PyMethodDef RTIMUMethods[] = { {NULL, NULL, 0, NULL} }; // (from PyRTIMUSettings.cpp) int RTIMU_Settings_ready(); struct module_state { PyObject *error; }; #if PY_MAJOR_VERSION >= 3 #define GETSTATE(m) ((struct module_state*)PyModule_GetState(m)) #else #define GETSTATE(m) (&_state) static struct module_state _state; #endif #if PY_MAJOR_VERSION >= 3 static int RTIMUTraverse(PyObject *m, visitproc visit, void *arg) { Py_VISIT(GETSTATE(m)->error); return 0; } static int RTIMUClear(PyObject *m) { Py_CLEAR(GETSTATE(m)->error); return 0; } static struct PyModuleDef moduledef = { PyModuleDef_HEAD_INIT, "RTIMU", NULL, sizeof(struct module_state), RTIMUMethods, NULL, RTIMUTraverse, RTIMUClear, NULL }; PyMODINIT_FUNC PyInit_RTIMU(void) #else PyMODINIT_FUNC initRTIMU() #endif { // Initialize the module PyObject* m; #if PY_MAJOR_VERSION >= 3 m = PyModule_Create(&moduledef); #else m = Py_InitModule("RTIMU", RTIMUMethods); #endif // Insert types #if PY_MAJOR_VERSION >= 3 if (RTIMU_Settings_create(m) < 0) return 0; if (RTIMU_RTIMU_create(m) < 0) return 0; if (RTIMU_RTPressure_create(m) < 0) return 0; if (RTIMU_RTHumidity_create(m) < 0) return 0; return m; #else if (RTIMU_Settings_create(m) < 0) return; if (RTIMU_RTIMU_create(m) < 0) return; if (RTIMU_RTPressure_create(m) < 0) return; if (RTIMU_RTHumidity_create(m) < 0) return; return; #endif } rtimulib-7.2.1/Linux/python/PyRTIMU.h000066400000000000000000000034001254201074400173460ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech // Copyright (c) 2014, avishorp // // Permission 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. // Python binding for RTIMULib #include #include "structmember.h" #include "RTIMULib.h" // RTIMUSettings Type struct RTIMU_Settings { PyObject_HEAD RTIMUSettings* val; }; // RTIMU Type struct RTIMU_RTIMU { PyObject_HEAD RTIMU* val; }; // Create the RTIMU_Settings type int RTIMU_Settings_create(PyObject* module); // Check if the given object is of RTIMU_Settings type bool RTIMU_Settings_typecheck(PyObject* obj); // Create the RTIMU type int RTIMU_RTIMU_create(PyObject* module); rtimulib-7.2.1/Linux/python/PyRTIMU_RTHumidity.cpp000066400000000000000000000160371254201074400220350ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech // Copyright (c) 2014, avishorp // // Permission 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. // RTIUMULib Python Module - RTHumidity type implementation /////////////////////////////////////////////////////// #include "PyRTHumidity.h" #include "PyRTIMU.h" // Forwards /////////// static void RTIMU_RTHumidity_dealloc(RTIMU_RTHumidity* self); static PyObject* RTIMU_RTHumidity_new(PyTypeObject *type, PyObject *args, PyObject *kwds); static int RTIMU_RTHumidity_init(RTIMU_RTHumidity *self, PyObject *args, PyObject *kwds); // The RTIMU_Settings struct //////////////////////////// static PyMethodDef RTIMU_RTHumidity_methods[] = { //////// humidityName {"humidityName", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { if (((RTIMU_RTHumidity*)self)->val == NULL) #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString("none"); #else return PyString_FromString("none"); #endif else #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(((RTIMU_RTHumidity*)self)->val->humidityName()); #else return PyString_FromString(((RTIMU_RTHumidity*)self)->val->humidityName()); #endif }), METH_NOARGS, "Get the name of the humidity sensor" }, //////// humidityType {"humidityType", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { if (((RTIMU_RTHumidity*)self)->val == NULL) #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(0); #else return PyInt_FromLong(0); #endif else #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(((RTIMU_RTHumidity*)self)->val->humidityType()); #else return PyInt_FromLong(((RTIMU_RTHumidity*)self)->val->humidityType()); #endif }), METH_NOARGS, "Get the type code of the humidity sensor" }, //////// presureInit {"humidityInit", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { if (((RTIMU_RTHumidity*)self)->val == NULL) return PyBool_FromLong(0); else return PyBool_FromLong(((RTIMU_RTHumidity*)self)->val->humidityInit()); }), METH_NOARGS, "Set up the humidity sensor" }, //////// humidityRead {"humidityRead", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { RTIMU_DATA data; if (((RTIMU_RTHumidity*)self)->val == NULL) { data.temperatureValid = data.humidityValid = false; data.temperature = 0; data.humidity = 0; } else { ((RTIMU_RTHumidity*)self)->val->humidityRead(data); } return Py_BuildValue("idid", data.humidityValid, data.humidity, data.temperatureValid, data.temperature); }), METH_NOARGS, "Get current values" }, { NULL } }; static PyTypeObject RTIMU_RTHumidity_type = { #if PY_MAJOR_VERSION >= 3 PyVarObject_HEAD_INIT(NULL, 0) #else PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ #endif "RTIMU.RTHumidity", /*tp_name*/ sizeof(RTIMU_RTHumidity), /*tp_basicsize*/ 0, /*tp_itemsize*/ (destructor)RTIMU_RTHumidity_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ "RTIMU.RTHumidity object", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ RTIMU_RTHumidity_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)RTIMU_RTHumidity_init, /* tp_init */ 0, /* tp_alloc */ RTIMU_RTHumidity_new, /* tp_new */ }; static void RTIMU_RTHumidity_dealloc(RTIMU_RTHumidity* self) { if (self->val != NULL) delete self->val; } static PyObject* RTIMU_RTHumidity_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { RTIMU_RTHumidity* self; // Allocate the object memory self = (RTIMU_RTHumidity*)type->tp_alloc(type, 0); if (self != NULL) { // Set the value field to NULL. The RTHumidity Settings object // creation is deferred to init self->val = NULL; } return (PyObject*)self; } static int RTIMU_RTHumidity_init(RTIMU_RTHumidity *self, PyObject *args, PyObject *kwds) { PyObject* settings; // The user should pass settings pointer as an argument if (!PyArg_ParseTuple(args, "O", &settings)) return -1; // Create an RTHumidity object self->val = RTHumidity::createHumidity(((RTIMU_Settings*)settings)->val); return 0; } int RTIMU_RTHumidity_create(PyObject* module) { // Prepare the type structure if (PyType_Ready(&RTIMU_RTHumidity_type) < 0) return -1; // Insert it into the module Py_INCREF(&RTIMU_RTHumidity_type); PyModule_AddObject(module, "RTHumidity", (PyObject*)&RTIMU_RTHumidity_type); return 0; } rtimulib-7.2.1/Linux/python/PyRTIMU_RTIMU.cpp000066400000000000000000000360541254201074400206740ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech // Copyright (c) 2014, avishorp // // Permission 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. // RTIUMULib Python Module - RTIMU type implementation /////////////////////////////////////////////////////// #include "PyRTIMU.h" // Forwards /////////// static void RTIMU_RTIMU_dealloc(RTIMU_RTIMU* self); static PyObject* RTIMU_RTIMU_new(PyTypeObject *type, PyObject *args, PyObject *kwds); static int RTIMU_RTIMU_init(RTIMU_RTIMU *self, PyObject *args, PyObject *kwds); // The RTIMU_Settings struct //////////////////////////// static PyMethodDef RTIMU_RTIMU_methods[] = { //////// IMUName {"IMUName", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { if (((RTIMU_RTIMU*)self)->val == NULL) #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString("none"); #else return PyString_FromString("none"); #endif else #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(((RTIMU_RTIMU*)self)->val->IMUName()); #else return PyString_FromString(((RTIMU_RTIMU*)self)->val->IMUName()); #endif }), METH_NOARGS, "Get the name of the IMU" }, //////// IMUType {"IMUType", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { if (((RTIMU_RTIMU*)self)->val == NULL) #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(0); #else return PyInt_FromLong(0); #endif else #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(((RTIMU_RTIMU*)self)->val->IMUType()); #else return PyInt_FromLong(((RTIMU_RTIMU*)self)->val->IMUType()); #endif }), METH_NOARGS, "Get the type code of the IMU" }, //////// IMUInit {"IMUInit", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { if (((RTIMU_RTIMU*)self)->val == NULL) return PyBool_FromLong(0); else return PyBool_FromLong(((RTIMU_RTIMU*)self)->val->IMUInit()); }), METH_NOARGS, "Set up the IMU" }, //////// IMUGetPollInterval {"IMUGetPollInterval", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(((RTIMU_RTIMU*)self)->val->IMUGetPollInterval()); #else return PyInt_FromLong(((RTIMU_RTIMU*)self)->val->IMUGetPollInterval()); #endif }), METH_NOARGS, "Get the recommended poll interval in mS" }, //////// IMURead {"IMURead", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { return PyBool_FromLong(((RTIMU_RTIMU*)self)->val->IMURead()); }), METH_NOARGS, "Get a sample" }, //////// IMUGyroBiasValid {"IMUGyroBiasValid", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { return PyBool_FromLong(((RTIMU_RTIMU*)self)->val->IMUGyroBiasValid()); }), METH_NOARGS, "Return true if valid bias" }, //////// getCompassCalibrationValid {"getCompassCalibrationValid", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { return PyBool_FromLong(((RTIMU_RTIMU*)self)->val->getCompassCalibrationValid()); }), METH_NOARGS, "Return true if the compass is using min/max calibration" }, //////// getCompassCalibrationEllipsoidValid {"getCompassCalibrationEllipsoidValid", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { return PyBool_FromLong(((RTIMU_RTIMU*)self)->val->getCompassCalibrationEllipsoidValid()); }), METH_NOARGS, "Return true if the compass is using ellipsoid calibration" }, //////// getAccelCalibrationValid {"getAccelCalibrationValid", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { return PyBool_FromLong(((RTIMU_RTIMU*)self)->val->getAccelCalibrationValid()); }), METH_NOARGS, "Return true if the compass is using ellipsoid calibration" }, //////// IMUGyroBiasValid {"IMUGyroBiasValid", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { return PyBool_FromLong(((RTIMU_RTIMU*)self)->val->IMUGyroBiasValid()); }), METH_NOARGS, "Return true if the compass is using ellipsoid calibration" }, //////// resetFusion {"resetFusion", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { ((RTIMU_RTIMU*)self)->val->resetFusion(); Py_RETURN_NONE; }), METH_NOARGS, "Return true if valid bias" }, //////// setSlerpPower {"setSlerpPower", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { double power; PyArg_ParseTuple(args, "d", &power); ((RTIMU_RTIMU*)self)->val->setSlerpPower(power); Py_RETURN_NONE; }), METH_VARARGS, "Enable or disable Gyro reading" }, //////// setGyroEnable {"setGyroEnable", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { int en; PyArg_ParseTuple(args, "i", &en); ((RTIMU_RTIMU*)self)->val->setGyroEnable(en > 0); Py_RETURN_NONE; }), METH_VARARGS, "Enable or disable Gyro reading" }, //////// setAccelEnable {"setAccelEnable", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { int en; PyArg_ParseTuple(args, "i", &en); ((RTIMU_RTIMU*)self)->val->setAccelEnable(en > 0); Py_RETURN_NONE; }), METH_VARARGS, "Enable or disable the Accelerometer reading" }, //////// setCompassEnable {"setCompassEnable", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { int en; PyArg_ParseTuple(args, "i", &en); ((RTIMU_RTIMU*)self)->val->setCompassEnable(en > 0); Py_RETURN_NONE; }), METH_VARARGS, "Enable or disable the Compass reading" }, //////// getIMUData {"getIMUData", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { const RTIMU_DATA& data = ((RTIMU_RTIMU*)self)->val->getIMUData(); return Py_BuildValue("{s:K,s:O,s:(d,d,d),s:O,s:(d,d,d,d),s:O,s:(d,d,d),s:O,s:(d,d,d),s:O,s:(d,d,d),s:O,s:d,s:O,s:d,s:O,s:d}", "timestamp", data.timestamp, "fusionPoseValid", PyBool_FromLong(data.fusionPoseValid), "fusionPose", data.fusionPose.x(), data.fusionPose.y(), data.fusionPose.z(), "fusionQPoseValid", PyBool_FromLong(data.fusionQPoseValid), "fusionQPose", data.fusionQPose.scalar(), data.fusionQPose.x(), data.fusionQPose.y(), data.fusionQPose.z(), "gyroValid", PyBool_FromLong(data.gyroValid), "gyro", data.gyro.x(), data.gyro.y(), data.gyro.z(), "accelValid", PyBool_FromLong(data.accelValid), "accel", data.accel.x(), data.accel.y(), data.accel.z(), "compassValid", PyBool_FromLong(data.compassValid), "compass", data.compass.x(), data.compass.y(), data.compass.z(), "pressureValid", PyBool_FromLong(data.pressureValid), "pressure", data.pressure, "temperatureValid", PyBool_FromLong(data.temperatureValid), "temperature", data.temperature, "humidityValid", PyBool_FromLong(data.humidityValid), "humidity", data.humidity); }), METH_NOARGS, "Return true if valid bias" }, //////// getMeasuredPose {"getMeasuredPose", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { const RTVector3& r = ((RTIMU_RTIMU*)self)->val->getMeasuredPose(); return Py_BuildValue("(d,d,d)", r.x(), r.y(), r.z()); }), METH_NOARGS, "Return the measured pose" }, //////// getMeasuredQPose {"getMeasuredQPose", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { const RTQuaternion& r = ((RTIMU_RTIMU*)self)->val->getMeasuredQPose(); return Py_BuildValue("(d,d,d,d)", r.scalar(), r.x(), r.y(), r.z()); }), METH_NOARGS, "Return the measured QPose" }, //////// setCompassCalibrationMode {"setCompassCalibrationMode", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { int en; PyArg_ParseTuple(args, "i", &en); ((RTIMU_RTIMU*)self)->val->setCompassCalibrationMode(en > 0); Py_RETURN_NONE; }), METH_VARARGS, "Turn on compass calibration mode" }, //////// setAccelCalibrationMode {"setAccelCalibrationMode", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { int en; PyArg_ParseTuple(args, "i", &en); ((RTIMU_RTIMU*)self)->val->setAccelCalibrationMode(en > 0); Py_RETURN_NONE; }), METH_VARARGS, "Turn on accel calibration mode" }, //////// getFusionData {"getFusionData", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { const RTIMU_DATA& data = ((RTIMU_RTIMU*)self)->val->getIMUData(); return Py_BuildValue("(d,d,d)", data.fusionPose.x(), data.fusionPose.y(), data.fusionPose.z()); }), METH_NOARGS, "Return true if valid bias" }, //////// getGyro {"getGyro", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { const RTVector3& r = ((RTIMU_RTIMU*)self)->val->getGyro(); return Py_BuildValue("(d,d,d)", r.x(), r.y(), r.z()); }), METH_NOARGS, "Return the gyro readings" }, //////// getAccel {"getAccel", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { const RTVector3& r = ((RTIMU_RTIMU*)self)->val->getAccel(); return Py_BuildValue("(d,d,d)", r.x(), r.y(), r.z()); }), METH_NOARGS, "Return the accel readings" }, //////// getCompass {"getCompass", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { const RTVector3& r = ((RTIMU_RTIMU*)self)->val->getCompass(); return Py_BuildValue("(d,d,d)", r.x(), r.y(), r.z()); }), METH_NOARGS, "Return the compass readings" }, //////// getAccelResiduals {"getAccelResiduals", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { const RTVector3& r = ((RTIMU_RTIMU*)self)->val->getAccelResiduals(); return Py_BuildValue("(d,d,d)", r.x(), r.y(), r.z()); }), METH_NOARGS, "Return the accel residual readings" }, //////// setExtIMUData {"setExtIMUData", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { double gx, gy, gz, ax, ay, az, mx, my, mz; uint64_t timestamp; PyArg_ParseTuple(args, "dddddddddK", &gx, &gy, &gz, &ax, &ay, &az, &mx, &my, &mz, ×tamp); ((RTIMU_RTIMU*)self)->val->setExtIMUData(gx, gy, gz, ax, ay, az, mx, my, mz, timestamp); Py_RETURN_NONE; }), METH_VARARGS, "Inject data from external IMU" }, { NULL } }; static PyTypeObject RTIMU_RTIMU_type = { #if PY_MAJOR_VERSION >= 3 PyVarObject_HEAD_INIT(NULL, 0) #else PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ #endif "RTIMU.RTIMU", /*tp_name*/ sizeof(RTIMU_RTIMU), /*tp_basicsize*/ 0, /*tp_itemsize*/ (destructor)RTIMU_RTIMU_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ "RTIMU.RTIMU object", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ RTIMU_RTIMU_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)RTIMU_RTIMU_init, /* tp_init */ 0, /* tp_alloc */ RTIMU_RTIMU_new, /* tp_new */ }; static void RTIMU_RTIMU_dealloc(RTIMU_RTIMU* self) { if (self->val != NULL) delete self->val; } static PyObject* RTIMU_RTIMU_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { RTIMU_RTIMU* self; // Allocate the object memory self = (RTIMU_RTIMU*)type->tp_alloc(type, 0); if (self != NULL) { // Set the value field to NULL. The RTIMU Settings object // creation is deferred to init self->val = NULL; } return (PyObject*)self; } static int RTIMU_RTIMU_init(RTIMU_RTIMU *self, PyObject *args, PyObject *kwds) { PyObject* settings; // The user should pass "product name" as an argument if (!PyArg_ParseTuple(args, "O", &settings)) return -1; // Make sure the settings argument is of the correct size if (!RTIMU_Settings_typecheck(settings)) { PyErr_SetString(PyExc_ValueError, (char*)"Argument must be of RTIMU.Settings type"); return -1; } // Create an RTIMU object self->val = RTIMU::createIMU(((RTIMU_Settings*)settings)->val); return 0; } int RTIMU_RTIMU_create(PyObject* module) { // Prepare the type structure if (PyType_Ready(&RTIMU_RTIMU_type) < 0) return -1; // Insert it into the module Py_INCREF(&RTIMU_RTIMU_type); PyModule_AddObject(module, "RTIMU", (PyObject*)&RTIMU_RTIMU_type); return 0; } rtimulib-7.2.1/Linux/python/PyRTIMU_RTPressure.cpp000066400000000000000000000160371254201074400220510ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech // Copyright (c) 2014, avishorp // // Permission 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. // RTIUMULib Python Module - RTPressure type implementation /////////////////////////////////////////////////////// #include "PyRTPressure.h" #include "PyRTIMU.h" // Forwards /////////// static void RTIMU_RTPressure_dealloc(RTIMU_RTPressure* self); static PyObject* RTIMU_RTPressure_new(PyTypeObject *type, PyObject *args, PyObject *kwds); static int RTIMU_RTPressure_init(RTIMU_RTPressure *self, PyObject *args, PyObject *kwds); // The RTIMU_Settings struct //////////////////////////// static PyMethodDef RTIMU_RTPressure_methods[] = { //////// pressureName {"pressureName", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { if (((RTIMU_RTPressure*)self)->val == NULL) #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString("none"); #else return PyString_FromString("none"); #endif else #if PY_MAJOR_VERSION >= 3 return PyUnicode_FromString(((RTIMU_RTPressure*)self)->val->pressureName()); #else return PyString_FromString(((RTIMU_RTPressure*)self)->val->pressureName()); #endif }), METH_NOARGS, "Get the name of the pressure sensor" }, //////// pressureType {"pressureType", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { if (((RTIMU_RTPressure*)self)->val == NULL) #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(0); #else return PyInt_FromLong(0); #endif else #if PY_MAJOR_VERSION >= 3 return PyLong_FromLong(((RTIMU_RTPressure*)self)->val->pressureType()); #else return PyInt_FromLong(((RTIMU_RTPressure*)self)->val->pressureType()); #endif }), METH_NOARGS, "Get the type code of the pressure sensor" }, //////// presureInit {"pressureInit", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { if (((RTIMU_RTPressure*)self)->val == NULL) return PyBool_FromLong(0); else return PyBool_FromLong(((RTIMU_RTPressure*)self)->val->pressureInit()); }), METH_NOARGS, "Set up the pressure sensor" }, //////// pressureRead {"pressureRead", (PyCFunction)([] (PyObject *self, PyObject* args) -> PyObject* { RTIMU_DATA data; if (((RTIMU_RTPressure*)self)->val == NULL) { data.temperatureValid = data.pressureValid = false; data.temperature = 0; data.pressure = 0; } else { ((RTIMU_RTPressure*)self)->val->pressureRead(data); } return Py_BuildValue("idid", data.pressureValid, data.pressure, data.temperatureValid, data.temperature); }), METH_NOARGS, "Get current values" }, { NULL } }; static PyTypeObject RTIMU_RTPressure_type = { #if PY_MAJOR_VERSION >= 3 PyVarObject_HEAD_INIT(NULL, 0) #else PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ #endif "RTIMU.RTPressure", /*tp_name*/ sizeof(RTIMU_RTPressure), /*tp_basicsize*/ 0, /*tp_itemsize*/ (destructor)RTIMU_RTPressure_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ "RTIMU.RTPressure object", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ RTIMU_RTPressure_methods, /* tp_methods */ 0, /* tp_members */ 0, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)RTIMU_RTPressure_init, /* tp_init */ 0, /* tp_alloc */ RTIMU_RTPressure_new, /* tp_new */ }; static void RTIMU_RTPressure_dealloc(RTIMU_RTPressure* self) { if (self->val != NULL) delete self->val; } static PyObject* RTIMU_RTPressure_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { RTIMU_RTPressure* self; // Allocate the object memory self = (RTIMU_RTPressure*)type->tp_alloc(type, 0); if (self != NULL) { // Set the value field to NULL. The RTPressure Settings object // creation is deferred to init self->val = NULL; } return (PyObject*)self; } static int RTIMU_RTPressure_init(RTIMU_RTPressure *self, PyObject *args, PyObject *kwds) { PyObject* settings; // The user should pass settings pointer as an argument if (!PyArg_ParseTuple(args, "O", &settings)) return -1; // Create an RTPressure object self->val = RTPressure::createPressure(((RTIMU_Settings*)settings)->val); return 0; } int RTIMU_RTPressure_create(PyObject* module) { // Prepare the type structure if (PyType_Ready(&RTIMU_RTPressure_type) < 0) return -1; // Insert it into the module Py_INCREF(&RTIMU_RTPressure_type); PyModule_AddObject(module, "RTPressure", (PyObject*)&RTIMU_RTPressure_type); return 0; } rtimulib-7.2.1/Linux/python/PyRTIMU_Settings.cpp000066400000000000000000000422171254201074400215720ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech // Copyright (c) 2014, avishorp // // Permission 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. // RTIUMULib Python Module - RTIMUSettings type implementation /////////////////////////////////////////////////////////////// #include "PyRTIMU.h" #include // Forwards /////////// struct RTIMU_Settings; static void RTIMU_Settings_dealloc(RTIMU_Settings* self); static PyObject* RTIMU_Settings_new(PyTypeObject *type, PyObject *args, PyObject *kwds); static int RTIMU_Settings_init(RTIMU_Settings *self, PyObject *args, PyObject *kwds); static PyObject* RTIMU_Settings_load(RTIMU_Settings* self); static PyObject* RTIMU_Settings_save(RTIMU_Settings* self); static PyObject* RTIMU_Settings_discover(RTIMU_Settings* self, PyObject *args, PyObject *keywds); // The RTIMU_Settings struct //////////////////////////// static PyMethodDef RTIMU_Settings_methods[] = { {"save", (PyCFunction)RTIMU_Settings_save, METH_NOARGS, "Save settings to a file"}, {"load", (PyCFunction)RTIMU_Settings_load, METH_NOARGS, "Load settings from a file"}, {"discoverIMU", (PyCFunction)RTIMU_Settings_discover, METH_VARARGS|METH_KEYWORDS, "Try do discover and auto-set a connected IMU"}, { NULL } }; #if PY_MAJOR_VERSION >= 3 #define RTIMU_PARAM_INT(name, member) \ {(char*)#name, \ (getter)([] (_object* self, void* closure) { \ return Py_BuildValue("i", ((RTIMU_Settings*)self)->val->member); \ }), \ (setter)([] (_object* self, PyObject *value, void *closure) { \ long d = PyLong_AsLong(value); \ if (PyErr_Occurred()) \ return -1; \ ((RTIMU_Settings*)self)->val->member = d; \ return 0; \ }), \ NULL} #else #define RTIMU_PARAM_INT(name, member) \ {(char*)#name, \ (getter)([] (_object* self, void* closure) { \ return Py_BuildValue("i", ((RTIMU_Settings*)self)->val->member); \ }), \ (setter)([] (_object* self, PyObject *value, void *closure) { \ long d = PyInt_AsLong(value); \ if (PyErr_Occurred()) \ return -1; \ ((RTIMU_Settings*)self)->val->member = d; \ return 0; \ }), \ NULL} #endif #if PY_MAJOR_VERSION >= 3 #define RTIMU_PARAM_FLOAT(name, member) \ {(char*)#name, \ (getter)([] (_object* self, void* closure) { \ return Py_BuildValue("d", ((RTIMU_Settings*)self)->val->member); \ }), \ (setter)([] (_object* self, PyObject *value, void *closure) { \ long d = PyLong_AsLong(value); \ if (PyErr_Occurred()) \ return -1; \ ((RTIMU_Settings*)self)->val->member = d; \ return 0; \ }), \ NULL} #else #define RTIMU_PARAM_FLOAT(name, member) \ {(char*)#name, \ (getter)([] (_object* self, void* closure) { \ return Py_BuildValue("d", ((RTIMU_Settings*)self)->val->member); \ }), \ (setter)([] (_object* self, PyObject *value, void *closure) { \ long d = PyInt_AsLong(value); \ if (PyErr_Occurred()) \ return -1; \ ((RTIMU_Settings*)self)->val->member = d; \ return 0; \ }), \ NULL} #endif #define RTIMU_PARAM_VEC3(name, member) \ {(char*)#name, \ (getter)([] (_object* self, void* closure) -> PyObject* { \ return Py_BuildValue("(ddd)", \ ((RTIMU_Settings*)self)->val->member.x(), \ ((RTIMU_Settings*)self)->val->member.y(), \ ((RTIMU_Settings*)self)->val->member.z()); \ }), \ (setter)([] (_object* self, PyObject *value, void *closure) { \ return (Unpack_VEC3(value, ((RTIMU_Settings*)self)->val->member));\ }), \ NULL} int Unpack_VEC3(const PyObject* val, RTVector3& dest) { static char Unpack_VEC3_ValueError[] = "Must be a 3-element float tuple"; // First of all check that the argument is a tuple if (!PyTuple_Check(val)) { PyErr_SetString(PyExc_ValueError, (char*)Unpack_VEC3_ValueError); return -1; } // Make sure the tuple has exactly 3 elements if (!(PyTuple_GET_SIZE(val) == 3)) { PyErr_SetString(PyExc_ValueError, (char*)Unpack_VEC3_ValueError); return -1; } // For each element, make sure it's a float or an int and set it // into the destination vector for(int i = 0; i < 3; i++) { PyObject* item = PyTuple_GET_ITEM(val, i); double val = PyFloat_AsDouble(item); if (PyErr_Occurred()) { PyErr_SetString(PyExc_ValueError, (char*)Unpack_VEC3_ValueError); return -1; } dest.setData(i, val); } return 0; // Success } static PyGetSetDef RTIMU_Settings_getset[] = { RTIMU_PARAM_INT(IMUType, m_imuType), RTIMU_PARAM_INT(FusionType, m_fusionType), RTIMU_PARAM_INT(I2CAddress, m_I2CSlaveAddress), RTIMU_PARAM_INT(I2CBus, m_I2CBus), RTIMU_PARAM_INT(CompassCalValid, m_compassCalValid), RTIMU_PARAM_VEC3(CompassCalMin, m_compassCalMin), RTIMU_PARAM_VEC3(CompassCalMax, m_compassCalMax), RTIMU_PARAM_INT(CompassCalEllipsoidValid, m_compassCalEllipsoidValid), RTIMU_PARAM_VEC3(CompassCalEllipsoidOffset, m_compassCalEllipsoidOffset), RTIMU_PARAM_FLOAT(CompassCalEllipsoidCorr11, m_compassCalEllipsoidCorr[0][0]), RTIMU_PARAM_FLOAT(CompassCalEllipsoidCorr12, m_compassCalEllipsoidCorr[0][1]), RTIMU_PARAM_FLOAT(CompassCalEllipsoidCorr13, m_compassCalEllipsoidCorr[0][2]), RTIMU_PARAM_FLOAT(CompassCalEllipsoidCorr21, m_compassCalEllipsoidCorr[1][0]), RTIMU_PARAM_FLOAT(CompassCalEllipsoidCorr22, m_compassCalEllipsoidCorr[1][1]), RTIMU_PARAM_FLOAT(CompassCalEllipsoidCorr23, m_compassCalEllipsoidCorr[1][2]), RTIMU_PARAM_FLOAT(CompassCalEllipsoidCorr31, m_compassCalEllipsoidCorr[2][0]), RTIMU_PARAM_FLOAT(CompassCalEllipsoidCorr32, m_compassCalEllipsoidCorr[2][1]), RTIMU_PARAM_FLOAT(CompassCalEllipsoidCorr33, m_compassCalEllipsoidCorr[2][2]), RTIMU_PARAM_INT(AccelCalValid, m_accelCalValid), RTIMU_PARAM_VEC3(AccelCalMin, m_accelCalMin), RTIMU_PARAM_VEC3(AccelCalMax, m_accelCalMax), RTIMU_PARAM_INT(GyroBiasValid, m_gyroBiasValid), RTIMU_PARAM_VEC3(GyroBias, m_gyroBias), RTIMU_PARAM_INT(MPU9150GyroAccelSampleRate, m_MPU9150GyroAccelSampleRate), RTIMU_PARAM_INT(MPU9150CompassSampleRate,m_MPU9150CompassSampleRate), RTIMU_PARAM_INT(MPU9150GyroAccelLpf, m_MPU9150GyroAccelLpf), RTIMU_PARAM_INT(MPU9150GyroFst, m_MPU9150GyroFsr), RTIMU_PARAM_INT(MPU9150AccelFsr, m_MPU9150AccelFsr), RTIMU_PARAM_INT(MPU9250GyroAccelSampleRate, m_MPU9250GyroAccelSampleRate), RTIMU_PARAM_INT(MPU9250CompassSampleRate,m_MPU9250CompassSampleRate), RTIMU_PARAM_INT(MPU9250GyroLpf, m_MPU9250GyroLpf), RTIMU_PARAM_INT(MPU9250AccelLpf, m_MPU9250AccelLpf), RTIMU_PARAM_INT(MPU9250GyroFsr, m_MPU9250GyroFsr), RTIMU_PARAM_INT(MPU9250AccelFsr, m_MPU9250AccelFsr), RTIMU_PARAM_INT(GD20HM303DGyroSampleRate, m_GD20HM303DGyroSampleRate), RTIMU_PARAM_INT(GD20HM303DGyroBW, m_GD20HM303DGyroBW), RTIMU_PARAM_INT(GD20HM303DGyroHpf, m_GD20HM303DGyroHpf), RTIMU_PARAM_INT(GD20HM303DGyroFsr, m_GD20HM303DGyroFsr), RTIMU_PARAM_INT(GD20HM303DAccelSampleRate, m_GD20HM303DAccelSampleRate), RTIMU_PARAM_INT(GD20HM303DAccelFsr, m_GD20HM303DAccelFsr), RTIMU_PARAM_INT(GD20HM303DAccelLpf, m_GD20HM303DAccelLpf), RTIMU_PARAM_INT(GD20HM303DCompassSampleRate, m_GD20HM303DCompassSampleRate), RTIMU_PARAM_INT(GD20HM303DCompassFsr, m_GD20HM303DCompassFsr), RTIMU_PARAM_INT(GD20M303DLHCGyroSampleRate, m_GD20M303DLHCGyroSampleRate), RTIMU_PARAM_INT(GD20M303DLHCGyroBW, m_GD20M303DLHCGyroBW), RTIMU_PARAM_INT(GD20M303DLHCGyroHpf, m_GD20M303DLHCGyroHpf), RTIMU_PARAM_INT(GD20M303DLHCGyroFsr, m_GD20M303DLHCGyroFsr), RTIMU_PARAM_INT(GD20M303DLHCAccelSampleRate, m_GD20M303DLHCAccelSampleRate), RTIMU_PARAM_INT(GD20M303DLHCAccelFsr, m_GD20M303DLHCAccelFsr), RTIMU_PARAM_INT(GD20M303DLHCCompassSampleRate, m_GD20M303DLHCCompassSampleRate), RTIMU_PARAM_INT(GD20M303DLHCCompassFsr, m_GD20M303DLHCCompassFsr), RTIMU_PARAM_INT(GD20HM303DLHCGyroSampleRate, m_GD20HM303DLHCGyroSampleRate), RTIMU_PARAM_INT(GD20HM303DLHCGyroBW, m_GD20HM303DLHCGyroBW), RTIMU_PARAM_INT(GD20HM303DLHCGyroHpf, m_GD20HM303DLHCGyroHpf), RTIMU_PARAM_INT(GD20HM303DLHCGyroFsr, m_GD20HM303DLHCGyroFsr), RTIMU_PARAM_INT(GD20HM303DLHCAccelSampleRate, m_GD20HM303DLHCAccelSampleRate), RTIMU_PARAM_INT(GD20HM303DLHCAccelFsr, m_GD20HM303DLHCAccelFsr), RTIMU_PARAM_INT(GD20HM303DLHCCompassSampleRate, m_GD20HM303DLHCCompassSampleRate), RTIMU_PARAM_INT(GD20HM303DLHCCompassFsr, m_GD20HM303DLHCCompassFsr), RTIMU_PARAM_INT(LSM9DS0GyroSampleRate, m_LSM9DS0GyroSampleRate), RTIMU_PARAM_INT(LSM9DS0GyroBW, m_LSM9DS0GyroBW), RTIMU_PARAM_INT(LSM9DS0GyroHpf, m_LSM9DS0GyroHpf), RTIMU_PARAM_INT(LSM9DS0GyroFsr, m_LSM9DS0GyroFsr), RTIMU_PARAM_INT(LSM9DS0AccelSampleRate, m_LSM9DS0AccelSampleRate), RTIMU_PARAM_INT(LSM9DS0AccelFsr, m_LSM9DS0AccelFsr), RTIMU_PARAM_INT(LSM9DS0AccelLpf, m_LSM9DS0AccelLpf), RTIMU_PARAM_INT(LSM9DS0CompassSampleRate, m_LSM9DS0CompassSampleRate), RTIMU_PARAM_INT(LSM9DS0CompassFsr, m_LSM9DS0CompassFsr), RTIMU_PARAM_INT(LSM9DS1GyroSampleRate, m_LSM9DS1GyroSampleRate), RTIMU_PARAM_INT(LSM9DS1GyroBW, m_LSM9DS1GyroBW), RTIMU_PARAM_INT(LSM9DS1GyroHpf, m_LSM9DS1GyroHpf), RTIMU_PARAM_INT(LSM9DS1GyroFsr, m_LSM9DS1GyroFsr), RTIMU_PARAM_INT(LSM9DS1AccelSampleRate, m_LSM9DS1AccelSampleRate), RTIMU_PARAM_INT(LSM9DS1AccelFsr, m_LSM9DS1AccelFsr), RTIMU_PARAM_INT(LSM9DS1AccelLpf, m_LSM9DS1AccelLpf), RTIMU_PARAM_INT(LSM9DS1CompassSampleRate, m_LSM9DS1CompassSampleRate), RTIMU_PARAM_INT(LSM9DS1CompassFsr, m_LSM9DS1CompassFsr), { NULL } }; static PyTypeObject RTIMU_Settings_type = { #if PY_MAJOR_VERSION >= 3 PyVarObject_HEAD_INIT(NULL, 0) #else PyObject_HEAD_INIT(NULL) 0, /*ob_size*/ #endif "RTIMU.Settings", /*tp_name*/ sizeof(RTIMU_Settings), /*tp_basicsize*/ 0, /*tp_itemsize*/ (destructor)RTIMU_Settings_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ 0, /*tp_compare*/ 0, /*tp_repr*/ 0, /*tp_as_number*/ 0, /*tp_as_sequence*/ 0, /*tp_as_mapping*/ 0, /*tp_hash */ 0, /*tp_call*/ 0, /*tp_str*/ 0, /*tp_getattro*/ 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT, /*tp_flags*/ "RTIMU.Settings object", /* tp_doc */ 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ RTIMU_Settings_methods, /* tp_methods */ 0, /* tp_members */ RTIMU_Settings_getset, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ 0, /* tp_descr_set */ 0, /* tp_dictoffset */ (initproc)RTIMU_Settings_init, /* tp_init */ 0, /* tp_alloc */ RTIMU_Settings_new, /* tp_new */ }; static void RTIMU_Settings_dealloc(RTIMU_Settings* self) { if (self->val != NULL) delete self->val; } static PyObject* RTIMU_Settings_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { RTIMU_Settings* self; // Allocate the object memory self = (RTIMU_Settings*)type->tp_alloc(type, 0); if (self != NULL) { // Set the value field to NULL. The RTIMU Settings object // creation is deferred to init self->val = NULL; } return (PyObject*)self; } static int RTIMU_Settings_init(RTIMU_Settings *self, PyObject *args, PyObject *kwds) { const char* product_name; // The user should pass "product name" as an argument if (!PyArg_ParseTuple(args, "s", &product_name)) return -1; // Create an RTIMUSettings object self->val = new RTIMUSettings(product_name); return 0; } static PyObject* RTIMU_Settings_load(RTIMU_Settings* self) { // Invoke the load function self->val->loadSettings(); // Return None Py_INCREF(Py_None); return Py_None; } static PyObject* RTIMU_Settings_save(RTIMU_Settings* self) { // Invoke the save function self->val->saveSettings(); // Return None Py_INCREF(Py_None); return Py_None; } static PyObject* RTIMU_Settings_discover(RTIMU_Settings* self, PyObject *args, PyObject *keywds) { static char* kwlist[] = { (char*)"IMUType", (char*)"SlaveAddress", NULL}; int imu_type = self->val->m_imuType; int slave_addr = self->val->m_I2CSlaveAddress; // Parse the (optional) arguments if (!PyArg_ParseTupleAndKeywords(args, keywds, "|ii", kwlist, &imu_type, &slave_addr)) return NULL; // Do the discovery unsigned char slave_addr_ch = slave_addr; bool isI2C; bool r = self->val->discoverIMU(imu_type, isI2C, slave_addr_ch); if (r) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; } } int RTIMU_Settings_create(PyObject* module) { // Prepare the type structure if (PyType_Ready(&RTIMU_Settings_type) < 0) return -1; // Insert it into the module Py_INCREF(&RTIMU_Settings_type); PyModule_AddObject(module, "Settings", (PyObject*)&RTIMU_Settings_type); return 0; } bool RTIMU_Settings_typecheck(PyObject* obj) { return PyObject_TypeCheck(obj, (PyTypeObject*)&RTIMU_Settings_type); } rtimulib-7.2.1/Linux/python/PyRTPressure.h000066400000000000000000000030021254201074400205220ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, avishorp // Copyright (c) 2014, richards-tech // // Permission 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. // Python binding for RTIMULib #include #include "structmember.h" #include "RTIMULib.h" // RTPressure Type struct RTIMU_RTPressure { PyObject_HEAD RTPressure* val; }; // Create the RTPressure type int RTIMU_RTPressure_create(PyObject* module); rtimulib-7.2.1/Linux/python/README.md000066400000000000000000000056211254201074400172520ustar00rootroot00000000000000Python Binding for RTIMULib =========================== Description ----------- This directory contains a Python module, providing an interface to the RTIMULib library from Python code. Note: this has been tested with Python 2.7 and Python 3.4. Replace "python" with "python3" in the instructions that follow. Installation ------------ python-dev is needed for the compilation. Use: ``` sudo apt-get install python-dev ``` if it is not already installed. The module is built and installed using distutils: ```python python setup.py build ``` followed by: ```python python setup.py install ``` The last command should be run as super-user if system-wide installation is required. All the setup.py options are available. For more information type: ```python python setup.py --help ``` Usage ----- The tests directory has four demo scripts. Fusion.py displays the fused 9-dof data from an IMU. Fusion10.py also displays pressure and temperature data from a 10-dof IMU if a pressure sensor is present. Fusion11.py is the same as Fusion10.py except that it also handles humidity sensors. InjectIMU.py shows how to inject raw IMU data from a source outside of RTIMULib but still utilize the fusion functions. As in the C library, the usage of RTIMULib comprises of three major steps: 1. Creating an `RTIMU.Settings` object. The class constructor receives a "product name" which is actually used as the ini filename (without the ".ini" extension). The file will be created if it doesn't already exist. After creating the `RTIMU.Settings` object, the various parameters appear as the object attributes and can be examined and changed. The settings can also be saved back to the ini file using the `save()` method. 2. Creating an `RTIMU.RTIMU` object. The constructor receives an `RTIMU.Settings` object and auto detects the IMU (if not specified explicitly). 3. Initializing the IMU by calling `IMUInit()` on the `RTIMU.RTIMU` object. The method returns True on successful initialization. 4. Call the `IMURead()` method in regular intervals to retrieve data from the IMU. When the function returns true, the `getFusionData()` method can be used to retrieve the calculated angles. `getIMUData()` can be called to get the complete set of data including quaternions and individual sensor data. Check Fusion.py, Fusion10.py and Fusion11.py for more information on how to use the Python interface. Magnetometer Calibration ------------------------ For good results, magnetometer calibration is essential. The Linux directory contains an app, RTIMULibCal, that can be used to generate an RTIMULib.ini file with calibration data. See the Linux readme for more details on how to build and install RTIMULibCal. Run RTIMULibCal in the directory in which the python app is to be executed. RTIMULibCal will write an RTIMULib.ini file to the working directory which will then be picked up when the Python app is executed. rtimulib-7.2.1/Linux/python/setup.py000066400000000000000000000054101254201074400175010ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014-2015, richards-tech #// Copyright (c) 2014, avishorp #// #// Permission 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. from distutils.core import setup, Extension import os.path RTIMU_sources = [ "RTMath.cpp", "RTIMUHal.cpp", "RTFusion.cpp", "RTFusionKalman4.cpp", "RTFusionRTQF.cpp", "RTIMUSettings.cpp", "IMUDrivers/RTIMU.cpp", "IMUDrivers/RTIMUNull.cpp", "IMUDrivers/RTIMUMPU9150.cpp", "IMUDrivers/RTIMUMPU9250.cpp", "IMUDrivers/RTIMUGD20HM303D.cpp", "IMUDrivers/RTIMUGD20HM303DLHC.cpp", "IMUDrivers/RTIMUGD20M303DLHC.cpp", "IMUDrivers/RTIMULSM9DS0.cpp", "IMUDrivers/RTIMULSM9DS1.cpp", "IMUDrivers/RTIMUBMX055.cpp", "IMUDrivers/RTIMUBNO055.cpp", "IMUDrivers/RTPressure.cpp", "IMUDrivers/RTPressureBMP180.cpp", "IMUDrivers/RTPressureLPS25H.cpp", "IMUDrivers/RTPressureMS5611.cpp", "IMUDrivers/RTPressureMS5637.cpp", "IMUDrivers/RTHumidity.cpp", "IMUDrivers/RTHumidityHTS221.cpp", "IMUDrivers/RTHumidityHTU21D.cpp", ] RTIMU_sourcedir = "../../RTIMULib" mod = Extension('RTIMU', sources = ['PyRTIMU.cpp', 'PyRTIMU_Settings.cpp', 'PyRTIMU_RTIMU.cpp', 'PyRTIMU_RTPressure.cpp', 'PyRTIMU_RTHumidity.cpp'] + [ os.path.join(RTIMU_sourcedir, sr) for sr in RTIMU_sources], include_dirs = [RTIMU_sourcedir], extra_compile_args = ['-std=c++0x'], define_macros = [("HAL_QUIET", None)] ) setup (name = 'RTIMULib', version = '7.2.1', description = 'richards-tech IMU Sensor Fusion Library', ext_modules = [mod]) rtimulib-7.2.1/Linux/python/tests/000077500000000000000000000000001254201074400171315ustar00rootroot00000000000000rtimulib-7.2.1/Linux/python/tests/Fusion.py000066400000000000000000000020631254201074400207470ustar00rootroot00000000000000import sys, getopt sys.path.append('.') import RTIMU import os.path import time import math SETTINGS_FILE = "RTIMULib" print("Using settings file " + SETTINGS_FILE + ".ini") if not os.path.exists(SETTINGS_FILE + ".ini"): print("Settings file does not exist, will be created") s = RTIMU.Settings(SETTINGS_FILE) imu = RTIMU.RTIMU(s) print("IMU Name: " + imu.IMUName()) if (not imu.IMUInit()): print("IMU Init Failed") sys.exit(1) else: print("IMU Init Succeeded") # this is a good time to set any fusion parameters imu.setSlerpPower(0.02) imu.setGyroEnable(True) imu.setAccelEnable(True) imu.setCompassEnable(True) poll_interval = imu.IMUGetPollInterval() print("Recommended Poll Interval: %dmS\n" % poll_interval) while True: if imu.IMURead(): # x, y, z = imu.getFusionData() # print("%f %f %f" % (x,y,z)) data = imu.getIMUData() fusionPose = data["fusionPose"] print("r: %f p: %f y: %f" % (math.degrees(fusionPose[0]), math.degrees(fusionPose[1]), math.degrees(fusionPose[2]))) time.sleep(poll_interval*1.0/1000.0) rtimulib-7.2.1/Linux/python/tests/Fusion10.py000066400000000000000000000043501254201074400211110ustar00rootroot00000000000000import sys, getopt sys.path.append('.') import RTIMU import os.path import time import math SETTINGS_FILE = "RTIMULib" # computeHeight() - the conversion uses the formula: # # h = (T0 / L0) * ((p / P0)**(-(R* * L0) / (g0 * M)) - 1) # # where: # h = height above sea level # T0 = standard temperature at sea level = 288.15 # L0 = standard temperatur elapse rate = -0.0065 # p = measured pressure # P0 = static pressure = 1013.25 # g0 = gravitational acceleration = 9.80665 # M = mloecular mass of earth's air = 0.0289644 # R* = universal gas constant = 8.31432 # # Given the constants, this works out to: # # h = 44330.8 * (1 - (p / P0)**0.190263) def computeHeight(pressure): return 44330.8 * (1 - pow(pressure / 1013.25, 0.190263)); print("Using settings file " + SETTINGS_FILE + ".ini") if not os.path.exists(SETTINGS_FILE + ".ini"): print("Settings file does not exist, will be created") s = RTIMU.Settings(SETTINGS_FILE) imu = RTIMU.RTIMU(s) pressure = RTIMU.RTPressure(s) print("IMU Name: " + imu.IMUName()) print("Pressure Name: " + pressure.pressureName()) if (not imu.IMUInit()): print("IMU Init Failed") sys.exit(1) else: print("IMU Init Succeeded"); # this is a good time to set any fusion parameters imu.setSlerpPower(0.02) imu.setGyroEnable(True) imu.setAccelEnable(True) imu.setCompassEnable(True) if (not pressure.pressureInit()): print("Pressure sensor Init Failed") else: print("Pressure sensor Init Succeeded") poll_interval = imu.IMUGetPollInterval() print("Recommended Poll Interval: %dmS\n" % poll_interval) while True: if imu.IMURead(): # x, y, z = imu.getFusionData() # print("%f %f %f" % (x,y,z)) data = imu.getIMUData() (data["pressureValid"], data["pressure"], data["temperatureValid"], data["temperature"]) = pressure.pressureRead() fusionPose = data["fusionPose"] print("r: %f p: %f y: %f" % (math.degrees(fusionPose[0]), math.degrees(fusionPose[1]), math.degrees(fusionPose[2]))) if (data["pressureValid"]): print("Pressure: %f, height above sea level: %f" % (data["pressure"], computeHeight(data["pressure"]))) if (data["temperatureValid"]): print("Temperature: %f" % (data["temperature"])) time.sleep(poll_interval*1.0/1000.0) rtimulib-7.2.1/Linux/python/tests/Fusion11.py000066400000000000000000000054601254201074400211150ustar00rootroot00000000000000import sys, getopt sys.path.append('.') import RTIMU import os.path import time import math SETTINGS_FILE = "RTIMULib" # computeHeight() - the conversion uses the formula: # # h = (T0 / L0) * ((p / P0)**(-(R* * L0) / (g0 * M)) - 1) # # where: # h = height above sea level # T0 = standard temperature at sea level = 288.15 # L0 = standard temperatur elapse rate = -0.0065 # p = measured pressure # P0 = static pressure = 1013.25 # g0 = gravitational acceleration = 9.80665 # M = mloecular mass of earth's air = 0.0289644 # R* = universal gas constant = 8.31432 # # Given the constants, this works out to: # # h = 44330.8 * (1 - (p / P0)**0.190263) def computeHeight(pressure): return 44330.8 * (1 - pow(pressure / 1013.25, 0.190263)); print("Using settings file " + SETTINGS_FILE + ".ini") if not os.path.exists(SETTINGS_FILE + ".ini"): print("Settings file does not exist, will be created") s = RTIMU.Settings(SETTINGS_FILE) imu = RTIMU.RTIMU(s) pressure = RTIMU.RTPressure(s) humidity = RTIMU.RTHumidity(s) print("IMU Name: " + imu.IMUName()) print("Pressure Name: " + pressure.pressureName()) print("Humidity Name: " + humidity.humidityName()) if (not imu.IMUInit()): print("IMU Init Failed") sys.exit(1) else: print("IMU Init Succeeded"); # this is a good time to set any fusion parameters imu.setSlerpPower(0.02) imu.setGyroEnable(True) imu.setAccelEnable(True) imu.setCompassEnable(True) if (not pressure.pressureInit()): print("Pressure sensor Init Failed") else: print("Pressure sensor Init Succeeded") if (not humidity.humidityInit()): print("Humidity sensor Init Failed") else: print("Humidity sensor Init Succeeded") poll_interval = imu.IMUGetPollInterval() print("Recommended Poll Interval: %dmS\n" % poll_interval) while True: if imu.IMURead(): # x, y, z = imu.getFusionData() # print("%f %f %f" % (x,y,z)) data = imu.getIMUData() (data["pressureValid"], data["pressure"], data["pressureTemperatureValid"], data["pressureTemperature"]) = pressure.pressureRead() (data["humidityValid"], data["humidity"], data["humidityTemperatureValid"], data["humidityTemperature"]) = humidity.humidityRead() fusionPose = data["fusionPose"] print("r: %f p: %f y: %f" % (math.degrees(fusionPose[0]), math.degrees(fusionPose[1]), math.degrees(fusionPose[2]))) if (data["pressureValid"]): print("Pressure: %f, height above sea level: %f" % (data["pressure"], computeHeight(data["pressure"]))) if (data["pressureTemperatureValid"]): print("Pressure temperature: %f" % (data["pressureTemperature"])) if (data["humidityValid"]): print("Humidity: %f" % (data["humidity"])) if (data["humidityTemperatureValid"]): print("Humidity temperature: %f" % (data["humidityTemperature"])) time.sleep(poll_interval*1.0/1000.0) rtimulib-7.2.1/Linux/python/tests/InjectIMU.py000066400000000000000000000021561254201074400212760ustar00rootroot00000000000000import sys, getopt sys.path.append('.') import RTIMU import os.path import time import math # Note: timestamp is in microseconds timestamp = 0 SETTINGS_FILE = "RTIMULib" print("Using settings file " + SETTINGS_FILE + ".ini") if not os.path.exists(SETTINGS_FILE + ".ini"): print("Settings file does not exist, will be created") s = RTIMU.Settings(SETTINGS_FILE) imu = RTIMU.RTIMU(s) imu.IMUInit() print("IMU Name: " + imu.IMUName()) # set some gyro rate here to test (units: rads/s) gx = 0.0 gy = 0.0 gz = 0.1 # set accel to indicate horizontal (units: g) ax = 0.0 ay = 0.0 az = 1.0 # set mag to whatever (or leave as 0 if turned off) (units: uT) mx = 0.0 my = 0.0 mz = 0.0 # this is how to turn off the magnetometer imu.setCompassEnable(False) # everything is now ready while True: print("gz: %f" % gz) imu.setExtIMUData(gx, gy, gz, ax, ay, az, mx, my, mz, timestamp) data = imu.getIMUData() fusionPose = data["fusionPose"] print("r: %f p: %f y: %f" % (math.degrees(fusionPose[0]), math.degrees(fusionPose[1]), math.degrees(fusionPose[2]))) time.sleep(0.1) timestamp += 100000 rtimulib-7.2.1/README.md000066400000000000000000000470521254201074400146360ustar00rootroot00000000000000# RTIMULib - a versatile C++ and Python 9-dof, 10-dof and 11-dof IMU library RTIMULib is the simplest way to connect a 9-dof, 10-dof or 11-dof IMU to an embedded Linux system and obtain Kalman-filtered quaternion or Euler angle pose data. Basically, two simple funtion calls (IMUInit() and IMURead()) are pretty much all that's needed to integrate RTIMULib. ## Have questions, need help or want to comment? Please use the richards-tech user forum at https://groups.google.com/forum/#!forum/richards-tech-user-forum. ## Features The Linux directory contains the main demo apps for embeeded Linux systems: * RTIMULibDrive is a simple app that shows to to use the RTIMULib library in a basic way. * RTIMULibDrive10 adds support for pressure/temperature sensors. * RTIMULibDrive11 adds support for pressure/temperature/humidity sensors. * RTIMULibCal is a command line calibration tool for the magnetometers and accelerometers. * RTIMULibvrpn shows how to use RTIMULib with vrpn. * RTIMULibDemo is a simple GUI app that displays the fused IMU data in real-time. * RTIMULibDemoGL adds OpenGL visualization to RTIMULibDemo. RTIMULib is a C++ library but there are also Python bindings in Linux/python. It's easy to build and install the Python RTIMULib library using the provided setup.py after which any Python script will have access to RTIMULib functionality. See Linux/python.README.md (https://github.com/richards-tech/RTIMULib/blob/master/Linux/python/README.md) for more details. Two demo scripts show how to use the Python interface. Check out www.richards-tech.com for more details, updates and news. RTIMULib currently supports the following IMUs: * InvenSense MPU-9150 single chip IMU. * InvenSense MPU-6050 plus HMC5883 magnetometer on MPU-6050's aux bus (handled by the MPU-9150 driver). * InvenSense MPU-6050 gyros + acclerometers. Treated as MPU-9150 without magnetometers. * InvenSense MPU-9250 single chip IMU (I2C and SPI). * STM LSM9DS0 single chip IMU. * STM LSM9DS1 single chip IMU. * L3GD20H + LSM303D (optionally with the LPS25H) as used on the Pololu AltIMU-10 v4. * L3GD20 + LSM303DLHC as used on the Adafruit 9-dof (older version with GD20 gyro) IMU. * L3GD20H + LSM303DLHC (optionally with BMP180) as used on the new Adafruit 10-dof IMU. * Bosch BMX055 (although magnetometer support is experimental currently). * Bosch BNO055 IMU with onchip fusion. Note: will not work reliably with RaspberryPi/Pi2 due to clock-stretching issues. The LSM9DS1 implementation was generously supplied by XECDesign. Pressure/temperature sensing is supported for the following pressure sensors: * BMP180 * LPS25H * MS5611 * MS5637 Humidity/temperature sensing is supported for the following humidity sensors: * HTS221 * HTU21D The humidity infrastructure and HTS221 support was generously supplied by XECDesign. It follows the model used by the pressure infrastructure - see RTIMULibDrive11 for an example of how to use this. Note that currently only pressure and humidity sensors connected via I2C are supported. Also, an MS5637 sensor will be auto-detected as an MS5611. To get the correct processing for the MS5637, edit the RTIMULib.ini file and set PressureType=5. By default, RTIMULib will try to autodiscover IMUs, pressure and humidity sensors on I2C and SPI busses (only IMUs on the SPI bus). This will use I2C bus 1 and SPI bus 0 although this can be changed by hand editing the .ini settings file (usually called RTIMULib.ini) loaded/saved in the current working directory by any of the RTIMULib apps. RTIMULib.ini is self-documenting making it easy to edit. Alternatively, RTIMULibDemo and RTIMULibDemoGL provide a GUI interface for changing some of the major settings in the .ini file. RTIMULib also supports multiple sensor integration fusion filters such as Kalman filters. Two types of platforms are supported: * Embedded Linux. RTIMULib is supported for the Raspberry Pi (Raspbian) and Intel Edison. Demo apps for these can be found in the Linux directory and instructions for building and running can be found there. Its prerequisites are very simple - just I2C support on the target system along with the standard build-essential (included in the Raspberry Pi Raspbian distribution by default). * Desktop (Ubuntu/Windows/Mac). There are two apps (RTHostIMU and RTHostIMUGL) that allow the sensor fusion to be separated from the sensor interfacing and data collection. An Arduino (running the RTArduLinkIMU sketch from the RTIMULib-Arduino repo) fitted with an IMU chip collects the sensor data and sends it to the desktop. RTHostIMU and RTHostIMUGL (this one has an OpenGL visualization of the data) communicate with the Arduino via a USB connection. The MPU-9250 and SPI driver code is based on code generously supplied by staslock@gmail.com (www.clickdrive.io). I am sure that any bugs that may exist are due to my integration efforts and not the quality of the supplied code! RTIMULib is licensed under the MIT license. ## Repo structure ### RTIMULib This is the actual RTIMULib library source. Custom apps only need to include this library. ### Linux This directory contains the embedded Linux demo apps (for Raspberry Pi and Intel Edison) and also the Python interface to RTIMULib. ### RTHost RTHost contains the two apps, RTHost and RTHostGL, that can be used by desktops that don't have direct connection to an IMU (as they don't have I2C or SPI interfaces). An Arduino running RTArduLinkIMU from the RTIMULib-Arduino repo provides the hardware interface and a USB cable provides the connection between the desktop and the Arduino. ### RTEllipsoidFit This contains Octave code used by the ellipsiod fit data generation in RTIMULibCal, RTIMULibDemo, RTIMULibDemoGL, RTHostIMU and RTHostIMUGL. It's important that a copy of this directory is at the same level, or the one above, the app's working directory or ellipsoid fit data generation will fail. ## Note about magnetometer (compass) calibration It is essential to calibrate the magnetometer or else very poor fusion results will be obtained. For more about this, see http://wp.me/p4qcHg-b4. RTIMULibDemo (GUI) and RTIMULibCal (command line) can be used to do this. They both support magnetometer min/max, magnetometer ellipsoid fit and accelerometer min/max calibration. Also, if using a non-standard axis rotation (see http://wp.me/p4qcHg-cO), magnetometer calibration (and accelerometer calibration if that has been performed) MUST be run AFTER changing the axis rotation. ## Next Steps SyntroPiNav (an app for the Raspberry Pi) and SyntroNavView can be used as a convenient system to experiment with IMU chips, drivers and filters. SyntroPiNav runs on the Pi and transmits IMU data along with filter outputs over a LAN to SyntroNavView running on an Ubuntu PC. SyntroNavView also displays the data and provides a 3D graphics view of the calculated pose. Since all IMU data is sent to SyntroNavView, SyntroNavView can run its own local filter. This makes it a very convenient testbed for new filter development as the speed of the desktop can be used to accelerate implementation and testing. When ready, the updated RTIMULib can be compiled into SyntroPiNav and should work exactly the same as on the desktop. SyntroPiNav is available as part of the richards-tech SyntroPiApps repo (https://github.com/richards-tech/SyntroPiApps) while SyntroNavView is available as part of the richards-tech SyntroApps repo (https://github.com/richards-tech/SyntroApps). ## Release history ### June 22 2015 - 7.2.1 Improvement to Linux CMakeLists.txt. Added temp comp to HTU21D. ### June 21 2015 - 7.2.0 Added support for HTU21D humidity sensor. ### June 17 2015 - 7.1.0 Added humidity support to the demo apps and created RTIMULibDrive11 and Fusion11.py. Fixed some related build problems. Updated some source headers. ### June 15 2015 - 7.0.3 Improved CMake versioning system. ### June 12 2015 - 7.0.2 Also added SOVERSION to RTIMULibGL build via CMake. Note - on Linux it may be necessary to run "sudo ldconfig" after building. ### June 12 2015 - 7.0.1 Added SOVERSION to CMake build library. ### June 9 2015 - 7.0.0 New humidity infrastructure and HTS221 support added. Thanks to XECDesign for this. Due to lack of hardware for testing at this time, this release is somewhat experimental - use 6.3.0 if problems are encountered. LSM9DS1 support added. ### May 17 2015 - 6.3.0 Added support for the BNO055. The BNO055 always uses its onchip fusion rather than RTIMULib filters. This will not work reliably with the Raspberry Pi/Pi2 due to clock-stretching issues. ### April 30 2015 - 6.2.1 Added second order temperature compensation for MS5611 and MS5637. MS5637 still seems to be affected by temperature - this is being investigated. ### April 24 2015 - 6.2.0 Add support for Bosch BMX055 IMU and MS5637 pressure sensor. See notes above about auto-detection for the MS5637. The BMX055 implementation is slightly experimental as the magnetometer results show significant asymmetry about zero. The processing of the magnetometer data is fairly complex and there could be an error in it. Calibration is essential for this IMU at the moment. ### March 31 2015 - 6.1.0 Allow RTQF Slerp power to be changed while running while fusion running. Some performance improvements and cleanups. ### March 29 2015 - 6.0.0 Changed RTQF state correction mechanism to use quaternion SLERP. This is a little experimental - if you encounter problems, please use the 5.6.0 release (from the Releases tab). ### March 21 2015 - 5.6.0 Added support for MPU6050 + HMC5883 IMUs (HMC5883 on MPU-6050's aux bus). ### March 20 2015 - 5.5.0 Added support for the MS5611 pressure sensor and also modified MPU-9150 driver to also support the MPU-6050. ### February 21 2015 - 5.4.0 Python API now works with Python 2.7 and Python 3.4. Changed MPU9150 and MPU9250 drivers so that compass adjust is performed before axis swap. ### January 31 2015 - 5.3.0 Added abilty to set magnetic declination in the .ini file. This value in radians is subtracted from the measured heading before being used by the fusion algorithm. ### January 24 2015 - 5.2.3 Fixed problem with CMakeLists.txt for RTIMULibGL. ### January 19 2015 - 5.2.2 Improved some CMakeLists. RTIMULib can now be built with cmake independently. ### December 29 2014 - 5.2.1 Some improvements to the RTHost CMakelists.txt. Changed Visual Studio version to VS2013. ### December 29 2014 - 5.2.0 Added support for vrpn. There is a new demo app, RTIMULibvrpn, that shows how this works. RTSettings constructor now optionally allows the directory used for the .ini settings file to be specified. The original constructor uses the working directory whereas the additional constructor allows this bevaiour to be overridden. Changed install directory to /usr/local/bin when using the supplid Makefiles and qmake instead of /usr/bin. This is to be consistent with cmake-generated makefiles. ### December 15 2014 - 5.1.0 Added support for the LPS25H pressure/temperature sensor. Addeed support for SPI chip select 1 in addition to chip select 0. A new field, SPISelect, has been added. RTIMULib will try to autodetect SPI bus 0, select 0 and SPI bus 0 select 1 but others can be used if the RTIMULib.ini file is hand-edited. ### December 10 2014 - 5.0.0 Top level directory structure completely reorganized. Support for pressure sensors. New demo app, RTIMULibDrive10, to demonstrate use of pressure sensors with RTIMULib. RTIMULibDemo and RTIMULibDemoGL upgraded to support pressure sensors. Python library upgraded to support pressure sensors. A new demo script, Fusion10.py, shows how to use the interface. ### December 3 2014 - 4.4.0 Added RTIMULibDemoGL and reorganized the OpenGL components. ### December 3 2014 - 4.3.2 CMake build system now works for the RTHostIMU and RTHostIMUGL apps on Windows and Mac OS X. The full set of apps are built on Linux. Some minor driver fixes. RTHostIMUGL now runs on the Raspberry Pi. Simpler (non-ADS) shading is used to imporve performance. ### December 2 2014 - 4.3.1 Fixed the CMakeLists.txt for RTIMULibDemo. ### December 2 2014 - 4.3.0 Added cmake support (see build instructions for more info). This was based on work by Moritz Fischer at ettus.com. As part of this, the qextserialport folder was renamed to RTSerialPort. There are also some small fixes in the MPU-9150/9250 and GD20HM303D drivers. ### November 18 2014 - 4.2.0 Add the IMU axis rotation capability to better handle IMUs in non-standard orientations. See http://wp.me/p4qcHg-cO for more details of how to use this capability. ### November 14 2014 - 4.1.0 Corrected some problems with the continuous gyro bias update system. There is a new function in RTIMU called setGyroContinuousLearningAlpha() that allows the continuous learning rate to be set. RTIMULIB uses a rapid learning rate to collect the initial gyro bias data but then uses a much slower rate for continuous tracking of bias. This function allows the rate to be set if necessary - values can be between 0.0 and 1.0. Setting it to 0.0 turns off continuous learning completely so that gyro bias calculation only occurs during the rapid learning period. loadSettings() and saveSettings() in RTSettings.cpp are now virtual to give more flexibility in how settings are stored. ### November 8 2014 - 4.0.1 Fixed some missing MPU-9250 related defs in python interface. ### November 7 2014 - 4.0.0 Restructured library to add support for the MPU-9250 and SPI bus. This is a little experimental right now - use V3.1.1 if problems are encountered with existing supported IMUs. The MPU-9250 has been seen to hang when used on the SPI bus at sample rates above 300 samples per second. However, sample rates up to 1000 seem to work fine using I2C. The RTIMULib apps are now able to auto-detect on the I2C and SPI bus so, if only one supported IMU is present on either bus, the code should find it. Note that only the MPU-9250 is supported by the SPI driver at the moment. There are some new settings in the RTIMULib.ini file related to the SPI bus that may need editing in some systems. The default SPI bus is set 0 which works nicely for the Raspberry Pi. Connect the MPU-9250 to SPI0 and CS0 and it should work without needing to change anythin in RTIMULib.ini. ### November 4 2014 - 3.1.1 Can now supply the .ini name as a command line argument. For example: RTIMULibCal Palm would calibrate a settings file called Palm.ini. ### November 1 2014 - 3.1.0 Added the RTIMULibCal application. This implements IMU calibration in no GUI (command line) environments. ### October 13 2014 - 3.0.3 Increased time allowed for ellipse fitting to complete before declaring it finished. ### October 13 2014 - 3.0.2 Added license information to Calibrate.pdf. ### October 13 2014 - 3.0.1 Fixed missing license header in RTEllipsoidFit. ### October 13 2014 - 3.0.0 RTIMULib now support accelerometer calibration and enhanced magnetometer calibration using ellipsoid fitting. Please check the Calibration.pdf document for instructions on how to create the calibration data. ### October 8 2014 - 2.1.2 Fixed some missed license header changes. ### October 8 2014 - 2.1.1 Fixed bug where the first com put was missed on the GUI dropdown in RTHostIMU and RTHostIMUGL. ### October 8 2014 - 2.1.0 Changed license to MIT. Added RTHostIMU and RTHostIMUGL. These apps use RTArduLink to connect the host system to an IMU connected to an Arduino. This allows processing to be split between the Arduino and the host system. Sensor data collection is performed on the Arduino, sensor fusion and display is performed on the host. This means that the apps will run on hosts without I2C ports (such as PCs). See below for more details. ### October 2 2014 - 2.0.0 Changed the gyro bias calculation to run automatically when the IMU is detected as being stable. This means that the IMU no longer needs to be kept still for 5 seconds after restart and gyro bias is continually tracked. IMUGyroBiasValid can be called to check if enough stable samples have been obtained for a reasonable bias calculation to be made. If the IMU is stable, this will normally occur within 5 seconds. If not stable at the start, it may take longer but it will occur eventually once enough stable samples have been obtained. If RTIMULibDemo never indicates a valid bias, the #defines RTIMU_FUZZY_GYRO_ZERO and/or RTIMU_FUZZY_ACCEL_ZERO may need to be increased if the gyro bias or accelerometer noise is unusually high. These should be set to be greater than the readings observed using RTIMULibDemo when the IMU is completely stable. In the case of the gyros, this should be the absolute values when the IMU isn't being moved. In the case of the accels, this should be the maximum change in values when the IMU isn't being moved. Stable gyro bias values are saved to the RTIMULib.ini file in order to speed up restarts. The values will once again be updated after enough stable samples have been obtained in order to track long term changes in gyro bias. If problems are encountered, try version 1.0.4 which is available under the GitHub repo releases tab. Please also report any issues via the GitHub issue system to help improve RTIMULib! ### September 3 2014 - 1.0.4 Fixed message error in RTIMUSettings. ### September 2 2014 - 1.0.3 CompassCalMax was returning m_compassCalMin in PyRTIMU_settings.cpp - changed to max instead. Thanks to Stefan Grufman for finding that. ### August 6 2014 - 1.0.2 Added missing compass sample rate defines for LSM303DLHC and updated settings comments. Thanks to Bill Robertson (broberts4) for spotting that! ### July 29 2014 - 1.0.1 Fixed the python getIMUData function. ### July 7 2014 - 1.0.0 #### Added python bindings Thanks to avishorp for the python code and Luke Heidelberger for a bug fix. ### April 13 2014 - 0.9.4 #### Added new RTQF fusion filter RTQF is a very highly stripped down Kalman filter that avoids matrix inversion and lot of other matrix operations. It makes a small performance difference on the Raspberry Pi but would have more impact on lower powered processors. ### April 10 2014 - 0.9.3 #### STM LSM9DS0 IMU Implementation now working The single chip IMU LSM9DS0 is now working with RTIMULib. An example breakout is available from Sparkfun - https://www.sparkfun.com/products/12636. ### April 9 2014 - 0.9.2 #### STM L3GD20H + LSM303D IMU Implementation now working The combination of the L3GD20H gyro and LSM303D accel/mag chip is now working. The physical configuration supported is as used on the Pololu Altimu V3 - http://www.pololu.com/product/2469. The pressure chip on the 10-dof version will be supported shortly but 9-dof is working now. #### STM L3GD20 + LSM303DLHC IMU Implementation now working The combination of the L3GD20 and LSM303DLHC accel/mag chip is now working. The physical configuration supported is as used on the Adafruit 9-dof IMU - http://www.adafruit.com/products/1714. ### April 7 2014 - 0.9.1 #### Improved performance with MPU-9150 A new caching strategy for the MPU-9150 seems to be achieving 1000 samples per second without fifo overflows using a 900MHz Raspberry Pi and 400kHz I2C bus. This is as reported by RTIMULibDrive with a CPU utilization of 28%. RTIMULibDemo manages 890 samples per second with the MPU-9150 set to 1000 samples per second. The driver gracefully handles this situation although there is increased delay when the application cannot handle the full sample rate. #### Auto detection of IMU RTIMULib can now scan for supported IMUs and configure automatically. This is the default behavior now. It handles IMUs at alternate address automatically as well (for example, it will detect an MPU-9150 at 0x68 or 0x69). #### Partial support for STM L3GD20H/LSM303D IMUs This is in a very early state and only supports the gyro sensor at the moment. ### April 4 2014 - 0.9.0 Initial release with support for MPU9150. rtimulib-7.2.1/RTEllipsoidFit/000077500000000000000000000000001254201074400162045ustar00rootroot00000000000000rtimulib-7.2.1/RTEllipsoidFit/COPYING000066400000000000000000001045131254201074400172430ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . rtimulib-7.2.1/RTEllipsoidFit/RTEllipsoidFit.m000066400000000000000000000053101254201074400212160ustar00rootroot00000000000000%// %// Copyright (c) 2014, richards-tech %// %// This file is part of RTEllipsoidFit %// %// RTEllipsoidFit is free software: you can redistribute it and/or modify %// it under the terms of the GNU General Public License as published by %// the Free Software Foundation, either version 3 of the License, or %// (at your option) any later version. %// %// RTEllipsoidFit is distributed in the hope that it will be useful, %// but WITHOUT ANY WARRANTY; without even the implied warranty of %// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %// GNU General Public License for more details. %// %// You should have received a copy of the GNU General Public License %// along with RTEllipsoidFit. If not, see . %// %// Parts of this file are based on original code as below: %****************************************************************************************** % Magnetometer Calibration Skript for Razor AHRS v1.4.2 % 9 Degree of Measurement Attitude and Heading Reference System % for Sparkfun "9DOF Razor IMU" and "9DOF Sensor Stick" % % Released under GNU GPL (General Public License) v3.0 % Copyright (C) 2013 Peter Bartz [http://ptrbrtz.net] % Copyright (C) 2012 Quality & Usability Lab, Deutsche Telekom Laboratories, TU Berlin % Written by Peter Bartz (peter-bartz@gmx.de) % % Infos, updates, bug reports, contributions and feedback: % https://github.com/ptrbrtz/razor-9dof-ahrs %****************************************************************************************** % RTEllipsoidFit % read in data from saved file magFile = fopen('magRaw.dta', 'r'); magCalDataMat = fscanf(magFile, '%f %f %f', [3, inf]); fprintf('Mat size = %d\n', size(magCalDataMat)); fclose(magFile); % open output file corrFile = fopen('magCorr.dta', 'w'); % create the vectors x = magCalDataMat(1, :)'; y = magCalDataMat(2, :)'; z = magCalDataMat(3, :)'; [center, radii, evecs, v] = ellipsoid_fit( [x y z ] ); scaleMat = inv([radii(1) 0 0; 0 radii(2) 0; 0 0 radii(3)]) * min(radii); correctionMat = evecs * scaleMat * evecs'; % now correct the data to show that it works magVector = [x - center(1), y - center(2), z - center(3)]'; % take off center offset %magVector = correctionMat * magVector; % do rotation and scale magVector = evecs * magVector; % do rotation and scale xCorr = magVector(1, :); % get corrected vectors yCorr = magVector(2, :); zCorr = magVector(3, :); fprintf(corrFile, '%f %f %f %f %f %f %f %f %f %f %f %f\n', center(1), center(2), center(3), correctionMat(1, 1), correctionMat(1, 2), correctionMat(1, 3), correctionMat(2, 1), correctionMat(2, 2), correctionMat(2, 3), correctionMat(3, 1), correctionMat(3, 2), correctionMat(3, 3)); fclose(corrFile); rtimulib-7.2.1/RTEllipsoidFit/ellipsoid_fit.m000066400000000000000000000112071254201074400212110ustar00rootroot00000000000000function [ center, radii, evecs, v ] = ellipsoid_fit( X, flag, equals ) % % Fit an ellispoid/sphere to a set of xyz data points: % % [center, radii, evecs, pars ] = ellipsoid_fit( X ) % [center, radii, evecs, pars ] = ellipsoid_fit( [x y z] ); % [center, radii, evecs, pars ] = ellipsoid_fit( X, 1 ); % [center, radii, evecs, pars ] = ellipsoid_fit( X, 2, 'xz' ); % [center, radii, evecs, pars ] = ellipsoid_fit( X, 3 ); % % Parameters: % * X, [x y z] - Cartesian data, n x 3 matrix or three n x 1 vectors % * flag - 0 fits an arbitrary ellipsoid (default), % - 1 fits an ellipsoid with its axes along [x y z] axes % - 2 followed by, say, 'xy' fits as 1 but also x_rad = y_rad % - 3 fits a sphere % % Output: % * center - ellispoid center coordinates [xc; yc; zc] % * ax - ellipsoid radii [a; b; c] % * evecs - ellipsoid radii directions as columns of the 3x3 matrix % * v - the 9 parameters describing the ellipsoid algebraically: % Ax^2 + By^2 + Cz^2 + 2Dxy + 2Exz + 2Fyz + 2Gx + 2Hy + 2Iz = 1 % % Author: % Yury Petrov, Northeastern University, Boston, MA % error( nargchk( 1, 3, nargin ) ); % check input arguments if nargin == 1 flag = 0; % default to a free ellipsoid end if flag == 2 && nargin == 2 equals = 'xy'; end if size( X, 2 ) ~= 3 error( 'Input data must have three columns!' ); else x = X( :, 1 ); y = X( :, 2 ); z = X( :, 3 ); end % need nine or more data points if length( x ) < 9 && flag == 0 error( 'Must have at least 9 points to fit a unique ellipsoid' ); end if length( x ) < 6 && flag == 1 error( 'Must have at least 6 points to fit a unique oriented ellipsoid' ); end if length( x ) < 5 && flag == 2 error( 'Must have at least 5 points to fit a unique oriented ellipsoid with two axes equal' ); end if length( x ) < 3 && flag == 3 error( 'Must have at least 4 points to fit a unique sphere' ); end if flag == 0 % fit ellipsoid in the form Ax^2 + By^2 + Cz^2 + 2Dxy + 2Exz + 2Fyz + 2Gx + 2Hy + 2Iz = 1 D = [ x .* x, ... y .* y, ... z .* z, ... 2 * x .* y, ... 2 * x .* z, ... 2 * y .* z, ... 2 * x, ... 2 * y, ... 2 * z ]; % ndatapoints x 9 ellipsoid parameters elseif flag == 1 % fit ellipsoid in the form Ax^2 + By^2 + Cz^2 + 2Gx + 2Hy + 2Iz = 1 D = [ x .* x, ... y .* y, ... z .* z, ... 2 * x, ... 2 * y, ... 2 * z ]; % ndatapoints x 6 ellipsoid parameters elseif flag == 2 % fit ellipsoid in the form Ax^2 + By^2 + Cz^2 + 2Gx + 2Hy + 2Iz = 1, % where A = B or B = C or A = C if strcmp( equals, 'yz' ) || strcmp( equals, 'zy' ) D = [ y .* y + z .* z, ... x .* x, ... 2 * x, ... 2 * y, ... 2 * z ]; elseif strcmp( equals, 'xz' ) || strcmp( equals, 'zx' ) D = [ x .* x + z .* z, ... y .* y, ... 2 * x, ... 2 * y, ... 2 * z ]; else D = [ x .* x + y .* y, ... z .* z, ... 2 * x, ... 2 * y, ... 2 * z ]; end else % fit sphere in the form A(x^2 + y^2 + z^2) + 2Gx + 2Hy + 2Iz = 1 D = [ x .* x + y .* y + z .* z, ... 2 * x, ... 2 * y, ... 2 * z ]; % ndatapoints x 4 sphere parameters end % solve the normal system of equations v = ( D' * D ) \ ( D' * ones( size( x, 1 ), 1 ) ); % find the ellipsoid parameters if flag == 0 % form the algebraic form of the ellipsoid A = [ v(1) v(4) v(5) v(7); ... v(4) v(2) v(6) v(8); ... v(5) v(6) v(3) v(9); ... v(7) v(8) v(9) -1 ]; % find the center of the ellipsoid center = -A( 1:3, 1:3 ) \ [ v(7); v(8); v(9) ]; % form the corresponding translation matrix T = eye( 4 ); T( 4, 1:3 ) = center'; % translate to the center R = T * A * T'; % solve the eigenproblem [ evecs evals ] = eig( R( 1:3, 1:3 ) / -R( 4, 4 ) ); radii = sqrt( 1 ./ diag( evals ) ); else if flag == 1 v = [ v(1) v(2) v(3) 0 0 0 v(4) v(5) v(6) ]; elseif flag == 2 if strcmp( equals, 'xz' ) || strcmp( equals, 'zx' ) v = [ v(1) v(2) v(1) 0 0 0 v(3) v(4) v(5) ]; elseif strcmp( equals, 'yz' ) || strcmp( equals, 'zy' ) v = [ v(2) v(1) v(1) 0 0 0 v(3) v(4) v(5) ]; else % xy v = [ v(1) v(1) v(2) 0 0 0 v(3) v(4) v(5) ]; end else v = [ v(1) v(1) v(1) 0 0 0 v(2) v(3) v(4) ]; end center = ( -v( 7:9 ) ./ v( 1:3 ) )'; gam = 1 + ( v(7)^2 / v(1) + v(8)^2 / v(2) + v(9)^2 / v(3) ); radii = ( sqrt( gam ./ v( 1:3 ) ) )'; evecs = eye( 3 ); end rtimulib-7.2.1/RTEllipsoidFit/ellipsoid_fit_license.txt000066400000000000000000000024371254201074400233030ustar00rootroot00000000000000Copyright (c) 2009, Yury Petrov All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. rtimulib-7.2.1/RTEllipsoidFit/mag_cal.m000066400000000000000000000055751254201074400177610ustar00rootroot00000000000000%// %// Copyright (c) 2014, richards-tech %// %// This file is part of RTEllipsoidFit %// %// RTEllipsoidFit is free software: you can redistribute it and/or modify %// it under the terms of the GNU General Public License as published by %// the Free Software Foundation, either version 3 of the License, or %// (at your option) any later version. %// %// RTEllipsoidFit is distributed in the hope that it will be useful, %// but WITHOUT ANY WARRANTY; without even the implied warranty of %// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %// GNU General Public License for more details. %// %// You should have received a copy of the GNU General Public License %// along with RTEllipsoidFit. If not, see . %// %// Parts of this file are based on original code as below: %****************************************************************************************** % Magnetometer Calibration Skript for Razor AHRS v1.4.2 % 9 Degree of Measurement Attitude and Heading Reference System % for Sparkfun "9DOF Razor IMU" and "9DOF Sensor Stick" % % Released under GNU GPL (General Public License) v3.0 % Copyright (C) 2013 Peter Bartz [http://ptrbrtz.net] % Copyright (C) 2012 Quality & Usability Lab, Deutsche Telekom Laboratories, TU Berlin % Written by Peter Bartz (peter-bartz@gmx.de) % % Infos, updates, bug reports, contributions and feedback: % https://github.com/ptrbrtz/razor-9dof-ahrs %****************************************************************************************** % mag_cal % read in data from saved file magFile = fopen('magRaw.dta', 'r'); magCalDataMat = fscanf(magFile, '%f %f %f', [3, inf]); fprintf('Mat size = %d\n', size(magCalDataMat)); fclose(magFile); % create the vectors x = magCalDataMat(1, :)'; y = magCalDataMat(2, :)'; z = magCalDataMat(3, :)'; [center, radii, evecs, v] = ellipsoid_fit( [x y z ] ); fprintf('Center: %f %f %f\n', center(1), center(2), center(3)); fprintf('Radii: %f %f %f\n', radii(1), radii(2), radii(3)); fprintf('Evecs:\n%f %f %f\n%f %f %f\n%f %f %f\n', evecs(1, 1), evecs(1, 2), evecs(1, 3), evecs(2, 1), evecs(2, 2), evecs(2, 3), evecs(3, 1), evecs(3, 2), evecs(3, 3)); scaleMat = inv([radii(1) 0 0; 0 radii(2) 0; 0 0 radii(3)]) * min(radii); correctionMat = evecs * scaleMat * evecs'; fprintf('correctionMat:\n%f %f %f\n%f %f %f\n%f %f %f\n', correctionMat(1, 1), correctionMat(1, 2), correctionMat(1, 3), correctionMat(2, 1), correctionMat(2, 2), correctionMat(2, 3), correctionMat(3, 1), correctionMat(3, 2), correctionMat(3, 3)); % now correct the data to show that it works magVector = [x - center(1), y - center(2), z - center(3)]'; % take off center offset magVector = correctionMat * magVector; % do rotation and scale xCorr = magVector(1, :); % get corrected vectors yCorr = magVector(2, :); zCorr = magVector(3, :); mag_fit_display(x, y, z, center, radii, v, xCorr, yCorr, zCorr); rtimulib-7.2.1/RTEllipsoidFit/mag_fit_display.m000066400000000000000000000023041254201074400215140ustar00rootroot00000000000000%// %// Copyright (c) 2014, richards-tech %// %// This file is part of RTEllipsoidFit %// %// RTEllipsoidFit is free software: you can redistribute it and/or modify %// it under the terms of the GNU General Public License as published by %// the Free Software Foundation, either version 3 of the License, or %// (at your option) any later version. %// %// RTEllipsoidFit is distributed in the hope that it will be useful, %// but WITHOUT ANY WARRANTY; without even the implied warranty of %// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the %// GNU General Public License for more details. %// %// You should have received a copy of the GNU General Public License %// along with RTEllipsoidFit. If not, see . %// function mag_fit_display(x, y, z, center, radii, v, xCorr, yCorr, zCorr) displayRadius = 100; figure(1); hold on; grid; xlabel('x'); ylabel('y'); zlabel('z'); axis ([-displayRadius, displayRadius, -displayRadius, displayRadius, -displayRadius, displayRadius], "square"); title('Uncorrected samples (red) and corrected (blue) values'); plot3( x, y, z, ['.' 'r'] ); plot3( xCorr, yCorr, zCorr, ['.' 'b'] ); % display immediately drawnow(); end rtimulib-7.2.1/RTHost/000077500000000000000000000000001254201074400145325ustar00rootroot00000000000000rtimulib-7.2.1/RTHost/CMakeLists.txt000066400000000000000000000066431254201074400173030ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014, richards-tech #// #// Permission 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. #// The cmake support was based on work by Moritz Fischer at ettus.com. #// Original copyright notice: # # Copyright 2014 Ettus Research LLC # ######################################################################## IF(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) MESSAGE(FATAL_ERROR "Prevented in-tree built. This is bad practice.") ENDIF(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) ######################################################################## # Project setup ######################################################################## CMAKE_MINIMUM_REQUIRED(VERSION 2.8.9) PROJECT(RTIMUHOST CXX) ENABLE_TESTING() INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../RTIMULib) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/RTIMULibGL) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/RTIMULibGL/QtGLLib) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/RTIMULibGL/VRWidgetLib) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/RTArduLinkHost) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/RTHostIMUCommon) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/RTSerialPort/src) IF(APPLE) cmake_policy(SET CMP0042 NEW) # use, i.e. don't skip the full RPATH for the build tree SET(CMAKE_SKIP_BUILD_RPATH FALSE) # when building, don't use the install RPATH already # (but later on when installing) SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") # add the automatically determined parts of the RPATH # which point to directories outside the build tree to the install RPATH SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) # the RPATH to be used when installing, but only if it's not a system directory LIST(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" isSystemDir) IF("${isSystemDir}" STREQUAL "-1") SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") ENDIF("${isSystemDir}" STREQUAL "-1") ENDIF(APPLE) ADD_SUBDIRECTORY(${CMAKE_CURRENT_SOURCE_DIR}/../RTIMULib RTIMULib) ADD_SUBDIRECTORY(RTIMULibGL) ADD_SUBDIRECTORY(RTSerialPort) ADD_SUBDIRECTORY(RTArduLinkHost) ADD_SUBDIRECTORY(RTHostIMUCommon) ADD_SUBDIRECTORY(RTHostIMU) ADD_SUBDIRECTORY(RTHostIMUGL) MESSAGE(STATUS "Using install prefix: ${CMAKE_INSTALL_PREFIX}") rtimulib-7.2.1/RTHost/README.md000066400000000000000000000065671254201074400160270ustar00rootroot00000000000000# RTHost - interfacing to IMU chips from desktop systems via an Arduino RTHost includes two RTIMULib-based apps - RTHostIMU and RTHostIMUGL. These apps provide a simple way of using IMU chips with a desktop computer running Ubuntu, Windows or Mac OS X. They will also run on a Raspberry Pi (although RTHostIMUGL will only work a low sample rates). The IMU chip is directly connected to an Arduino (e.g. Arduino Uno) and the Arduino connected to the host system via a USB cable. The RTArduLink protocol runs between the host system and Arduino. The Arduino must be running the RTArduLinkIMU sketch from the RTIMULib-Arduino repo (https://github.com/richards-tech/RTIMULib-Arduino). ### Setting up the desktop #### Ubuntu 14.04 and later Pre-requisites can be installed with: sudo apt-get install qt5-default build-essential cmake #### Mac OS X Download and install Qt from http://www.qt.io/download-open-source/. Qt needs to be added to the PATH variable. The easiest way to do this is to create a file called .profile in ~/ and add the following two lines: export QTDIR=/Users//Qt/5.4/clang_64 export PATH=$PATH:$QTDIR/bin Replace the "5.4" with whatever is the downloaded Qt version and with the appropriate username. To test, try running qmake. If it works, Qt is probably installed correctly. To use the cmake build system, cmake must be downloaded and installed from http://www.cmake.org/download/. #### Windows 7 Windows requires a version of Visual Studio and Qt to be installed. To use the cmake build system, cmake must also be installed. VS2013 is strongly recommended and a free version for most users is available at http://www.visualstudio.com/products/visual-studio-community-vs. Qt can be downloaded and installed from http://www.qt.io/download-open-source/. Make sure it is the VS2013 OpenGL 32 bit version as this is the version used for testing. An environment variable needs to be created: QTDIR=c:\Qt\5.4\mscv2013_opengl for version 5.4 for example. Also add the following to the PATH: %QTDIR%\bin cmake can be downloaded and installed from http://www.cmake.org/download/. ### Setting up the Arduino The Arduino must have the RTArduLinkIMU sketch from the richards-tech RTIMULib-Arduino repo installed. The Arduino must be connected to the desktop using a USB cable. ### Build #### Ubuntu/Mac OS X - Build using cmake Navigate to the RTHost directory. Then run: mkdir build cd build cmake -DQT5=1 .. make sudo make install sudo ldconfig (Ubuntu only) ### Ubuntu/Mac OS X - Build using qmake Qt's qmake can be used to build the apps. Navigate to the RTHostIMU or RTHostIMUGL directory and run: qmake make sudo make install ### Windows - Build using cmake Open a VS2013 x86 native tools command prompt, navigate to the RTHost directory and run: mkdir build cd build cmake -DQT5=1 .. This will generate VS2013 solution files that can be used to build the programs. ### Windows - Build using Visual Studio On Windows, Visual Studio 2013 can be used to build the apps. .sln files are included in the RTHostIMU and RTHostIMUGL directories. ### Running RTHostIMU or RTHostIMUGL When the apps are started, the interface to the Arduino is not enabled. To enable the interface, use the drop-down to select the correct serial port/device. IMU data should be seen shortly after. rtimulib-7.2.1/RTHost/RTArduLinkHost/000077500000000000000000000000001254201074400173475ustar00rootroot00000000000000rtimulib-7.2.1/RTHost/RTArduLinkHost/CMakeLists.txt000066400000000000000000000044051254201074400221120ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014, richards-tech #// #// Permission 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. #// The cmake support was based on work by Moritz Fischer at ettus.com. #// Original copyright notice: # # Copyright 2014 Ettus Research LLC # SET(ARDULINK_SRCS RTArduLinkHost.cpp RTArduLinkUtils.cpp) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../RTSerialPort/src) SET(CMAKE_AUTOMOC ON) IF(DEFINED QT5) FIND_PACKAGE(Qt5Core REQUIRED) IF(WIN32) ADD_LIBRARY(RTArduLinkHost STATIC ${ARDULINK_SRCS}) ELSE(WIN32) ADD_LIBRARY(RTArduLinkHost SHARED ${ARDULINK_SRCS}) ENDIF(WIN32) qt5_use_modules(RTArduLinkHost Core) TARGET_LINK_LIBRARIES(RTArduLinkHost RTSerialPort) ELSE(DEFINED QT5) FIND_PACKAGE(Qt4 REQUIRED) INCLUDE(${QT_USE_FILE}) ADD_DEFINITIONS(${QT_DEFINITIONS}) IF(WIN32) ADD_LIBRARY(RTArduLinkHost STATIC ${ARDULINK_SRCS}) ELSE(WIN32) ADD_LIBRARY(RTArduLinkHost SHARED ${ARDULINK_SRCS}) ENDIF(WIN32) TARGET_LINK_LIBRARIES(RTArduLinkHost RTSerialPort ${QT_LIBRARIES}) ENDIF(DEFINED QT5) INSTALL(TARGETS RTArduLinkHost DESTINATION lib) rtimulib-7.2.1/RTHost/RTArduLinkHost/RTArduLinkDefs.h000066400000000000000000000220771254201074400223110ustar00rootroot00000000000000/////////////////////////////////////////////////////////// // // This file is part of RTArduLink // // Copyright (c) 2014 richards-tech // // Permission 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. // The RTArduLink communications protocol works by exchanging frames across the host <-> subsystem interface. // The structure RTARDULINK_FRAME defines the frame structure. There is a 4 byte header for frame level control // while the remainder is available for higher level message. Note that the structure implies fixed length // buffers which works well with the subsystem however only the number of bytes actually used are transferred // across the interface. // // Note that there is no flow control at the frame level - it is assumed that the higher level interactions host -> subsystem // are window 1 acknowledged transfers so that the maximum possible unprocessed frames is equal to the number // of higher level services. Subsystem -> host transfers are always either responses to host commands or else regular // status updates so the rate from subsystem to host is controlled by configuration. // // The frame integrity is protected by a single byte checksum. To keep things very simple, it is the 2s complement // of the 8 bit sum of all the bytes in the message array. This is used in conjunction with 0xAA and 0x55 bytes // to determine correct sync (in case of lost bytes which should not really happen!). // // Frame sync is obtained by reading bytes until the 0xAA pattern is seen. If the next byte is not 0x55, keep // scanning. If it is, assume this byte is messageLength and the next one is the frameCksm value. Read in the // message array based on messageLength and then calculate the checksum. If the checksum is correct, sync has been // obtained and the message is valid. Otherwise, start looking for an 0xAA value again. #ifndef _RTARDULINKDEFS_H #define _RTARDULINKDEFS_H // Some defines to cope with compiler differences #ifndef __cplusplus #ifndef false #define false 0 #endif #ifndef true #define true 1 #endif typedef unsigned char bool; #endif #ifndef NULL #define NULL 0 #endif // Some general purpose typedefs - used especially for transferring values greater than // 8 bits across the link and avoids endian issues. Assumes processor has 32 bit ints! typedef unsigned char RTARDULINK_UC2[2]; // an array of two unsigned chars typedef unsigned char RTARDULINK_UC4[4]; // an array of four unsigned chars // Port speed codes #define RTARDULINK_PORT_SPEED_OFF 0 // port is unused #define RTARDULINK_PORT_SPEED_9600 1 // 9600 baud #define RTARDULINK_PORT_SPEED_19200 2 // 19200 baud #define RTARDULINK_PORT_SPEED_38400 3 // 38400 baud #define RTARDULINK_PORT_SPEED_57600 4 // 57600 baud #define RTARDULINK_PORT_SPEED_115200 5 // 115200 baud #define RTARDULINK_PORT_SPEED_COUNT 6 // six codes total extern unsigned long RTArduLinkSpeedMap[]; //------------------------------------------------------------------------------------------------------ // // Frame level defs and structure #define RTARDULINK_FRAME_MAX_LEN 64 // maximum possible length of a frame #define RTARDULINK_FRAME_HEADER_LEN 4 // 4 bytes in frame header (must correspond with the structure below!) #define RTARDULINK_MESSAGE_HEADER_LEN 4 // 4 bytes in message header (must correspond with the structure below!) #define RTARDULINK_MESSAGE_MAX_LEN (RTARDULINK_FRAME_MAX_LEN - RTARDULINK_FRAME_HEADER_LEN) // max length of message #define RTARDULINK_DATA_MAX_LEN (RTARDULINK_MESSAGE_MAX_LEN - RTARDULINK_MESSAGE_HEADER_LEN)// max length of data field #define RTARDULINK_MESSAGE_SYNC0 0xAA #define RTARDULINK_MESSAGE_SYNC1 0x55 #define RTARDULINK_MY_ADDRESS 0 // the subsystem address for local processing #define RTARDULINK_BROADCAST_ADDRESS 0xffff // the subsystem address for all subsystems #define RTARDULINK_ADDRESSES 0x1000 // number of addresses // RTARDULINK_MESSAGE is carried in the RTARDULINK_FRAME // // The messageAddress field allows subsystems to be daisy-chained. Valid addresses are 0 to 65534. // Address 65535 is a broadcast and goes to all subsystems. // Every message has the messageType and messageParam bytes but there can be from 0 to 56 bytes of data typedef struct { RTARDULINK_UC2 messageAddress; // subsystem message address unsigned char messageType; // message type code unsigned char messageParam; // an optional parameter to the message type unsigned char data[RTARDULINK_DATA_MAX_LEN]; // the actual data! Length is computed from messageLength. } RTARDULINK_MESSAGE; // RTARDULINK_FRAME is the lowest level structure used across the RTArduLink typedef struct { unsigned char sync0; // sync0 code unsigned char sync1; // sync1 code unsigned char messageLength; // the length of the message in the message field - between 4 and 60 bytes unsigned char frameChecksum; // checksum for frame RTARDULINK_MESSAGE message; // the actual message } RTARDULINK_FRAME; // RTARDULINK_RXFRAME is a type that is used to reassemble a frame from a stream of bytes in conjunction with RTArduLinkReassemble() typedef struct { RTARDULINK_FRAME *frameBuffer; // the frame buffer pointer int length; // current length of frame int bytesLeft; // number of bytes needed to complete bool complete; // true if frame is complete and correct (as far as checksum goes) } RTARDULINK_RXFRAME; // Message types // RTARDULINK_MESSAGE_POLL // // The host should poll the RTArduLink at every RTARDULINK_POLL_INTERVAL. // The subsystem will respond by echoing the poll message as received. #define RTARDULINK_MESSAGE_POLL 0 // poll message // RTARDULINK_MESSAGE_IDENTIFY // // The host can send this message to request an identity string from the subsystem. // Only the messageType field is used in the request host -> subsystem. The subsystem // responds with an identity string in the data field. #define RTARDULINK_MESSAGE_IDENTITY 1 // identity message // RTARDULINK_MESSAGE_DEBUG // // This can be used to send a debug message up to the host. The data field contains a debug message #define RTARDULINK_MESSAGE_DEBUG 2 // debug message // RTARDULINK_MESSAGE_INFO // // This can be used to send an info message up to the host. The data field contains the message #define RTARDULINK_MESSAGE_INFO 3 // info message // RTARDULINK_MESSAGE_ERROR // // This code is returned by the subsystem if it received a message with an illegal message type // The first byte of the data is the error code. The rest of the data field depends on the error. #define RTARDULINK_MESSAGE_ERROR 4 // illegal message type response // RTARDULINK_MESSAGE_ECHO // // This message can be used to test link performance. The addressed subsystem just returns // the entire message to the host. #define RTARDULINK_MESSAGE_ECHO 5 // echo message // RTARDULINK_MESSAGE_CUSTOM // // This is the first message code that should be used for custom messages 16-255 are available. #define RTARDULINK_MESSAGE_CUSTOM 16 // start of custom messages // RTArduLink response codes #define RTARDULINK_RESPONSE_OK 0 // means things worked #define RTARDULINK_RESPONSE_ILLEGAL_COMMAND 1 // not a supported message type, data[1] has offending type #endif // _RTARDULINKDEFS_H rtimulib-7.2.1/RTHost/RTArduLinkHost/RTArduLinkDemoDefs.h000066400000000000000000000052701254201074400231120ustar00rootroot00000000000000/////////////////////////////////////////////////////////// // // This file is part of RTArduLink // // Copyright (c) 2014 richards-tech // // Permission 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. #ifndef RTARDULINKDEMODEFS_H_ #define RTARDULINKDEMODEFS_H_ #include "RTArduLinkUtils.h" #define SERVO_COUNT 2 // 2 servo channels #define PWM_COUNT 3 // 3 pwm channels #define INPUT_COUNT 2 // 2 inputs #define OUTPUT_COUNT 2 // 2 outputs #define SERVO_MIN_VALUE 1000 // min servo value #define SERVO_CTR_VALUE 1500 // center servo value #define SERVO_MAX_VALUE 2000 // max servo value #define PWM_MIN_VALUE 0 // min pwm value #define PWM_CTR_VALUE 128 // center pwm value #define PWM_MAX_VALUE 255 // max pwm value // The command structure is sent from the host to the subsystem typedef struct { RTARDULINK_UC2 servoPos[SERVO_COUNT]; // the servo positions unsigned char pwmValue[PWM_COUNT]; // PWM values unsigned char outputValue[OUTPUT_COUNT]; // the output pin values (true=high, false=low) } RTARDULINKDEMO_COMMAND; // the response structure is sent from the subsystem to the host typedef struct { unsigned char inputValue[INPUT_COUNT]; // the input pin values (true=high, false=low) } RTARDULINKDEMO_RESPONSE; #endif /* RTARDULINKDEMODEFS_H_ */ rtimulib-7.2.1/RTHost/RTArduLinkHost/RTArduLinkHost.cpp000066400000000000000000000331621254201074400226750ustar00rootroot00000000000000/////////////////////////////////////////////////////////// // // This file is part of RTArduLink // // Copyright (c) 2014 richards-tech // // Permission 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. #include "RTArduLinkHost.h" #include "RTArduLinkUtils.h" #include #include "qdebug.h" // Uncomment this symbol to get message trace //#define RTARDULINKHOST_TRACE RTArduLinkHost::RTArduLinkHost(QObject *parent) : QObject(parent) { for (int i = 0; i < RTARDULINKHOST_MAX_PORTS; i++) { m_ports[i].index = i; m_ports[i].port = NULL; } m_running = false; } RTArduLinkHost::~RTArduLinkHost() { if (m_running) end(); } bool RTArduLinkHost::begin() { initSubsystem(); m_backgroundTimerID = startTimer(RTARDULINKHOST_BACKGROUND_INTERVAL); m_identityTimerID = startTimer(RTARDULINKHOST_IDENTITY_INTERVAL); m_pollTimerID = startTimer(RTARDULINKHOST_POLL_INTERVAL); sendIdentifyRequest(); m_running = true; return true; } bool RTArduLinkHost::addPort(int port, QString portName, BaudRateType portSpeed) { RTARDULINKHOST_PORT *portInfo; if ((port < 0) || (port >= RTARDULINKHOST_MAX_PORTS)) return false; portInfo = m_ports + port; if (portInfo->port != NULL) { // must clear old one deletePort(port); } portInfo->settings.BaudRate = portSpeed; portInfo->settings.DataBits = DATA_8; portInfo->settings.FlowControl = FLOW_OFF; portInfo->settings.Parity = PAR_NONE; portInfo->settings.StopBits = STOP_1; portInfo->settings.Timeout_Millisec = -1; portInfo->port = new QextSerialPort(portName, portInfo->settings, QextSerialPort::Polling); RTArduLinkRXFrameInit(&(portInfo->RXFrame), &(portInfo->RXFrameBuffer)); portInfo->TXFrameBuffer.sync0 = RTARDULINK_MESSAGE_SYNC0; portInfo->TXFrameBuffer.sync1 = RTARDULINK_MESSAGE_SYNC1; connect(portInfo->port, SIGNAL(readyRead()), this, SLOT(readyRead())); portInfo->open = false; return true; } void RTArduLinkHost::deletePort(int port) { RTARDULINKHOST_PORT *portInfo; if ((port < 0) || (port >= RTARDULINKHOST_MAX_PORTS)) return; portInfo = m_ports + port; if (portInfo->port == NULL) return; closePort(portInfo); delete portInfo->port; portInfo->port = NULL; } void RTArduLinkHost::end() { for (int i = 0; i < RTARDULINKHOST_MAX_PORTS; i++) deletePort(i); killTimer(m_backgroundTimerID); killTimer(m_identityTimerID); killTimer(m_pollTimerID); initSubsystem(); m_running = false; } void RTArduLinkHost::initSubsystem() { RTARDULINKHOST_SUBSYSTEM *subsystem; int port, address; for (port = 0; port < RTARDULINKHOST_MAX_PORTS; port++) { subsystem = m_subsystem[port]; for (address = 0; address < RTARDULINK_ADDRESSES; address++, subsystem++) { subsystem->address = address; subsystem->active = false; subsystem->identity[0] = 0; subsystem->pollsIn = 0; subsystem->pollsOut = 0; emitStatus(port, address); } } } void RTArduLinkHost::timerEvent(QTimerEvent *event) { RTARDULINKHOST_PORT *portInfo; if (event->timerId() == m_backgroundTimerID) { processBackground(); for (int i = 0; i < RTARDULINKHOST_MAX_PORTS; i++) { portInfo = m_ports + i; if (portInfo->port == NULL) continue; if (!portInfo->port->isOpen()) { if (portInfo->open) emit RTArduLinkPortClosed(i); portInfo->open = false; if (portInfo->port->open(QIODevice::ReadWrite)) { emit RTArduLinkPortOpen(i); portInfo->open = true; } } } readyRead(); } else if (event->timerId() == m_identityTimerID) { sendIdentifyRequest(); } else if (event->timerId() == m_pollTimerID) { sendPollRequest(); } } void RTArduLinkHost::readyRead() { QByteArray data; int bytesAvailable; unsigned char *charData; RTARDULINKHOST_PORT *portInfo; for (int index = 0; index < RTARDULINKHOST_MAX_PORTS; index++) { portInfo = m_ports + index; if (portInfo->port == NULL) continue; if (!portInfo->port->isOpen()) { if (portInfo->open) emit RTArduLinkPortClosed(index); portInfo->open = false; continue; } bytesAvailable = portInfo->port->bytesAvailable(); if (bytesAvailable == -1) { // port must have closed or had an error closePort(portInfo); continue; } if (bytesAvailable) { data = portInfo->port->readAll(); charData = (unsigned char *)data.constData(); for (int i = 0; i < data.length(); i++) { if (!RTArduLinkReassemble(&(portInfo->RXFrame), *charData++)) qDebug() << QString("Reassembly error on port %1").arg(index); if (portInfo->RXFrame.complete) { processReceivedMessage(portInfo); RTArduLinkRXFrameInit(&(portInfo->RXFrame), &(portInfo->RXFrameBuffer)); } } } } } void RTArduLinkHost::processReceivedMessage(RTARDULINKHOST_PORT *portInfo) { RTARDULINK_MESSAGE *message; // a pointer to the message part of the frame unsigned int address; RTARDULINKHOST_SUBSYSTEM *subsystem; emit RTArduLinkPortRX(portInfo->index); if ((portInfo->RXFrameBuffer.messageLength < RTARDULINK_MESSAGE_HEADER_LEN) || (portInfo->RXFrameBuffer.messageLength > RTARDULINK_MESSAGE_MAX_LEN)) { qDebug() << QString("Received message with incorrect length %1").arg(portInfo->RXFrameBuffer.messageLength); return; } message = &(portInfo->RXFrameBuffer.message); // get the message pointer address = RTArduLinkConvertUC2ToUInt(message->messageAddress); if (address == RTARDULINK_BROADCAST_ADDRESS) { qDebug() << "Received message with broadcast address"; return; } subsystem = m_subsystem[portInfo->index] + address; #ifdef RTARDULINKHOST_TRACE qDebug() << "Received " << message->messageType << " from port " << portInfo->index << " address " << address; #endif switch (message->messageType) { case RTARDULINK_MESSAGE_POLL: subsystem->pollsIn++; emitStatus(portInfo->index, address); if (!subsystem->active && ((int)strlen(subsystem->identity) > 0)) { #ifdef RTARDULINKHOST_TRACE qDebug() << QString("Subsystem port %1 address %2 active").arg(portInfo->index).arg(address); #endif subsystem->active = true; subsystem->pollsIn = 0; subsystem->pollsOut = 0; emitStatus(portInfo->index, address); } subsystem->waitingForPoll = false; break; case RTARDULINK_MESSAGE_IDENTITY: subsystem->waitingForIdentity = false; message->data[RTARDULINK_DATA_MAX_LEN - 1] = 0; // make sure zero terminated strcpy(subsystem->identity, (const char *)message->data); #ifdef RTARDULINKHOST_TRACE qDebug() << QString("Received identity %1") .arg(subsystem->identity); #endif emitStatus(portInfo->index, address); break; case RTARDULINK_MESSAGE_DEBUG: message->data[RTARDULINK_DATA_MAX_LEN - 1] = 0; // make sure zero terminated qDebug() << QString("Debug message from port %1, address %2: %3") .arg(portInfo->index).arg(address).arg((char *)message->data); break; default: processCustomMessage(portInfo,address, message->messageType, message->messageParam, message->data, portInfo->RXFrameBuffer.messageLength - RTARDULINK_MESSAGE_HEADER_LEN); break; } } void RTArduLinkHost::sendIdentifyRequest() { int port, address; RTARDULINKHOST_SUBSYSTEM *subsystem; // check if any active subsystem failed to respond for (port = 0; port < RTARDULINKHOST_MAX_PORTS; port++) { subsystem = m_subsystem[port]; for (address = 0; address < RTARDULINK_ADDRESSES; address++, subsystem++) { if (subsystem->active && subsystem->waitingForIdentity) { qDebug() << QString("Subsystem %1 failed to re-identify") .arg(subsystem->identity); subsystem->active = false; subsystem->identity[0] = 0; emitStatus(port, address); } } } for (int i = 0; i < RTARDULINKHOST_MAX_PORTS; i++) sendMessage(i, RTARDULINK_BROADCAST_ADDRESS, RTARDULINK_MESSAGE_IDENTITY, 0, NULL, 0); for (port = 0; port < RTARDULINKHOST_MAX_PORTS; port++) { subsystem = m_subsystem[port]; for (address = 0; address < RTARDULINK_ADDRESSES; address++, subsystem++) subsystem->waitingForIdentity = true; } // qDebug() << "Sent identity request"; } void RTArduLinkHost::sendPollRequest() { int port, address; RTARDULINKHOST_SUBSYSTEM *subsystem; // check if any active subsystem failed to respond for (port = 0; port < RTARDULINKHOST_MAX_PORTS; port++) { subsystem = m_subsystem[port]; for (address = 0; address < RTARDULINK_ADDRESSES; address++, subsystem++) { if (subsystem->active && subsystem->waitingForPoll) { qDebug() << QString("Subsystem %1 failed to respond to poll") .arg(subsystem->identity); subsystem->active = false; emitStatus(port, address); } } } for (int i = 0; i < RTARDULINKHOST_MAX_PORTS; i++) sendMessage(i, RTARDULINK_BROADCAST_ADDRESS, RTARDULINK_MESSAGE_POLL, 0, NULL, 0); for (port = 0; port < RTARDULINKHOST_MAX_PORTS; port++) { subsystem = m_subsystem[port]; for (address = 0; address < RTARDULINK_ADDRESSES; address++, subsystem++) { subsystem->pollsOut++; subsystem->waitingForPoll = true; } } // qDebug() << "Sent poll"; } bool RTArduLinkHost::sendMessage(int port, unsigned int messageAddress, unsigned char messageType, unsigned char messageParam, unsigned char *data, int length) { RTARDULINK_MESSAGE *message; qint64 bytesWritten; qint64 totalLength; unsigned char *framePtr; RTARDULINKHOST_PORT *portInfo; if ((port < 0) || (port >= RTARDULINKHOST_MAX_PORTS)) return false; portInfo = m_ports + port; if (portInfo->port == NULL) return false; if (!portInfo->port->isOpen()) return false; if (length > RTARDULINK_DATA_MAX_LEN) return false; message = &(portInfo->TXFrameBuffer.message); #ifdef RTARDULINKHOST_TRACE qDebug() << "Sending " << message->messageType << " to port " << portInfo->index << " address " << messageAddress; #endif portInfo->TXFrameBuffer.messageLength = length + RTARDULINK_MESSAGE_HEADER_LEN; RTArduLinkConvertIntToUC2(messageAddress, message->messageAddress); message->messageType = messageType; message->messageParam = messageParam; if (data != NULL) memcpy(message->data, data, length); RTArduLinkSetChecksum(&portInfo->TXFrameBuffer); totalLength = RTARDULINK_FRAME_HEADER_LEN + RTARDULINK_MESSAGE_HEADER_LEN + length; framePtr = (unsigned char *)&portInfo->TXFrameBuffer; while (totalLength > 0) { bytesWritten = portInfo->port->write((const char *)framePtr, totalLength); if (bytesWritten == -1) { closePort(portInfo); return false; } totalLength -= bytesWritten; framePtr += bytesWritten; } emit RTArduLinkPortTX(port); return true; } void RTArduLinkHost::closePort(RTARDULINKHOST_PORT *portInfo) { if (portInfo->port == NULL) return; if (portInfo->port->isOpen()) { portInfo->port->close(); if (portInfo->open) emit RTArduLinkPortClosed(portInfo->index); } portInfo->open = false; } void RTArduLinkHost::emitStatus(int port, int address) { RTARDULINKHOST_SUBSYSTEM *subsystem; subsystem = m_subsystem[port] + address; if (subsystem->active) emit RTArduLinkStatus(port, address, true, subsystem->identity, subsystem->pollsIn, subsystem->pollsOut); else emit RTArduLinkStatus(port, address, false, subsystem->identity, 0, 0); } // Dummy functions to satisfy linker if not overridden void RTArduLinkHost::processCustomMessage(RTARDULINKHOST_PORT *, unsigned int, unsigned char, unsigned char, unsigned char *, int ) {} rtimulib-7.2.1/RTHost/RTArduLinkHost/RTArduLinkHost.h000066400000000000000000000201101254201074400223270ustar00rootroot00000000000000/////////////////////////////////////////////////////////// // // This file is part of RTArduLink // // Copyright (c) 2014 richards-tech // // Permission 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. #ifndef RTARDULINKHOST_H #define RTARDULINKHOST_H // RTArduLinkHost is the common RTArduLink API and library for host processors #include "RTArduLinkDefs.h" #include "qextserialport.h" #include // This value defines how manu USB/serial ports are supported // This can be modified if required #define RTARDULINKHOST_MAX_PORTS 8 // max supported COM ports // Poll Intervals and timeouts // // Note - all intervals are in units of mS. #define RTARDULINKHOST_POLL_INTERVAL 1000 // time between polls in normal operation #define RTARDULINKHOST_POLL_TIMEOUT (4 * RTARDULINKHOST_POLL_INTERVAL) // timeout interval after which link is declared down #define RTARDULINKHOST_BACKGROUND_INTERVAL 5 // background loop interval #define RTARDULINKHOST_IDENTITY_INTERVAL (5 * 1000) // identity request interval // RTARDULINKHOST_SUBSYSTEM maintains state for each subsystem port/address combination typedef struct { int address; // address of this entry bool active; // if polling operational bool waitingForIdentity; // if still waiting for identity bool waitingForPoll; // if still waiting for poll response char identity[RTARDULINK_DATA_MAX_LEN]; // identity string qint64 pollsOut; // number of polls sent qint64 pollsIn; // number of polls received } RTARDULINKHOST_SUBSYSTEM; // RTARDULINKHOST_PORT maintains state for each USB/serial port typedef struct { int index; // index of port bool open; // true if open QextSerialPort *port; // serial port driver PortSettings settings; // the settings structure RTARDULINK_RXFRAME RXFrame; // structure to maintain receive frame state RTARDULINK_FRAME RXFrameBuffer; // used to assemble received frames RTARDULINK_FRAME TXFrameBuffer; // used to assemble frames for transmission } RTARDULINKHOST_PORT; // The RTArduLinkHost object itself. This should be sub-classed // in a custom application. class RTArduLinkHost : public QObject { Q_OBJECT public: RTArduLinkHost(QObject *parent); virtual ~RTArduLinkHost(); //---------------------------------------------------------- // // Public functions. These can be called to configure the library // and also send messages. // begin() is used to start processing RTArduLink. Any configured ports // will be opened and polling initiated bool begin(); // start comms // addPort() adds a new USB/serial port for processing. // // port = the subsystem port number to assign (0 - (RTARDULINKHOST_MAX_PORTS - 1)) // portName = the name of the port to open (eg COM3, /dev/ttyUSB0) // portSpeed = the serial port speed // // returns true if the port was added, false if the addPort failed. bool addPort(int port, QString portName, BaudRateType portSpeed); // add a serial port // deletePort() deletes a previously added port at index port. void deletePort(int port); // delete a serial port // end() terminates RTArduLink processing and removes all ports. void end(); // end communications // sendMessage() can be called to send a message to a subsystem // // port = subsystem port index to send the message on // messageAddress = the subsystem address of the target system // messageType = the messageType codes // messageParam = a parameter for the type // data = a pointer to the data field (NULL if there isn't one) // length = length of data in data field (0 - 56) // // Returns true if the message was sent, false if there was an error bool sendMessage(int port, unsigned int messageAddress, unsigned char messageType, unsigned char messageParam, unsigned char *data, int length); public slots: void readyRead(); signals: //---------------------------------------------------------- // // Signals. These are emitted when various conditions occur. They can be connected // to slots in the custom app code. // RTArduLinkStatus is emitted when a subsystem's status has changed or a poll event has occurred // // port = subsystem port index // address = subsystem address // active = true if the RTArduLink to this subsystem is active, false if not // identity = pointer to the identity string supplied by the subsystem // pollsIn = number of polls received while active // pollsOut = number of polls send while active void RTArduLinkStatus(int port, int address, bool active, QString identity, qint64 pollsIn, qint64 pollsOut); // RTArduLinkPortOpen is emitted when a USB/serial port has been opened void RTArduLinkPortOpen(int port); // RTArduLinkPortClosed is emitted when a USB/serial port has been closed void RTArduLinkPortClosed(int port); // RTArduLinkPortTX is emitted when a message is transmitted on a port void RTArduLinkPortTX(int port); // RTArduLinkPortRX is emitted when a message is received on a port void RTArduLinkPortRX(int port); protected: //---------------------------------------------------------- // // Overrides. These functions can be overridden in the sub-class // processCustomMessage() passes a received message to the custom code // The parameters include a pointer to the RTARDULINKHOST_PORT on which // the message was received and the various message fields virtual void processCustomMessage(RTARDULINKHOST_PORT *portInfo, unsigned int messageAddress, unsigned char messageType, unsigned char messageParam, unsigned char *data, int dataLength); // processBackground() is called once per background loop and custom code can perform // any regular background processing using this call virtual void processBackground() {}; //---------------------------------------------------------- void timerEvent(QTimerEvent *event); RTARDULINKHOST_SUBSYSTEM m_subsystem[RTARDULINKHOST_MAX_PORTS][RTARDULINK_ADDRESSES]; // the subsystem array RTARDULINKHOST_PORT m_ports[RTARDULINKHOST_MAX_PORTS]; // the port array bool m_running; private: void initSubsystem(); void processReceivedMessage(RTARDULINKHOST_PORT *portInfo); void sendIdentifyRequest(); void sendPollRequest(); void emitStatus(int port, int address); void closePort(RTARDULINKHOST_PORT *portInfo); int m_backgroundTimerID; int m_identityTimerID; int m_pollTimerID; }; #endif // RTARDULINKHOST_H rtimulib-7.2.1/RTHost/RTArduLinkHost/RTArduLinkHost.pri000066400000000000000000000030011254201074400226720ustar00rootroot00000000000000#/////////////////////////////////////////////////////////// #// #// This file is part of RTArduLink #// #// Copyright (c) 2014 richards-tech #// #// Permission 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. INCLUDEPATH += $$PWD DEPENDPATH += $$PWD HEADERS += $$PWD/RTArduLinkDefs.h \ $$PWD/RTArduLinkDemoDefs.h \ $$PWD/RTArduLinkHost.h \ $$PWD/RTArduLinkUtils.h SOURCES += $$PWD/RTArduLinkHost.cpp \ $$PWD/RTArduLinkUtils.cpp rtimulib-7.2.1/RTHost/RTArduLinkHost/RTArduLinkUtils.cpp000066400000000000000000000131271254201074400230570ustar00rootroot00000000000000/////////////////////////////////////////////////////////// // // This file is part of RTArduLink // // Copyright (c) 2014 richards-tech // // Permission 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. #include "RTArduLinkUtils.h" // RTArduLinkRXFrameInit initializes the structure for a new frame using frameBuffer for storage void RTArduLinkRXFrameInit(RTARDULINK_RXFRAME *RXFrame, RTARDULINK_FRAME *frameBuffer) { RXFrame->complete = false; RXFrame->length = 0; RXFrame->bytesLeft = 0; RXFrame->frameBuffer = frameBuffer; } // RTArduLinkReassemble takes a sequence of received bytes and tries to complete a frame. It returns true if ok // false if error. The caller can determine if the frame is complete by checking the complete flag. bool RTArduLinkReassemble(RTARDULINK_RXFRAME *RXFrame, unsigned char data) { bool flag = true; ((unsigned char *)(RXFrame->frameBuffer))[RXFrame->length] = data; // save byte in correct place switch (RXFrame->length) { case 0: // waiting for sync0 if (RXFrame->frameBuffer->sync0 == RTARDULINK_MESSAGE_SYNC0) { RXFrame->length = 1; } break; case 1: // waiting for sync1 if (RXFrame->frameBuffer->sync1 == RTARDULINK_MESSAGE_SYNC1) { RXFrame->length = 2; } else { RXFrame->length = 0; // try again if not correct two byte sequence } break; case 2: // should be message length if ((RXFrame->frameBuffer->messageLength <= RTARDULINK_MESSAGE_MAX_LEN) && (RXFrame->frameBuffer->messageLength >= RTARDULINK_MESSAGE_HEADER_LEN)) { RXFrame->length = 3; RXFrame->bytesLeft = RXFrame->frameBuffer->messageLength + 1; // +1 to allow for the checksum } else { RXFrame->length = 0; // discard this and resync frame flag = false; } break; default: RXFrame->length++; RXFrame->bytesLeft--; if (RXFrame->bytesLeft == 0) { // a complete frame! if (!RTArduLinkCheckChecksum(RXFrame->frameBuffer)) { RTArduLinkRXFrameInit(RXFrame, RXFrame->frameBuffer); flag = false; // flag the error } // this is a valid frame (so far) RXFrame->complete = true; } break; } return flag; } // RTArduLinkSetChecksum correctly sets the checksum field on an RCP frame prior to transmission // void RTArduLinkSetChecksum(RTARDULINK_FRAME *frame) { int cksm; int i; unsigned char *data; for (i = 0, cksm = 0, data = (unsigned char *)&(frame->message); i < frame->messageLength; i++) cksm += *data++; // add up checksum frame->frameChecksum = (255 - cksm) + 1; // 2s complement } // RTArduLinkCheckChecksum checks a received frame's checksum. // // It adds up all the bytes from the nFrameCksm byte to the end of the frame. The result should be 0. bool RTArduLinkCheckChecksum(RTARDULINK_FRAME *frame) { int length; int i; unsigned char *data; unsigned char cksm; length = frame->messageLength + 1; cksm = 0; data = (unsigned char *)&(frame->frameChecksum); for (i = 0; i < length; i++) cksm += *data++; return cksm == 0; } // UC2 and UC4 Conversion routines // long RTArduLinkConvertUC4ToLong(RTARDULINK_UC4 UC4) { long val; val = UC4[3]; val += (long)UC4[2] << 8; val += (long)UC4[1] << 16; val += (long)UC4[0] << 24; return val; } void RTArduLinkConvertLongToUC4(long val, RTARDULINK_UC4 UC4) { UC4[3] = val & 0xff; UC4[2] = (val >> 8) & 0xff; UC4[1] = (val >> 16) & 0xff; UC4[0] = (val >> 24) & 0xff; } int RTArduLinkConvertUC2ToInt(RTARDULINK_UC2 UC2) { int val; val = UC2[1]; val += (int)UC2[0] << 8; return val; } unsigned int RTArduLinkConvertUC2ToUInt(RTARDULINK_UC2 UC2) { unsigned int val; val = UC2[1]; val += (unsigned int)UC2[0] << 8; return val; } void RTArduLinkConvertIntToUC2(int val, RTARDULINK_UC2 UC2) { UC2[1] = val & 0xff; UC2[0] = (val >> 8) & 0xff; } void RTArduLinkCopyUC2(RTARDULINK_UC2 destUC2, RTARDULINK_UC2 sourceUC2) { destUC2[0] = sourceUC2[0]; destUC2[1] = sourceUC2[1]; } rtimulib-7.2.1/RTHost/RTArduLinkHost/RTArduLinkUtils.h000066400000000000000000000050301254201074400225160ustar00rootroot00000000000000/////////////////////////////////////////////////////////// // // This file is part of RTArduLink // // Copyright (c) 2014 richards-tech // // Permission 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. #ifndef _RTARDULINKUTILS_H #define _RTARDULINKUTILS_H #include "RTArduLinkDefs.h" // Function defs void RTArduLinkRXFrameInit(RTARDULINK_RXFRAME *RXFrame, RTARDULINK_FRAME *frameBuffer); // initializes RTARDULINK_RXFRAME for a new frame bool RTArduLinkReassemble(RTARDULINK_RXFRAME *RXFrame, unsigned char data); // adds a byte to the reassembly, returns false if error // Checksum utilities void RTArduLinkSetChecksum(RTARDULINK_FRAME *frame); // sets the checksum field prior to transmission bool RTArduLinkCheckChecksum(RTARDULINK_FRAME *frame); // checks the checksum field after reception - returns true if ok, false if error // Type conversion utilities long RTArduLinkConvertUC4ToLong(RTARDULINK_UC4 uc4); // converts a 4 byte array to a signed long void RTArduLinkConvertLongToUC4(long val, RTARDULINK_UC4 uc4); // converts a long to a four byte array int RTArduLinkConvertUC2ToInt(RTARDULINK_UC2 uc2); // converts a 2 byte array to a signed integer unsigned int RTArduLinkConvertUC2ToUInt(RTARDULINK_UC2 uc2);// converts a 2 byte array to an unsigned integer void RTArduLinkConvertIntToUC2(int val, RTARDULINK_UC2 uc2);// converts an integer to a two byte array void RTArduLinkCopyUC2(RTARDULINK_UC2 destUC2, RTARDULINK_UC2 sourceUC2); // copies a UC2 #endif // _RTARDULINKUTILS_H rtimulib-7.2.1/RTHost/RTHostIMU/000077500000000000000000000000001254201074400162705ustar00rootroot00000000000000rtimulib-7.2.1/RTHost/RTHostIMU/CMakeLists.txt000066400000000000000000000044611254201074400210350ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014, richards-tech #// #// Permission 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. #// The cmake support was based on work by Moritz Fischer at ettus.com. #// Original copyright notice: # # Copyright 2014 Ettus Research LLC # SET(HOST_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/RTHostIMU.cpp ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) SET(CMAKE_AUTOMOC ON) IF(DEFINED QT5) FIND_PACKAGE(Qt5Widgets) FIND_PACKAGE(Qt5Gui) qt5_wrap_ui(UI_HEADERS RTHostIMU.ui) ADD_EXECUTABLE(RTHostIMU ${HOST_SRCS} ${UI_HEADERS}) TARGET_LINK_LIBRARIES(RTHostIMU RTIMULib RTHostIMUCommon RTArduLinkHost ${Qt5Core_QTMAIN_LIBRARIES}) qt5_use_modules(RTHostIMU Widgets Gui) ELSE(DEFINED QT5) FIND_PACKAGE(Qt4 REQUIRED) INCLUDE(${QT_USE_FILE}) ADD_DEFINITIONS(${QT_DEFINITIONS}) QT4_WRAP_UI(UI_HEADERS RTHostIMU.ui) ADD_EXECUTABLE(RTHostIMU ${HOST_SRCS} ${UI_HEADERS}) TARGET_LINK_LIBRARIES(RTHostIMU RTIMULib RTHostIMUCommon RTArduLinkHost ${QT_LIBRARIES}) ENDIF(DEFINED QT5) INSTALL(TARGETS RTHostIMU DESTINATION bin) rtimulib-7.2.1/RTHost/RTHostIMU/RTHostIMU.cpp000066400000000000000000000467501254201074400205460ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include #include #include #include "qextserialenumerator.h" #include "RTHostIMU.h" #include "SelectFusionDlg.h" #include "RTHostIMUThread.h" #include "RTHostIMUClient.h" #include "AccelCalDlg.h" #include "MagCalDlg.h" #define RATE_TIMER_INTERVAL 2 RTHostIMU::RTHostIMU() : QMainWindow() { // This is some normal Qt GUI stuff ui.setupUi(this); layoutWindow(); layoutStatusBar(); // This code connects up signals from the GUI connect(ui.actionExit, SIGNAL(triggered()), this, SLOT(close())); connect(ui.actionSelectFusionAlgorithm, SIGNAL(triggered()), this, SLOT(onSelectFusionAlgorithm())); connect(ui.actionCalibrateAccelerometers, SIGNAL(triggered()), this, SLOT(onCalibrateAccelerometers())); connect(ui.actionCalibrateMagnetometers, SIGNAL(triggered()), this, SLOT(onCalibrateMagnetometers())); connect(m_enableGyro, SIGNAL(stateChanged(int)), this, SLOT(onEnableGyro(int))); connect(m_enableAccel, SIGNAL(stateChanged(int)), this, SLOT(onEnableAccel(int))); connect(m_enableCompass, SIGNAL(stateChanged(int)), this, SLOT(onEnableCompass(int))); connect(m_enableDebug, SIGNAL(stateChanged(int)), this, SLOT(onEnableDebug(int))); // create the imu thread and connect up the signal m_imuThread = new RTHostIMUThread(); connect(m_imuThread, SIGNAL(newIMUData(const RTIMU_DATA&)), this, SLOT(newIMUData(const RTIMU_DATA&)), Qt::DirectConnection); connect(m_imuThread, SIGNAL(IMURunning()), this, SLOT(IMURunning()), Qt::DirectConnection); connect(this, SIGNAL(newIMU()), m_imuThread, SLOT(newIMU())); m_imuThread->resumeThread(); // This value allows a sample rate to be calculated m_sampleCount = 0; // start some timers to get things going m_rateTimer = startTimer(RATE_TIMER_INTERVAL * 1000); // Only update the display 10 times per second to keep CPU reasonable m_displayTimer = startTimer(100); m_fusionType->setText(RTFusion::fusionName(m_imuThread->getSettings()->m_fusionType)); } RTHostIMU::~RTHostIMU() { } void RTHostIMU::onSelectFusionAlgorithm() { SelectFusionDlg dlg(m_imuThread->getSettings(), this); if (dlg.exec() == QDialog::Accepted) { emit newIMU(); m_fusionType->setText(RTFusion::fusionName(m_imuThread->getSettings()->m_fusionType)); } } void RTHostIMU::onCalibrateAccelerometers() { m_imuThread->getIMU()->setAccelCalibrationMode(true); AccelCalDlg dlg(this, m_imuThread->getSettings()); connect(m_imuThread, SIGNAL(newIMUData(const RTIMU_DATA&)), &dlg, SLOT(newIMUData(const RTIMU_DATA&)), Qt::DirectConnection); if (dlg.exec() == QDialog::Accepted) { } disconnect(m_imuThread, SIGNAL(newIMUData(const RTIMU_DATA&)), &dlg, SLOT(newIMUData(const RTIMU_DATA&))); emit newIMU(); } void RTHostIMU::onCalibrateMagnetometers() { m_imuThread->getIMU()->setCompassCalibrationMode(true); MagCalDlg dlg(this, m_imuThread->getSettings()); connect(m_imuThread, SIGNAL(newIMUData(const RTIMU_DATA&)), &dlg, SLOT(newIMUData(const RTIMU_DATA&)), Qt::DirectConnection); if (dlg.exec() == QDialog::Accepted) { } disconnect(m_imuThread, SIGNAL(newIMUData(const RTIMU_DATA&)), &dlg, SLOT(newIMUData(const RTIMU_DATA&))); emit newIMU(); } void RTHostIMU::newIMUData(const RTIMU_DATA& data) { m_imuData = data; m_sampleCount++; } void RTHostIMU::onEnableGyro(int state) { if (m_imuThread->getIMU() != NULL) m_imuThread->getIMU()->setGyroEnable(state == Qt::Checked); } void RTHostIMU::onEnableAccel(int state) { if (m_imuThread->getIMU() != NULL) m_imuThread->getIMU()->setAccelEnable(state == Qt::Checked); } void RTHostIMU::onEnableCompass(int state) { if (m_imuThread->getIMU() != NULL) m_imuThread->getIMU()->setCompassEnable(state == Qt::Checked); } void RTHostIMU::onEnableDebug(int state) { if (m_imuThread->getIMU() != NULL) m_imuThread->getIMU()->setDebugEnable(state == Qt::Checked); } void RTHostIMU::closeEvent(QCloseEvent *) { killTimer(m_displayTimer); killTimer(m_rateTimer); m_imuThread->exitThread(); } void RTHostIMU::timerEvent(QTimerEvent *event) { if (event->timerId() == m_displayTimer) { // Update the GUI m_gyroX->setText(QString::number(m_imuData.gyro.x(), 'f', 6)); m_gyroY->setText(QString::number(m_imuData.gyro.y(), 'f', 6)); m_gyroZ->setText(QString::number(m_imuData.gyro.z(), 'f', 6)); m_accelX->setText(QString::number(m_imuData.accel.x(), 'f', 6)); m_accelY->setText(QString::number(m_imuData.accel.y(), 'f', 6)); m_accelZ->setText(QString::number(m_imuData.accel.z(), 'f', 6)); m_compassX->setText(QString::number(m_imuData.compass.x(), 'f', 6)); m_compassY->setText(QString::number(m_imuData.compass.y(), 'f', 6)); m_compassZ->setText(QString::number(m_imuData.compass.z(), 'f', 6)); m_fusionPoseX->setText(QString::number(m_imuData.fusionPose.x() * RTMATH_RAD_TO_DEGREE, 'f', 6)); m_fusionPoseY->setText(QString::number(m_imuData.fusionPose.y() * RTMATH_RAD_TO_DEGREE, 'f', 6)); m_fusionPoseZ->setText(QString::number(m_imuData.fusionPose.z() * RTMATH_RAD_TO_DEGREE, 'f', 6)); m_fusionQPoseScalar->setText(QString::number(m_imuData.fusionQPose.scalar(), 'f', 6)); m_fusionQPoseX->setText(QString::number(m_imuData.fusionQPose.x(), 'f', 6)); m_fusionQPoseY->setText(QString::number(m_imuData.fusionQPose.y(), 'f', 6)); m_fusionQPoseZ->setText(QString::number(m_imuData.fusionQPose.z(), 'f', 6)); updateComRXTX(m_comRXLabel, false); updateComRXTX(m_comTXLabel, false); m_accelMagnitude->setText(QString::number(m_imuData.accel.length(), 'f', 6)); m_compassMagnitude->setText(QString::number(m_imuData.compass.length(), 'f', 6)); RTVector3 residuals = m_imuThread->getIMU()->getAccelResiduals(); m_accelResidualX->setText(QString::number(residuals.x(), 'f', 6)); m_accelResidualY->setText(QString::number(residuals.y(), 'f', 6)); m_accelResidualZ->setText(QString::number(residuals.z(), 'f', 6)); } else { // Update the sample rate float rate = (float)m_sampleCount / (float(RATE_TIMER_INTERVAL)); m_sampleCount = 0; m_rateStatus->setText(QString("Sample rate: %1 per second").arg(rate)); if (m_imuThread->getIMU() == NULL) { m_calStatus->setText("No IMU found"); } else { m_calStatus->setText(QString("Accel %1 : Compass %2 : Ellipsoid %3") .arg(m_imuThread->getIMU()->getAccelCalibrationValid() ? "calibrated" : "uncalibrated") .arg(m_imuThread->getIMU()->getCompassCalibrationValid() ? "calibrated" : "uncalibrated") .arg(m_imuThread->getIMU()->getCompassCalibrationEllipsoidValid() ? "in use" : "not in use")); } if (m_imuThread->getIMU() != NULL) { m_imuType->setText(m_imuThread->getIMU()->IMUName()); if (!m_imuThread->getIMU()->IMUGyroBiasValid()) m_biasStatus->setText("Gyro bias being calculated - keep IMU still!"); else m_biasStatus->setText("Gyro bias valid"); } } } void RTHostIMU::layoutWindow() { QVBoxLayout *vLayout = new QVBoxLayout(); vLayout->setContentsMargins(3, 3, 3, 3); vLayout->setSpacing(1); vLayout->addSpacing(10); // Set up com port line and controls QHBoxLayout *commLayout = new QHBoxLayout(); commLayout->setAlignment(Qt::AlignLeft); vLayout->addLayout(commLayout); m_comLabel = new QLabel("Com port: "); m_comLabel->setAlignment(Qt::AlignLeft); m_comLabel->setAlignment(Qt::AlignCenter); commLayout->addWidget(m_comLabel); m_comPort = new QComboBox(this); populateComPorts(); connect(m_comPort, SIGNAL(currentIndexChanged(int)), this, SLOT(newComSetting(int))); commLayout->addWidget(m_comPort); commLayout->addSpacing(10); QLabel *label = new QLabel("Port speed: "); label->setAlignment(Qt::AlignLeft); label->setAlignment(Qt::AlignCenter); commLayout->addWidget(label); m_comSpeed = new QComboBox(this); for (int i = 0; i < 5; i++) m_comSpeed->insertItem(i, speedString[i]); m_comSpeed->setCurrentIndex(4); commLayout->addWidget(m_comSpeed); commLayout->addSpacing(10); connect(m_comSpeed, SIGNAL(currentIndexChanged(int)), this, SLOT(newComSetting(int))); m_comRXLabel = new QLabel(QString("RX")); m_comRXLabel->setAlignment(Qt::AlignLeft); m_comRXLabel->setAlignment(Qt::AlignCenter); m_comRXLabel->setMaximumHeight(20); commLayout->addWidget(m_comRXLabel); commLayout->addSpacing(10); m_comTXLabel = new QLabel(QString("TX")); m_comTXLabel->setAlignment(Qt::AlignLeft); m_comTXLabel->setAlignment(Qt::AlignCenter); m_comTXLabel->setMaximumHeight(20); commLayout->addWidget(m_comTXLabel); vLayout->addSpacing(10); // Set up subsystem QHBoxLayout *subLayout = new QHBoxLayout(); subLayout->setAlignment(Qt::AlignLeft); vLayout->addLayout(subLayout); label = new QLabel("Subsystem: "); label->setAlignment(Qt::AlignLeft); label->setAlignment(Qt::AlignCenter); subLayout->addWidget(label); m_subsystem = new QLabel(); m_subsystem->setMinimumWidth(200); subLayout->addWidget(m_subsystem); vLayout->addSpacing(10); QHBoxLayout *imuLayout = new QHBoxLayout(); vLayout->addLayout(imuLayout); imuLayout->setAlignment(Qt::AlignLeft); imuLayout->addWidget(new QLabel("IMU type: ")); m_imuType = new QLabel(); imuLayout->addWidget(m_imuType); vLayout->addSpacing(10); QHBoxLayout *biasLayout = new QHBoxLayout(); vLayout->addLayout(biasLayout); biasLayout->setAlignment(Qt::AlignLeft); biasLayout->addWidget(new QLabel("Gyro bias status: ")); m_biasStatus = new QLabel(); biasLayout->addWidget(m_biasStatus); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Fusion state (quaternion): ")); QHBoxLayout *dataLayout = new QHBoxLayout(); dataLayout->setAlignment(Qt::AlignLeft); m_fusionQPoseScalar = getFixedPanel("1"); m_fusionQPoseX = getFixedPanel("0"); m_fusionQPoseY = getFixedPanel("0"); m_fusionQPoseZ = getFixedPanel("0"); dataLayout->addSpacing(30); dataLayout->addWidget(m_fusionQPoseScalar); dataLayout->addWidget(m_fusionQPoseX); dataLayout->addWidget(m_fusionQPoseY); dataLayout->addWidget(m_fusionQPoseZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Pose - roll, pitch, yaw (degrees): ")); m_fusionPoseX = getFixedPanel("0"); m_fusionPoseY = getFixedPanel("0"); m_fusionPoseZ = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->setAlignment(Qt::AlignLeft); dataLayout->addSpacing(30); dataLayout->addWidget(m_fusionPoseX); dataLayout->addWidget(m_fusionPoseY); dataLayout->addWidget(m_fusionPoseZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Gyros (radians/s): ")); m_gyroX = getFixedPanel("0"); m_gyroY = getFixedPanel("0"); m_gyroZ = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->setAlignment(Qt::AlignLeft); dataLayout->addSpacing(30); dataLayout->addWidget(m_gyroX); dataLayout->addWidget(m_gyroY); dataLayout->addWidget(m_gyroZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Accelerometers (g): ")); m_accelX = getFixedPanel("0"); m_accelY = getFixedPanel("0"); m_accelZ = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->addSpacing(30); dataLayout->setAlignment(Qt::AlignLeft); dataLayout->addWidget(m_accelX); dataLayout->addWidget(m_accelY); dataLayout->addWidget(m_accelZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Accelerometer magnitude (g): ")); m_accelMagnitude = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->addSpacing(30); dataLayout->addWidget(m_accelMagnitude); dataLayout->setAlignment(Qt::AlignLeft); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Accelerometer residuals (g): ")); m_accelResidualX = getFixedPanel("0"); m_accelResidualY = getFixedPanel("0"); m_accelResidualZ = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->addSpacing(30); dataLayout->setAlignment(Qt::AlignLeft); dataLayout->addWidget(m_accelResidualX); dataLayout->addWidget(m_accelResidualY); dataLayout->addWidget(m_accelResidualZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Magnetometers (uT): ")); m_compassX = getFixedPanel("0"); m_compassY = getFixedPanel("0"); m_compassZ = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->setAlignment(Qt::AlignLeft); dataLayout->addSpacing(30); dataLayout->addWidget(m_compassX); dataLayout->addWidget(m_compassY); dataLayout->addWidget(m_compassZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Compass magnitude (uT): ")); m_compassMagnitude = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->addSpacing(30); dataLayout->addWidget(m_compassMagnitude); dataLayout->setAlignment(Qt::AlignLeft); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addSpacing(10); QHBoxLayout *fusionBox = new QHBoxLayout(); QLabel *fusionTypeLabel = new QLabel("Fusion algorithm: "); fusionBox->addWidget(fusionTypeLabel); fusionTypeLabel->setMaximumWidth(150); m_fusionType = new QLabel(); fusionBox->addWidget(m_fusionType); vLayout->addLayout(fusionBox); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Fusion controls: ")); m_enableGyro = new QCheckBox("Enable gyros"); m_enableGyro->setChecked(true); vLayout->addWidget(m_enableGyro); m_enableAccel = new QCheckBox("Enable accels"); m_enableAccel->setChecked(true); vLayout->addWidget(m_enableAccel); m_enableCompass = new QCheckBox("Enable compass"); m_enableCompass->setChecked(true); vLayout->addWidget(m_enableCompass); m_enableDebug = new QCheckBox("Enable debug messages"); m_enableDebug->setChecked(false); vLayout->addWidget(m_enableDebug); vLayout->addStretch(1); centralWidget()->setLayout(vLayout); setFixedSize(700, 700); loadSettings(); updateComState(false); } QLabel* RTHostIMU::getFixedPanel(QString text) { QLabel *label = new QLabel(text); label->setFrameStyle(QFrame::Panel); label->setFixedSize(QSize(100, 16)); return label; } void RTHostIMU::layoutStatusBar() { m_rateStatus = new QLabel(this); m_rateStatus->setAlignment(Qt::AlignLeft); ui.statusBar->addWidget(m_rateStatus, 1); m_calStatus = new QLabel(this); m_calStatus->setAlignment(Qt::AlignLeft); ui.statusBar->addWidget(m_calStatus, 0); } void RTHostIMU::IMURunning() { RTHostIMUClient *client = (RTHostIMUClient *)m_imuThread->getIMU(); connect(client, SIGNAL(RTArduLinkStatus(int, int, bool, QString, qint64, qint64)), this, SLOT(RTArduLinkStatus(int, int, bool, QString, qint64, qint64))); connect(client, SIGNAL(RTArduLinkPortOpen(int)), SLOT(RTArduLinkPortOpen(int))); connect(client, SIGNAL(RTArduLinkPortClosed(int)), SLOT(RTArduLinkPortClosed(int))); connect(client, SIGNAL(RTArduLinkPortRX(int)), SLOT(RTArduLinkPortRX(int))); connect(client, SIGNAL(RTArduLinkPortTX(int)), SLOT(RTArduLinkPortTX(int))); } void RTHostIMU::populateComPorts() { QList ports = QextSerialEnumerator::getPorts(); m_comPort->clear(); m_comPort->insertItem(0, "Off"); for (int i = 0; i < ports.size(); i++) #if defined(Q_OS_WIN) || defined(Q_OS_MAC) m_comPort->insertItem(i + 1, ports.at(i).portName); #else m_comPort->insertItem(i + 1, ports.at(i).physName); #endif } void RTHostIMU::newComSetting(int) { saveSettings(); emit newIMU(); } void RTHostIMU::RTArduLinkPortOpen(int ) { updateComState(true); } void RTHostIMU::RTArduLinkPortClosed(int ) { updateComState(false); } void RTHostIMU::RTArduLinkPortRX(int ) { updateComRXTX(m_comRXLabel, true); } void RTHostIMU::RTArduLinkPortTX(int ) { updateComRXTX(m_comTXLabel, true); } void RTHostIMU::RTArduLinkStatus(int, int /* address */, bool /* active */, QString identity, qint64, qint64) { m_subsystem->setText(identity); } void RTHostIMU::updateComState(bool open) { QPalette pal = m_comLabel->palette(); if (open) pal.setColor(m_comLabel->backgroundRole(), Qt::green); else pal.setColor(m_comLabel->backgroundRole(), Qt::red); m_comLabel->setPalette(pal); m_comLabel->setAutoFillBackground(true); } void RTHostIMU::updateComRXTX(QLabel *label, bool active) { QPalette pal = label->palette(); if (active) pal.setColor(label->backgroundRole(), Qt::green); else pal.setColor(label->backgroundRole(), Qt::lightGray); label->setPalette(pal); label->setAutoFillBackground(true); } void RTHostIMU::loadSettings() { int speed; int port; QString portString; m_settings = new QSettings("RTHostIMU.ini", QSettings::IniFormat); // See if need to set defaults if (!m_settings->contains(RTARDULINKHOST_SETTINGS_PORT)) m_settings->setValue(RTARDULINKHOST_SETTINGS_PORT, "Off"); if (!m_settings->contains(RTARDULINKHOST_SETTINGS_SPEED)) m_settings->setValue(RTARDULINKHOST_SETTINGS_SPEED, 4); // Now read in values portString = m_settings->value(RTARDULINKHOST_SETTINGS_PORT).toString(); if ((port = m_comPort->findText(portString)) != -1) { m_comPort->setCurrentIndex(port); } else { m_comPort->addItem(portString); m_comPort->setCurrentIndex(m_comPort->count()-1); } speed = m_settings->value(RTARDULINKHOST_SETTINGS_SPEED).toInt(); if ((speed >= 0) && (speed <= 4)) m_comSpeed->setCurrentIndex(speed); } void RTHostIMU::saveSettings() { m_settings->setValue(RTARDULINKHOST_SETTINGS_PORT, m_comPort->currentText()); m_settings->setValue(RTARDULINKHOST_SETTINGS_SPEED, m_comSpeed->currentIndex()); } rtimulib-7.2.1/RTHost/RTHostIMU/RTHostIMU.h000066400000000000000000000075401254201074400202050ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef _RTHOSTIMU_H #define _RTHOSTIMU_H #include #include #include #include #include #include #include "ui_RTHostIMU.h" #include "RTIMULib.h" class RTHostIMUThread; class RTHostIMU : public QMainWindow { Q_OBJECT public: RTHostIMU(); ~RTHostIMU(); public slots: void onSelectFusionAlgorithm(); void onCalibrateAccelerometers(); void onCalibrateMagnetometers(); void onEnableGyro(int); void onEnableAccel(int); void onEnableCompass(int); void onEnableDebug(int); void newIMUData(const RTIMU_DATA&); void IMURunning(); void RTArduLinkStatus(int port, int address, bool active, QString identity, qint64 pollsIn, qint64 pollsOut); void RTArduLinkPortOpen(int port); void RTArduLinkPortClosed(int port); void RTArduLinkPortTX(int port); void RTArduLinkPortRX(int port); void newComSetting(int); signals: void newIMU(); protected: void timerEvent(QTimerEvent *event); void closeEvent(QCloseEvent *event); private: void layoutStatusBar(); void layoutWindow(); void updateComState(bool open); void updateComRXTX(QLabel *label, bool active); void populateComPorts(); void loadSettings(); void saveSettings(); QSettings *m_settings; RTHostIMUThread *m_imuThread; // the thread that operates the imu RTIMU_DATA m_imuData; // this holds the IMU information and funsion output // Qt GUI stuff Ui::RTHostIMUClass ui; QLabel *getFixedPanel(QString text); QLabel *m_comLabel; QLabel *m_comRXLabel; QLabel *m_comTXLabel; QComboBox *m_comPort; QComboBox *m_comSpeed; QLabel *m_subsystem; QLabel *m_fusionQPoseScalar; QLabel *m_fusionQPoseX; QLabel *m_fusionQPoseY; QLabel *m_fusionQPoseZ; QLabel *m_fusionPoseX; QLabel *m_fusionPoseY; QLabel *m_fusionPoseZ; QLabel *m_gyroX; QLabel *m_gyroY; QLabel *m_gyroZ; QLabel *m_accelX; QLabel *m_accelY; QLabel *m_accelZ; QLabel *m_accelMagnitude; QLabel *m_accelResidualX; QLabel *m_accelResidualY; QLabel *m_accelResidualZ; QLabel *m_compassX; QLabel *m_compassY; QLabel *m_compassZ; QLabel *m_compassMagnitude; QLabel *m_fusionType; QCheckBox *m_enableGyro; QCheckBox *m_enableAccel; QCheckBox *m_enableCompass; QCheckBox *m_enableDebug; QLabel *m_imuType; QLabel *m_biasStatus; QLabel *m_rateStatus; QLabel *m_calStatus; int m_rateTimer; int m_displayTimer; int m_sampleCount; }; #endif // _RTHOSTIMU_H rtimulib-7.2.1/RTHost/RTHostIMU/RTHostIMU.pri000066400000000000000000000025651254201074400205520ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014, richards-tech #// #// Permission 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. INCLUDEPATH += $$PWD DEPENDPATH += $$PWD HEADERS += RTHostIMU.h SOURCES += main.cpp \ RTHostIMU.cpp \ FORMS += RTHostIMU.ui rtimulib-7.2.1/RTHost/RTHostIMU/RTHostIMU.pro000066400000000000000000000035231254201074400205530ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014, richards-tech #// #// Permission 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. greaterThan(QT_MAJOR_VERSION, 4): cache() TEMPLATE = app TARGET = RTHostIMU DESTDIR = Output QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += debug_and_release target.path = /usr/local/bin INSTALLS += target DEFINES += QT_NETWORK_LIB INCLUDEPATH += GeneratedFiles MOC_DIR += GeneratedFiles/moc OBJECTS_DIR += objects UI_DIR += GeneratedFiles RCC_DIR += GeneratedFiles include(RTHostIMU.pri) include(../RTHostIMUCommon/RTHostIMUCommon.pri) include(../../RTIMULib/RTIMULib.pri) include(../RTSerialPort/src/qextserialport.pri) include(../RTArduLinkHost/RTArduLinkHost.pri) rtimulib-7.2.1/RTHost/RTHostIMU/RTHostIMU.sln000066400000000000000000000017111254201074400205440ustar00rootroot00000000000000 Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RTHostIMU", "RTHostIMU.vcxproj", "{CA859603-52C2-46FE-9535-24D89853619B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 Release|Win32 = Release|Win32 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {CA859603-52C2-46FE-9535-24D89853619B}.Debug|Win32.ActiveCfg = Debug|Win32 {CA859603-52C2-46FE-9535-24D89853619B}.Debug|Win32.Build.0 = Debug|Win32 {CA859603-52C2-46FE-9535-24D89853619B}.Release|Win32.ActiveCfg = Release|Win32 {CA859603-52C2-46FE-9535-24D89853619B}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution Qt5Version = $(DefaultQtVersion) EndGlobalSection EndGlobal rtimulib-7.2.1/RTHost/RTHostIMU/RTHostIMU.ui000066400000000000000000000050721254201074400203710ustar00rootroot00000000000000 RTHostIMUClass 0 0 658 562 0 0 RTHostIMU 400 300 0 0 658 21 Actions toolBar TopToolBarArea false Select IMU Select fusion algorithm Calibrate magnetometers Calibrate accelerometers Exit rtimulib-7.2.1/RTHost/RTHostIMU/RTHostIMU.vcxproj000066400000000000000000001052661254201074400214550ustar00rootroot00000000000000 Debug Win32 Release Win32 {CA859603-52C2-46FE-9535-24D89853619B} Qt4VSv1.0 Application v120 Application v120 <_ProjectFileVersion>10.0.40219.1 $(SolutionDir)$(Platform)\$(Configuration)\ $(SolutionDir)$(Platform)\$(Configuration)\ UNICODE;WIN32;WIN64;QT_DLL;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;%(PreprocessorDefinitions) ..\RTSerialPort\src;..\..\RTIMULib;..\RTArduLinkHost;..\RTHostIMUCommon;.\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtWidgets;%(AdditionalIncludeDirectories) Disabled ProgramDatabase MultiThreadedDebugDLL false Windows $(OutDir)\$(ProjectName).exe $(QTDIR)\lib;%(AdditionalLibraryDirectories) true Setupapi.lib;qtmaind.lib;Qt5Cored.lib;Qt5Guid.lib;Qt5Widgetsd.lib;%(AdditionalDependencies) UNICODE;WIN32;WIN64;QT_DLL;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_GUI_LIB;QT_WIDGETS_LIB;%(PreprocessorDefinitions) ..\RTSerialPort\src;..\..\RTIMULib;..\RTArduLinkHost;..\RTHostIMUCommon;.\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtWidgets;%(AdditionalIncludeDirectories) MultiThreadedDLL false Windows $(OutDir)\$(ProjectName).exe $(QTDIR)\lib;%(AdditionalLibraryDirectories) false Setupapi.lib;qtmain.lib;Qt5Core.lib;Qt5Gui.lib;Qt5Widgets.lib;%(AdditionalDependencies) true true true true true true true true true true true true true true true true true true true true true true $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing RTHostImu.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB "-I.\..\RTSerialPort\src" "-I.\..\..\RTIMULib" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing RTHostImu.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB "-I.\..\RTSerialPort\src" "-I.\..\..\RTIMULib" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing AccelCalDlg.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB "-I.\..\RTSerialPort\src" "-I.\..\..\RTIMULib" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing AccelCalDlg.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB "-I.\..\RTSerialPort\src" "-I.\..\..\RTIMULib" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing MagCalDlg.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB "-I.\..\RTSerialPort\src" "-I.\..\..\RTIMULib" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing MagCalDlg.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB "-I.\..\RTSerialPort\src" "-I.\..\..\RTIMULib" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing qextserialenumerator.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB "-I.\..\RTSerialPort\src" "-I.\..\..\RTIMULib" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing qextserialenumerator.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB "-I.\..\RTSerialPort\src" "-I.\..\..\RTIMULib" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing qextserialport.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB "-I.\..\RTSerialPort\src" "-I.\..\..\RTIMULib" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing qextserialport.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB "-I.\..\RTSerialPort\src" "-I.\..\..\RTIMULib" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing qextwineventnotifier_p.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB "-I.\..\RTSerialPort\src" "-I.\..\..\RTIMULib" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing qextwineventnotifier_p.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB "-I.\..\RTSerialPort\src" "-I.\..\..\RTIMULib" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing RTArduLinkHost.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB "-I.\..\RTSerialPort\src" "-I.\..\..\RTIMULib" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing RTArduLinkHost.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB "-I.\..\RTSerialPort\src" "-I.\..\..\RTIMULib" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing RTHostIMUClient.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB "-I.\..\RTSerialPort\src" "-I.\..\..\RTIMULib" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing RTHostIMUClient.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB "-I.\..\RTSerialPort\src" "-I.\..\..\RTIMULib" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing RTHostIMUThread.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB "-I.\..\RTSerialPort\src" "-I.\..\..\RTIMULib" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing RTHostIMUThread.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB "-I.\..\RTSerialPort\src" "-I.\..\..\RTIMULib" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing SelectFusionDlg.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB "-I.\..\RTSerialPort\src" "-I.\..\..\RTIMULib" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing SelectFusionDlg.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_WIDGETS_LIB "-I.\..\RTSerialPort\src" "-I.\..\..\RTIMULib" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtWidgets" Document $(QTDIR)\bin\uic.exe;%(AdditionalInputs) Uic%27ing %(Identity)... .\GeneratedFiles\ui_%(Filename).h;%(Outputs) "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" $(QTDIR)\bin\uic.exe;%(AdditionalInputs) Uic%27ing %(Identity)... .\GeneratedFiles\ui_%(Filename).h;%(Outputs) "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" rtimulib-7.2.1/RTHost/RTHostIMU/RTHostIMU.vcxproj.filters000066400000000000000000000363451254201074400231250ustar00rootroot00000000000000 {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;cxx;c;def {93995380-89BD-4b04-88EB-625FBE52EBFB} h {99349809-55BA-4b9d-BF79-8FDBB0286EB3} ui {D9D6E242-F8AF-46E4-B9FD-80ECBC20BA3E} qrc;* false {71ED8ED8-ACB9-4CE9-BBE1-E00B30144E11} moc;h;cpp False {0498a474-93ae-47ed-b6c2-c9298eeb7e77} cpp;moc False {e5a54512-acf5-4b3f-9b46-692730051ad8} cpp;moc False {af79c93d-1fc5-4cd7-8c1c-e60b40d40269} {7bb41597-6c89-406e-8b01-0768453bb56c} {83de7b79-2099-44d3-aed3-f09d3cfa140c} {dd0c7d74-9d10-4356-aecb-f66b9dc58ada} {525b3139-c312-4999-a056-5b23682ed5b4} Source Files Source Files Generated Files\Debug Generated Files\Release RTHostIMUCommon Generated Files\Debug Generated Files\Release RTHostIMUCommon Generated Files\Debug Generated Files\Release RTHostIMUCommon Generated Files\Debug Generated Files\Release RTArduLinkHost Generated Files\Debug Generated Files\Release RTArduLinkHost RTHostIMUCommon Generated Files\Debug Generated Files\Release RTHostIMUCommon Generated Files\Debug Generated Files\Release RTSerialPort Generated Files\Debug Generated Files\Release RTSerialPort RTSerialPort Generated Files\Debug Generated Files\Release RTSerialPort RTSerialPort Generated Files\Debug Generated Files\Release RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers Header Files RTHostIMUCommon RTHostIMUCommon RTHostIMUCommon RTArduLinkHost Form Files RTHostIMUCommon RTHostIMUCommon RTSerialPort RTSerialPort RTSerialPort RTHostIMUCommon RTArduLinkHost RTArduLinkHost RTArduLinkHost Generated Files RTSerialPort RTSerialPort RTSerialPort RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTArduLinkHost RTIMULib RTIMULib rtimulib-7.2.1/RTHost/RTHostIMU/main.cpp000066400000000000000000000026761254201074400177330ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "RTHostIMU.h" #include // This is standard Qt startup int main(int argc, char *argv[]) { QApplication a(argc, argv); RTHostIMU *w = new RTHostIMU(); w->show(); return a.exec(); } rtimulib-7.2.1/RTHost/RTHostIMUCommon/000077500000000000000000000000001254201074400174415ustar00rootroot00000000000000rtimulib-7.2.1/RTHost/RTHostIMUCommon/AccelCalDlg.cpp000066400000000000000000000230041254201074400222220ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "AccelCalDlg.h" #include "RTIMUAccelCal.h" #include "RTIMUSettings.h" #include #include #include #include #include AccelCalDlg::AccelCalDlg(QWidget *parent, RTIMUSettings* settings) : QDialog(parent) { m_cal = new RTIMUAccelCal(settings); m_newData = false; m_cal->accelCalInit(); layoutWindow(); setWindowTitle("Accelerometer Calibration"); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); m_timer = startTimer(50); connect(m_checkAllBtn, SIGNAL(clicked()), this, SLOT(onCheckAll())); connect(m_uncheckAllBtn, SIGNAL(clicked()), this, SLOT(onUncheckAll())); connect(m_resetBtn, SIGNAL(clicked()), this, SLOT(onReset())); connect(m_okBtn, SIGNAL(clicked()), this, SLOT(onOk())); connect(m_cancelBtn, SIGNAL(clicked()), this, SLOT(onCancel())); } AccelCalDlg::~AccelCalDlg() { } void AccelCalDlg::newIMUData(const RTIMU_DATA& data) { QMutexLocker lock(&m_refreshMutex); m_currentVal = data.accel; for (int i = 0; i < 3; i++) m_cal->accelCalEnable(i, m_check[i]->checkState() == Qt::Checked); m_cal->newAccelCalData(data.accel); m_newData = true; } void AccelCalDlg::onOk() { killTimer(m_timer); m_cal->accelCalSave(); accept(); } void AccelCalDlg::onCancel() { killTimer(m_timer); reject(); } void AccelCalDlg::onReset() { QMutexLocker lock(&m_refreshMutex); for (int i = 0; i < 3; i++) m_cal->accelCalEnable(i, m_check[i]->checkState() == Qt::Checked); m_cal->accelCalReset(); m_okBtn->setEnabled(m_cal->accelCalValid()); } void AccelCalDlg::onCheckAll() { for (int j = 0; j < 3; j++) m_check[j]->setChecked(true); } void AccelCalDlg::onUncheckAll() { for (int j = 0; j < 3; j++) m_check[j]->setChecked(false); } void AccelCalDlg::timerEvent(QTimerEvent *) { QMutexLocker lock(&m_refreshMutex); m_okBtn->setEnabled(m_cal->accelCalValid()); if (m_newData) updateControls(); m_newData = false; } void AccelCalDlg::updateControls() { for (int i = 0; i < 3; i++) { setRaw(m_raw[i], m_currentVal.data(i)); setRawMinMax(m_rawMin[i], m_cal->m_accelMin.data(i)); setRawMinMax(m_rawMax[i], m_cal->m_accelMax.data(i)); } } void AccelCalDlg::setRaw(QLabel *label, float val) { label->setText(QString::number(val, 'f', 4)); if (val < 0) label->setStyleSheet(m_redStyleSheet); else label->setStyleSheet(m_greenStyleSheet); } void AccelCalDlg::setRawMinMax(QLabel *label, float val) { label->setText(QString::number(val, 'f', 4)); if (val < 0) label->setStyleSheet(m_lightRedStyleSheet); else label->setStyleSheet(m_lightGreenStyleSheet); } void AccelCalDlg::layoutWindow() { QHBoxLayout *hLayout; QHBoxLayout *checkLayout; QLabel *label; QSpacerItem *spacer; int gridRow = 0; m_whiteStyleSheet = QString::fromUtf8("background-color: rgb(255, 255, 255);"); m_lightRedStyleSheet = QString::fromUtf8("background-color: rgb(200, 100, 100);"); m_lightGreenStyleSheet = QString::fromUtf8("background-color: rgb(100, 200, 100);"); m_redStyleSheet = QString::fromUtf8("background-color: rgb(220, 80, 80);"); m_greenStyleSheet = QString::fromUtf8("background-color: rgb(38, 244, 54);"); QVBoxLayout *centralLayout = new QVBoxLayout(this); centralLayout->setSpacing(20); centralLayout->setContentsMargins(6, 6, 6, 6); QGridLayout *gridLayout = new QGridLayout(); gridLayout->setSpacing(6); gridLayout->setContentsMargins(4, 4, 4, 4); hLayout = new QHBoxLayout(); hLayout->setSpacing(4); spacer = new QSpacerItem(80, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); label = getFixedLabel("XMin", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("X", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("XMax", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); label = getFixedLabel("YMin", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("Y", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("YMax", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); label = getFixedLabel("ZMin", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("Z", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("ZMax", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); gridLayout->addLayout(hLayout, gridRow++, 0, 1, 1); spacer = new QSpacerItem(20, 24, QSizePolicy::Minimum, QSizePolicy::Expanding); gridLayout->addItem(spacer, gridRow++, 0, 1, 1); // the old row hLayout = new QHBoxLayout(); hLayout->setSpacing(4); label = getFixedLabel("Old ", 80, 20, Qt::AlignLeft | Qt::AlignVCenter); hLayout->addWidget(label); m_oldMin = m_cal->m_accelMin; m_oldMax = m_cal->m_accelMax; for (int j = 0; j < 3; j++) { m_oldRawMin[j] = getFixedLabel(QString::number(m_oldMin.data(j)), 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_oldRawMin[j]); checkLayout = new QHBoxLayout(); checkLayout->setMargin(0); checkLayout->setAlignment(Qt::AlignCenter); m_check[j] = new QCheckBox(); checkLayout->addWidget(m_check[j]); hLayout->addLayout(checkLayout); m_oldRawMax[j] = getFixedLabel(QString::number(m_oldMax.data(j)), 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_oldRawMax[j]); if (j < 2) { spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); } } gridLayout->addLayout(hLayout, gridRow++, 0, 1, 1); spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding); gridLayout->addItem(spacer, gridRow++, 0, 1, 1); // the current row hLayout = new QHBoxLayout(); hLayout->setSpacing(4); label = getFixedLabel("Current ", 80, 20, Qt::AlignLeft | Qt::AlignVCenter); hLayout->addWidget(label); for (int j = 0; j < 3; j++) { m_rawMin[j] = getFixedLabel("0", 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_rawMin[j]); m_raw[j] = getFixedLabel("0", 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_raw[j]); m_rawMax[j] = getFixedLabel("0", 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_rawMax[j]); if (j < 2) { spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); } } gridLayout->addLayout(hLayout, gridRow++, 0, 1, 1); spacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); gridLayout->addItem(spacer, gridRow++, 0, 1, 1); spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding); gridLayout->addItem(spacer, gridRow++, 0, 1, 1); centralLayout->addLayout(gridLayout); m_checkAllBtn = new QPushButton("Check All"); m_uncheckAllBtn = new QPushButton("Uncheck All"); m_resetBtn = new QPushButton("Reset"); m_resetBtn->setDefault(true); m_okBtn = new QPushButton("OK"); m_okBtn->setEnabled(m_cal->accelCalValid()); m_cancelBtn = new QPushButton("Cancel"); hLayout = new QHBoxLayout(); hLayout->addSpacing(24); hLayout->addWidget(m_checkAllBtn); hLayout->addWidget(m_uncheckAllBtn); hLayout->addWidget(m_resetBtn); hLayout->addStretch(); hLayout->addWidget(m_okBtn); hLayout->addWidget(m_cancelBtn); hLayout->addSpacing(24); centralLayout->addLayout(hLayout); } QLabel* AccelCalDlg::getFixedLabel(QString text, int w, int h, Qt::Alignment alignment, QString styleSheet) { QLabel *label = new QLabel(text); label->setMaximumSize(QSize(w + 10, h)); label->setMinimumSize(QSize(w, h)); label->setAlignment(alignment); if (styleSheet.length() > 0) label->setStyleSheet(styleSheet); return label; } rtimulib-7.2.1/RTHost/RTHostIMUCommon/AccelCalDlg.h000066400000000000000000000054311254201074400216730ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef ACCELCALDLG_H #define ACCELCALDLG_H #include "RTIMULib.h" #include "RTIMUCalDefs.h" #include #include #include #include #include #include #include #include class RTIMUAccelCal; class AccelCalDlg : public QDialog { Q_OBJECT public: AccelCalDlg(QWidget *parent, RTIMUSettings* settings); ~AccelCalDlg(); public slots: void onOk(); void onCancel(); void onReset(); void onCheckAll(); void onUncheckAll(); void newIMUData(const RTIMU_DATA& data); protected: void timerEvent(QTimerEvent *); private: void updateControls(); void setRaw(QLabel *label, float val); void setRawMinMax(QLabel *label, float val); void layoutWindow(); QLabel* getFixedLabel(QString text, int w, int h, Qt::Alignment alignment = Qt::AlignCenter, QString styleSheet = ""); int m_timer; QMutex m_refreshMutex; QString m_whiteStyleSheet; QString m_lightRedStyleSheet; QString m_lightGreenStyleSheet; QString m_redStyleSheet; QString m_greenStyleSheet; RTVector3 m_currentVal; RTVector3 m_oldMin; RTVector3 m_oldMax; QLabel *m_rawMin[3]; QLabel *m_raw[3]; QLabel *m_rawMax[3]; QLabel *m_oldRawMin[3]; QLabel *m_oldRawMax[3]; QCheckBox *m_check[3]; QPushButton *m_okBtn; QPushButton *m_cancelBtn; QPushButton *m_resetBtn; QPushButton *m_checkAllBtn; QPushButton *m_uncheckAllBtn; bool m_newData; RTIMUAccelCal *m_cal; }; #endif // ACCELCALDLG_H rtimulib-7.2.1/RTHost/RTHostIMUCommon/CMakeLists.txt000066400000000000000000000045361254201074400222110ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014, richards-tech #// #// Permission 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. #// The cmake support was based on work by Moritz Fischer at ettus.com. #// Original copyright notice: # # Copyright 2014 Ettus Research LLC # SET(IMUCOMMON_SRCS AccelCalDlg.cpp MagCalDlg.cpp RTHostIMUClient.cpp RTHostIMUThread.cpp SelectFusionDlg.cpp) SET(CMAKE_AUTOMOC ON) IF(DEFINED QT5) FIND_PACKAGE(Qt5Core REQUIRED) FIND_PACKAGE(Qt5Widgets) FIND_PACKAGE(Qt5Gui) IF(WIN32) ADD_LIBRARY(RTHostIMUCommon STATIC ${IMUCOMMON_SRCS}) ELSE(WIN32) ADD_LIBRARY(RTHostIMUCommon SHARED ${IMUCOMMON_SRCS}) ENDIF(WIN32) qt5_use_modules(RTHostIMUCommon Core Widgets Gui) TARGET_LINK_LIBRARIES(RTHostIMUCommon RTArduLinkHost RTIMULib) ELSE(DEFINED QT5) FIND_PACKAGE(Qt4 REQUIRED) INCLUDE(${QT_USE_FILE}) ADD_DEFINITIONS(${QT_DEFINITIONS}) IF(WIN32) ADD_LIBRARY(RTHostIMUCommon STATIC ${IMUCOMMON_SRCS}) ELSE(WIN32) ADD_LIBRARY(RTHostIMUCommon SHARED ${IMUCOMMON_SRCS}) ENDIF(WIN32) TARGET_LINK_LIBRARIES(RTHostIMUCommon RTArduLinkHost RTIMULib ${QT_LIBRARIES}) ENDIF(DEFINED QT5) INSTALL(TARGETS RTHostIMUCommon DESTINATION lib) rtimulib-7.2.1/RTHost/RTHostIMUCommon/MagCalDlg.cpp000066400000000000000000000255131254201074400217260ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "MagCalDlg.h" #include "RTIMUMagCal.h" #include #include #include #include #include #include #include MagCalDlg::MagCalDlg(QWidget *parent, RTIMUSettings* settings) : QDialog(parent) { m_cal = new RTIMUMagCal(settings); m_newData = false; m_fitDirOptions.append("./RTEllipsoidFit/"); m_fitDirOptions.append("../RTEllipsoidFit/"); m_fitDirOptions.append("../../RTEllipsoidFit/"); findFitDir(); m_cal->magCalInit(); m_minMaxMode = true; layoutWindow(); setButtonEnables(); setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); m_timer = startTimer(50); connect(m_cancelBtn, SIGNAL(clicked()), this, SLOT(onCancel())); connect(m_resetBtn, SIGNAL(clicked()), this, SLOT(onReset())); connect(m_saveMinMaxBtn, SIGNAL(clicked()), this, SLOT(onSaveMinMax())); connect(m_processEllipsoidBtn, SIGNAL(clicked()), this, SLOT(onProcess())); } MagCalDlg::~MagCalDlg() { } void MagCalDlg::newIMUData(const RTIMU_DATA& data) { QMutexLocker lock(&m_refreshMutex); m_currentVal = data.compass; if (m_minMaxMode) m_cal->newMinMaxData(data.compass); else m_cal->newEllipsoidData(data.compass); m_newData = true; } void MagCalDlg::findFitDir() { m_fitDir = ""; m_usingEllipsoidFit = false; for (int i = 0; i < m_fitDirOptions.count(); i++) { QFileInfo file(m_fitDirOptions.at(i) + RTIMUCALDEFS_OCTAVE_CODE); if (file.exists()) { m_fitDir = m_fitDirOptions.at(i); m_usingEllipsoidFit = true; return; } } } void MagCalDlg::onCancel() { killTimer(m_timer); reject(); } void MagCalDlg::onReset() { m_cal->magCalReset(); m_minMaxMode = true; setButtonEnables(); } void MagCalDlg::onSaveMinMax() { m_cal->magCalSaveMinMax(); if (m_usingEllipsoidFit) { m_minMaxMode = false; setButtonEnables(); } else { accept(); } } void MagCalDlg::onProcess() { m_cal->magCalSaveRaw(qPrintable(m_fitDir)); QProcess proc; proc.setWorkingDirectory(m_fitDir); proc.start(RTIMUCALDEFS_OCTAVE_COMMAND); proc.waitForFinished(20000); if (proc.exitCode() == 0) { m_cal->magCalSaveCorr(qPrintable(m_fitDir)); } else { QMessageBox::warning(this, "Ellipsoid fit error", "Failed to execute RTEllipsoidFit.m. Only min/max calibration available", QMessageBox::Ok); } accept(); } void MagCalDlg::timerEvent(QTimerEvent *) { QMutexLocker lock(&m_refreshMutex); if (m_newData) updateControls(); m_newData = false; } void MagCalDlg::updateControls() { for (int i = 0; i < 3; i++) { setRaw(m_raw[i], m_currentVal.data(i)); setRawMinMax(m_rawMin[i], m_cal->m_magMin.data(i)); setRawMinMax(m_rawMax[i], m_cal->m_magMax.data(i)); } if (m_usingEllipsoidFit) setOctantCounts(); if (m_minMaxMode) { if (!m_saveMinMaxBtn->isEnabled() && m_cal->magCalValid()) m_saveMinMaxBtn->setEnabled(true); } else { if (!m_processEllipsoidBtn->isEnabled() && m_cal->magCalEllipsoidValid()) m_processEllipsoidBtn->setEnabled(true); } } void MagCalDlg::setRaw(QLabel *label, float val) { label->setText(QString::number(val, 'f', 4)); if (val < 0) label->setStyleSheet(m_redStyleSheet); else label->setStyleSheet(m_greenStyleSheet); } void MagCalDlg::setRawMinMax(QLabel *label, float val) { label->setText(QString::number(val, 'f', 4)); if (val < 0) label->setStyleSheet(m_lightRedStyleSheet); else label->setStyleSheet(m_lightGreenStyleSheet); } void MagCalDlg::setOctantCounts() { int counts[RTIMUCALDEFS_OCTANT_COUNT]; m_cal->magCalOctantCounts(counts); for (int i = 0; i < RTIMUCALDEFS_OCTANT_COUNT; i++) { m_octantCount[i]->setText(QString::number(counts[i])); } } void MagCalDlg::layoutWindow() { QHBoxLayout *hLayout; QLabel *label; QSpacerItem *spacer; int gridRow = 0; QString octantNames[] = {"---", "+--", "-+-", "++-", "--+", "+-+", "-++", "+++"}; m_whiteStyleSheet = QString::fromUtf8("background-color: rgb(255, 255, 255);"); m_lightRedStyleSheet = QString::fromUtf8("background-color: rgb(200, 100, 100);"); m_lightGreenStyleSheet = QString::fromUtf8("background-color: rgb(100, 200, 100);"); m_redStyleSheet = QString::fromUtf8("background-color: rgb(220, 80, 80);"); m_greenStyleSheet = QString::fromUtf8("background-color: rgb(38, 244, 54);"); QVBoxLayout *centralLayout = new QVBoxLayout(this); centralLayout->setSpacing(20); centralLayout->setContentsMargins(6, 6, 6, 6); QGridLayout *gridLayout = new QGridLayout(); gridLayout->setSpacing(6); gridLayout->setContentsMargins(4, 4, 4, 4); hLayout = new QHBoxLayout(); hLayout->setSpacing(4); spacer = new QSpacerItem(80, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); label = getFixedLabel("XMin", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("X", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("XMax", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); label = getFixedLabel("YMin", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("Y", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("YMax", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); label = getFixedLabel("ZMin", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("Z", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); label = getFixedLabel("ZMax", 80, 20, Qt::AlignCenter); hLayout->addWidget(label); gridLayout->addLayout(hLayout, gridRow++, 0, 1, 1); spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Expanding); gridLayout->addItem(spacer, gridRow++, 0, 1, 1); // the current row hLayout = new QHBoxLayout(); hLayout->setSpacing(4); label = getFixedLabel("Current ", 80, 20, Qt::AlignLeft | Qt::AlignVCenter); hLayout->addWidget(label); for (int j = 0; j < 3; j++) { m_rawMin[j] = getFixedLabel("0", 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_rawMin[j]); m_raw[j] = getFixedLabel("0", 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_raw[j]); m_rawMax[j] = getFixedLabel("0", 80, 20, Qt::AlignCenter, m_whiteStyleSheet); hLayout->addWidget(m_rawMax[j]); if (j < 2) { spacer = new QSpacerItem(20, 20, QSizePolicy::Minimum, QSizePolicy::Minimum); hLayout->addItem(spacer); } } gridLayout->addLayout(hLayout, gridRow++, 0, 1, 1); spacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); gridLayout->addItem(spacer, gridRow++, 0, 1, 1); centralLayout->addLayout(gridLayout); if (m_usingEllipsoidFit) { // Do octant displays centralLayout->addWidget(new QLabel("Octant counts:")); QGridLayout *octantLayout = new QGridLayout(); octantLayout->setSpacing(6); octantLayout->setContentsMargins(4, 4, 4, 4); for (int i = 0; i < RTIMUCALDEFS_OCTANT_COUNT; i++) { QHBoxLayout *hOctant = new QHBoxLayout(); QLabel *label = getFixedLabel(octantNames[i], 50, 20, Qt::AlignCenter, m_whiteStyleSheet); m_octantCount[i] = getFixedLabel("0", 50, 20, Qt::AlignCenter, m_whiteStyleSheet); hOctant->addWidget(label); hOctant->addWidget(m_octantCount[i]); octantLayout->addLayout(hOctant, i / 4, i % 4); } centralLayout->addLayout(octantLayout); } QHBoxLayout *hBox = new QHBoxLayout(); centralLayout->addLayout(hBox); QFormLayout *formLayout = new QFormLayout(); hBox->addLayout(formLayout); hBox->setAlignment(Qt::AlignHCenter); QHBoxLayout *buttonLayout = new QHBoxLayout(); m_processEllipsoidBtn = new QPushButton("Process ellipsoid"); m_saveMinMaxBtn = new QPushButton("Save min/max"); m_resetBtn = new QPushButton("Reset"); m_cancelBtn = new QPushButton("Cancel"); buttonLayout->addWidget(m_resetBtn); buttonLayout->addWidget(m_saveMinMaxBtn); if (m_usingEllipsoidFit) buttonLayout->addWidget(m_processEllipsoidBtn); buttonLayout->addWidget(m_cancelBtn); hBox->addLayout(buttonLayout); hLayout = new QHBoxLayout(); hLayout->addSpacing(24); hLayout->addStretch(); centralLayout->addLayout(hLayout); } QLabel* MagCalDlg::getFixedLabel(QString text, int w, int h, Qt::Alignment alignment, QString styleSheet) { QLabel *label = new QLabel(text); label->setMaximumSize(QSize(w + 10, h)); label->setMinimumSize(QSize(w, h)); label->setAlignment(alignment); if (styleSheet.length() > 0) label->setStyleSheet(styleSheet); return label; } void MagCalDlg::setButtonEnables() { m_saveMinMaxBtn->setEnabled(false); if (m_usingEllipsoidFit) m_processEllipsoidBtn->setEnabled(false); if (m_minMaxMode) { setWindowTitle("Magnetomer Calibration - collecting min/max data"); } else { setWindowTitle(QString("Magnetomer Calibration - collecting ellipsoid data (need %1 in each octant)") .arg(RTIMUCALDEFS_OCTANT_MIN_SAMPLES)); } } rtimulib-7.2.1/RTHost/RTHostIMUCommon/MagCalDlg.h000066400000000000000000000055241254201074400213730ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef MAGCALDLG_H #define MAGCALDLG_H #include "RTIMULib.h" #include "RTIMUCalDefs.h" #include #include #include #include #include #include #include #include class RTIMUMagCal; class MagCalDlg : public QDialog { Q_OBJECT public: MagCalDlg(QWidget *parent, RTIMUSettings* settings); ~MagCalDlg(); public slots: void onCancel(); void onReset(); void onSaveMinMax(); void onProcess(); void newIMUData(const RTIMU_DATA& data); protected: void timerEvent(QTimerEvent *); private: void updateControls(); void setRaw(QLabel *label, float val); void setRawMinMax(QLabel *label, float val); void setOctantCounts(); void setButtonEnables(); void findFitDir(); void layoutWindow(); QLabel* getFixedLabel(QString text, int w, int h, Qt::Alignment alignment = Qt::AlignCenter, QString styleSheet = ""); int m_timer; QMutex m_refreshMutex; QString m_whiteStyleSheet; QString m_lightRedStyleSheet; QString m_lightGreenStyleSheet; QString m_redStyleSheet; QString m_greenStyleSheet; RTVector3 m_currentVal; QLabel *m_rawMin[3]; QLabel *m_raw[3]; QLabel *m_rawMax[3]; QLabel *m_octantCount[RTIMUCALDEFS_OCTANT_COUNT]; QPushButton *m_resetBtn; QPushButton *m_saveMinMaxBtn; QPushButton *m_processEllipsoidBtn; QPushButton *m_cancelBtn; bool m_newData; bool m_minMaxMode; RTIMUMagCal *m_cal; QString m_fitDir; QStringList m_fitDirOptions; bool m_usingEllipsoidFit; }; #endif // MAGCALDLG_H rtimulib-7.2.1/RTHost/RTHostIMUCommon/RTArduLinkIMUDefs.h000066400000000000000000000045221254201074400227510ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef RTARDULINKIMUDEFS_H_ #define RTARDULINKIMUDEFS_H_ #include "RTArduLinkDefs.h" // Defines for the format of the IMU record passed between the Arduino and the // host system. Both Arduino and host system should use identical copies of this file. // RTARDULINKIMU_MESSAGE is used to send messages to the host. // Note: the gyro bias and mag cal state come back in the messageParam field typedef struct { RTARDULINK_UC4 timestamp; // timestamp in mS float gyro[3]; // the de-biased gyro data in rads/sec float accel[3]; // raw accel data in gs float mag[3]; // magnetometer data in uT } RTARDULINKIMU_MESSAGE; // Message type #define RTARDULINK_MESSAGE_IMU (RTARDULINK_MESSAGE_CUSTOM + 1) // Defines for the messageParam field #define RTARDULINKIMU_STATE_GYRO_BIAS_VALID 1 // bit 0 set if bias is valid #define RTARDULINKIMU_STATE_MAG_CAL_VALID 2 // bit 1 set if mag calibration is valid #endif /* RTARDULINKIMUDEFS_H_ */ rtimulib-7.2.1/RTHost/RTHostIMUCommon/RTHostIMUClient.cpp000066400000000000000000000107121254201074400230430ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "RTHostIMUClient.h" #include "RTIMUSettings.h" #include "RTArduLinkUtils.h" #include "qdebug.h" #include BaudRateType speedMap[]= {BAUD9600, BAUD19200, BAUD38400, BAUD57600, BAUD115200}; const char *speedString[] = {"9600", "19200", "38400", "57600", "115200"}; RTHostIMUClient::RTHostIMUClient(RTIMUSettings *settings) : RTArduLinkHost(NULL), RTIMU(settings) { m_settings->m_gyroBiasValid = false; } RTHostIMUClient::~RTHostIMUClient() { } //---------------------------------------------------------- // // RTIMU overrides bool RTHostIMUClient::IMUInit() { int speed; QString portString; setCalibrationData(); QSettings *settings = new QSettings("RTHostIMU.ini", QSettings::IniFormat); portString = settings->value(RTARDULINKHOST_SETTINGS_PORT).toString(); speed = settings->value(RTARDULINKHOST_SETTINGS_SPEED).toInt(); if ((speed >= 0) && (speed <= 4)) speed = 4; addPort(0, portString, speedMap[speed]); begin(); return true; } bool RTHostIMUClient::IMURead() { QMutexLocker lock(&m_lock); if (m_messageQ.empty()) return false; RTARDULINKIMU_MESSAGE message = m_messageQ.dequeue(); // put data in correct places m_imuData.gyro.setX(message.gyro[0]); m_imuData.gyro.setY(message.gyro[1]); m_imuData.gyro.setZ(message.gyro[2]); m_imuData.accel.setX(message.accel[0]); m_imuData.accel.setY(message.accel[1]); m_imuData.accel.setZ(message.accel[2]); m_imuData.compass.setX(message.mag[0]); m_imuData.compass.setY(message.mag[1]); m_imuData.compass.setZ(message.mag[2]); // The Arduino timestamp is unfortunately in mS so it needs to be multiplied by 1000 // as RTIMULib uses timestamps in uS. m_imuData.timestamp = RTArduLinkConvertUC4ToLong(message.timestamp) * 1000; calibrateAverageCompass(); calibrateAccel(); updateFusion(); return true; } int RTHostIMUClient::IMUGetPollInterval() { return 2; } //---------------------------------------------------------- // // RTArduLinkHost overrides // This functions processes response from the subsystem. It emits a series of signals // to update the input states. void RTHostIMUClient::processCustomMessage(RTARDULINKHOST_PORT *portInfo, unsigned int messageAddress, unsigned char messageType, unsigned char messageParam, unsigned char *data, int dataLength) { int port; port = portInfo->index; if (messageType != RTARDULINK_MESSAGE_IMU) { qDebug() << QString("Received unexpected message type %1 from port %2 address %3") .arg(messageType).arg(port).arg(messageAddress); return; } if (dataLength != sizeof(RTARDULINKIMU_MESSAGE)) { qDebug() << QString("Received message with incorrect length %1 (should have been %2) from port %3 address %4") .arg(dataLength).arg(sizeof(RTARDULINKIMU_MESSAGE)).arg(port).arg(messageAddress); return; } QMutexLocker lock(&m_lock); m_messageQ.enqueue((*(RTARDULINKIMU_MESSAGE *)data)); m_settings->m_gyroBiasValid = messageParam & RTARDULINKIMU_STATE_GYRO_BIAS_VALID; // check to see if too many messages and delete oldest if necessary if (m_messageQ.count() > 5) m_messageQ.dequeue(); } rtimulib-7.2.1/RTHost/RTHostIMUCommon/RTHostIMUClient.h000066400000000000000000000045241254201074400225140ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef _RTHOSTIMUCLIENT_H #define _RTHOSTIMUCLIENT_H #include "RTArduLinkHost.h" #include "IMUDrivers/RTIMU.h" #include "RTArduLinkIMUDefs.h" #include #include // setting keys for client data #define RTARDULINKHOST_SETTINGS_PORT "port" #define RTARDULINKHOST_SETTINGS_SPEED "speed" class RTIMUSettings; class RTHostIMUClient : public RTArduLinkHost, public RTIMU { Q_OBJECT public: RTHostIMUClient(RTIMUSettings *settings); ~RTHostIMUClient(); virtual const char *IMUName() { return "RTArduLink"; } virtual int IMUType() { return 0; } virtual bool IMUInit(); virtual bool IMURead(); virtual int IMUGetPollInterval(); signals: void updateInputStatus(int inputNumber, bool value); protected: void processCustomMessage(RTARDULINKHOST_PORT *portInfo, unsigned int messageAddress, unsigned char messageType, unsigned char messageParam, unsigned char *data, int dataLength); private: QQueue m_messageQ; QMutex m_lock; }; extern BaudRateType speedMap[]; extern const char *speedString[]; #endif // _RTHOSTIMUCLIENT_H rtimulib-7.2.1/RTHost/RTHostIMUCommon/RTHostIMUCommon.pri000066400000000000000000000031551254201074400230700ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014, richards-tech #// #// Permission 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. INCLUDEPATH += $$PWD DEPENDPATH += $$PWD HEADERS += $$PWD/RTArduLinkIMUDefs.h \ $$PWD/RTHostIMUClient.h \ $$PWD/RTHostIMUThread.h \ $$PWD/SelectFusionDlg.h \ $$PWD/MagCalDlg.h \ $$PWD/AccelCalDlg.h \ SOURCES += $$PWD/RTHostIMUClient.cpp \ $$PWD/RTHostIMUThread.cpp \ $$PWD/SelectFusionDlg.cpp \ $$PWD/MagCalDlg.cpp \ $$PWD/AccelCalDlg.cpp \ rtimulib-7.2.1/RTHost/RTHostIMUCommon/RTHostIMUThread.cpp000066400000000000000000000066121254201074400230400ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "RTHostIMUThread.h" #include "RTHostIMUClient.h" #include RTHostIMUThread::RTHostIMUThread() : QObject() { m_imu = NULL; m_settings = new RTIMUSettings(); // just use default name (RTIMULib.ini) for settings file m_timer = -1; } RTHostIMUThread::~RTHostIMUThread() { } void RTHostIMUThread::newIMU() { m_lock.lock(); if (m_imu != NULL) { delete m_imu; m_imu = NULL; } if (m_timer != -1) { killTimer(m_timer); m_timer = -1; } m_imu = new RTHostIMUClient(m_settings); m_lock.unlock(); emit IMURunning(); // set up IMU m_imu->IMUInit(); m_timer = startTimer(m_imu->IMUGetPollInterval()); } void RTHostIMUThread::initThread() { // create IMU. There's a special function call for this // as it makes sure that the required one is created as specified in the settings. m_imu = new RTHostIMUClient(m_settings); emit IMURunning(); // set up IMU m_imu->IMUInit(); // poll at the rate suggested by the IMU m_timer = startTimer(m_imu->IMUGetPollInterval()); } void RTHostIMUThread::finishThread() { if (m_timer != -1) killTimer(m_timer); m_timer = -1; if (m_imu != NULL) delete m_imu; m_imu = NULL; delete m_settings; } void RTHostIMUThread::timerEvent(QTimerEvent * /* event */) { // check for valid IMU if (m_imu == NULL) return; // loop here to clear all samples just in case things aren't keeping up while (m_imu->IMURead()) { emit newIMUData(m_imu->getIMUData()); } } //---------------------------------------------------------- // // The following is some Qt threading stuff void RTHostIMUThread::resumeThread() { m_thread = new QThread(); moveToThread(m_thread); connect(m_thread, SIGNAL(started()), this, SLOT(internalRunLoop())); connect(this, SIGNAL(internalEndThread()), this, SLOT(cleanup())); connect(this, SIGNAL(internalKillThread()), m_thread, SLOT(quit())); connect(m_thread, SIGNAL(finished()), m_thread, SLOT(deleteLater())); connect(m_thread, SIGNAL(finished()), this, SLOT(deleteLater())); m_thread->start(); } rtimulib-7.2.1/RTHost/RTHostIMUCommon/RTHostIMUThread.h000066400000000000000000000051161254201074400225030ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef _RTHOSTIMUTHREAD_H #define _RTHOSTIMUTHREAD_H #include #include #include "RTIMULib.h" class RTHostIMUThread : public QObject { Q_OBJECT public: RTHostIMUThread(); virtual ~RTHostIMUThread(); // resumeThread() is called when init is complete void resumeThread(); // exitThread is called to terminate and delete the thread void exitThread() { emit internalEndThread(); } RTIMUSettings *getSettings() { return m_settings; } RTIMU *getIMU() { QMutexLocker lock(&m_lock); return m_imu; } public slots: void internalRunLoop() { initThread(); emit running();} void cleanup() {finishThread(); emit internalKillThread(); } void newIMU(); signals: void running(); // emitted when everything set up and thread active void internalEndThread(); // this to end thread void internalKillThread(); // tells the QThread to quit void IMURunning(); // emitted when a new IMU is running void newIMUData(const RTIMU_DATA& data); // this contains the latest data from the IMU protected: void initThread(); void finishThread(); void timerEvent(QTimerEvent *event); private: int m_timer; RTIMUSettings *m_settings; RTIMU *m_imu; QThread *m_thread; QMutex m_lock; }; #endif // _RTHOSTIMUTHREAD_H rtimulib-7.2.1/RTHost/RTHostIMUCommon/SelectFusionDlg.cpp000066400000000000000000000053011254201074400231760ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "SelectFusionDlg.h" #include "RTIMUSettings.h" #include "RTFusion.h" #include #include SelectFusionDlg::SelectFusionDlg(RTIMUSettings *settings, QWidget *parent) : QDialog(parent, Qt::WindowCloseButtonHint | Qt::WindowTitleHint) { m_settings = settings; layoutWindow(); setWindowTitle("Select Fusion algorithm"); connect(m_buttons, SIGNAL(accepted()), this, SLOT(onOk())); connect(m_buttons, SIGNAL(rejected()), this, SLOT(onCancel())); } SelectFusionDlg::~SelectFusionDlg() { } void SelectFusionDlg::onOk() { m_settings->m_fusionType = m_selectFusion->currentIndex(); m_settings->saveSettings(); accept(); } void SelectFusionDlg::onCancel() { reject(); } void SelectFusionDlg::layoutWindow() { QVBoxLayout *mainLayout; QFormLayout *form; setModal(true); mainLayout = new QVBoxLayout(this); mainLayout->setSpacing(20); mainLayout->setContentsMargins(11, 11, 11, 11); form = new QFormLayout(); mainLayout->addLayout(form); m_selectFusion = new QComboBox(); for (int i = 0; i < RTFUSION_TYPE_COUNT; i++) m_selectFusion->addItem(RTFusion::fusionName(i)); m_selectFusion->setCurrentIndex(m_settings->m_fusionType); form->addRow("Select Fusion algorithm type: ", m_selectFusion); m_buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this); m_buttons->setCenterButtons(true); mainLayout->addWidget(m_buttons); } rtimulib-7.2.1/RTHost/RTHostIMUCommon/SelectFusionDlg.h000066400000000000000000000033361254201074400226510ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef SELECTFUSIONDLG_H #define SELECTFUSIONDLG_H #include #include #include class RTIMUSettings; class SelectFusionDlg : public QDialog { Q_OBJECT public: SelectFusionDlg(RTIMUSettings *settings, QWidget *parent = 0); ~SelectFusionDlg(); public slots: void onOk(); void onCancel(); private: void layoutWindow(); RTIMUSettings *m_settings; QDialogButtonBox *m_buttons; QComboBox *m_selectFusion; }; #endif // SELECTFUSIONDLG_H rtimulib-7.2.1/RTHost/RTHostIMUGL/000077500000000000000000000000001254201074400165135ustar00rootroot00000000000000rtimulib-7.2.1/RTHost/RTHostIMUGL/CMakeLists.txt000066400000000000000000000054151254201074400212600ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014, richards-tech #// #// Permission 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. #// The cmake support was based on work by Moritz Fischer at ettus.com. #// Original copyright notice: # # Copyright 2014 Ettus Research LLC # SET(HOSTIMUGL_SRCS RTHostIMUGL.cpp main.cpp) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) SET(CMAKE_AUTOMOC ON) IF(WIN32) SET(EXTRA_LIBS opengl32) ENDIF(WIN32) IF (UNIX AND (NOT APPLE)) SET(EXTRA_LIBS GL) ENDIF(UNIX AND (NOT APPLE)) IF(DEFINED QT5) FIND_PACKAGE(Qt5Widgets REQUIRED) FIND_PACKAGE(Qt5Core REQUIRED) FIND_PACKAGE(Qt5OpenGL REQUIRED) FIND_PACKAGE(Qt5Gui REQUIRED) qt5_wrap_ui(UI_HEADERS RTHostIMUGL.ui) ADD_EXECUTABLE(RTHostIMUGL ${HOSTIMUGL_SRCS} ${GL_SRCS} ${VRIMU_SRCS} ${UI_HEADERS} ${GL_HEADERS}) TARGET_LINK_LIBRARIES(RTHostIMUGL RTIMULib RTIMULibGL RTHostIMUCommon RTArduLinkHost RTSerialPort ${Qt5Gui_OPENGL_LIBRARIES} ${Qt5Core_QTMAIN_LIBRARIES} ${EXTRA_LIBS}) qt5_use_modules(RTHostIMUGL Widgets Core OpenGL) ELSE(DEFINED QT5) FIND_PACKAGE(Qt4 REQUIRED) SET(QT_USE_QTOPENGL TRUE) INCLUDE(${QT_USE_FILE}) ADD_DEFINITIONS(${QT_DEFINITIONS}) QT4_WRAP_UI(UI_HEADERS RTHostIMUGL.ui) ADD_EXECUTABLE(RTHostIMUGL ${HOSTIMUGL_SRCS} ${GL_SRCS} ${VRIMU_SRCS} ${UI_HEADERS} ${GL_HEADERS}) TARGET_LINK_LIBRARIES(RTHostIMUGL RTIMULib RTIMULibGL RTHostIMUCommon RTArduLinkHost ${QT_LIBRARIES} ${EXTRA_LIBS}) ENDIF(DEFINED QT5) INSTALL(TARGETS RTHostIMUGL DESTINATION bin) rtimulib-7.2.1/RTHost/RTHostIMUGL/RTHostIMUGL.cpp000066400000000000000000000527371254201074400212160ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include #include #include #include "qextserialenumerator.h" #include "RTHostIMUGL.h" #include "SelectFusionDlg.h" #include "RTHostIMUThread.h" #include "RTHostIMUClient.h" #include "IMUView.h" #include "AccelCalDlg.h" #include "MagCalDlg.h" #define RATE_TIMER_INTERVAL 2 RTHostIMUGL::RTHostIMUGL() : QMainWindow() { // This is some normal Qt GUI stuff ui.setupUi(this); layoutWindow(); layoutStatusBar(); // This code connects up signals from the GUI connect(ui.actionExit, SIGNAL(triggered()), this, SLOT(close())); connect(ui.actionSelectFusionAlgorithm, SIGNAL(triggered()), this, SLOT(onSelectFusionAlgorithm())); connect(ui.actionCalibrateAccelerometers, SIGNAL(triggered()), this, SLOT(onCalibrateAccelerometers())); connect(ui.actionCalibrateMagnetometers, SIGNAL(triggered()), this, SLOT(onCalibrateMagnetometers())); connect(m_enableGyro, SIGNAL(stateChanged(int)), this, SLOT(onEnableGyro(int))); connect(m_enableAccel, SIGNAL(stateChanged(int)), this, SLOT(onEnableAccel(int))); connect(m_enableCompass, SIGNAL(stateChanged(int)), this, SLOT(onEnableCompass(int))); connect(m_enableDebug, SIGNAL(stateChanged(int)), this, SLOT(onEnableDebug(int))); // create the imu thread and connect up the signal m_imuThread = new RTHostIMUThread(); connect(m_imuThread, SIGNAL(newIMUData(const RTIMU_DATA&)), this, SLOT(newIMUData(const RTIMU_DATA&)), Qt::DirectConnection); connect(m_imuThread, SIGNAL(IMURunning()), this, SLOT(IMURunning()), Qt::DirectConnection); connect(this, SIGNAL(newIMU()), m_imuThread, SLOT(newIMU())); m_imuThread->resumeThread(); // This value allows a sample rate to be calculated m_sampleCount = 0; // start some timers to get things going m_rateTimer = startTimer(RATE_TIMER_INTERVAL * 1000); // Only update the display 10 times per second to keep CPU reasonable m_displayTimer = startTimer(100); m_fusionType->setText(RTFusion::fusionName(m_imuThread->getSettings()->m_fusionType)); } RTHostIMUGL::~RTHostIMUGL() { } void RTHostIMUGL::onSelectFusionAlgorithm() { SelectFusionDlg dlg(m_imuThread->getSettings(), this); if (dlg.exec() == QDialog::Accepted) { emit newIMU(); m_fusionType->setText(RTFusion::fusionName(m_imuThread->getSettings()->m_fusionType)); } } void RTHostIMUGL::onCalibrateAccelerometers() { m_imuThread->getIMU()->setAccelCalibrationMode(true); AccelCalDlg dlg(this, m_imuThread->getSettings()); connect(m_imuThread, SIGNAL(newIMUData(const RTIMU_DATA&)), &dlg, SLOT(newIMUData(const RTIMU_DATA&)), Qt::DirectConnection); if (dlg.exec() == QDialog::Accepted) { } disconnect(m_imuThread, SIGNAL(newIMUData(const RTIMU_DATA&)), &dlg, SLOT(newIMUData(const RTIMU_DATA&))); emit newIMU(); } void RTHostIMUGL::onCalibrateMagnetometers() { m_imuThread->getIMU()->setCompassCalibrationMode(true); MagCalDlg dlg(this, m_imuThread->getSettings()); connect(m_imuThread, SIGNAL(newIMUData(const RTIMU_DATA&)), &dlg, SLOT(newIMUData(const RTIMU_DATA&)), Qt::DirectConnection); if (dlg.exec() == QDialog::Accepted) { } disconnect(m_imuThread, SIGNAL(newIMUData(const RTIMU_DATA&)), &dlg, SLOT(newIMUData(const RTIMU_DATA&))); emit newIMU(); } void RTHostIMUGL::newIMUData(const RTIMU_DATA& data) { m_imuData = data; m_sampleCount++; } void RTHostIMUGL::onEnableGyro(int state) { if (m_imuThread->getIMU() != NULL) m_imuThread->getIMU()->setGyroEnable(state == Qt::Checked); } void RTHostIMUGL::onEnableAccel(int state) { if (m_imuThread->getIMU() != NULL) m_imuThread->getIMU()->setAccelEnable(state == Qt::Checked); } void RTHostIMUGL::onEnableCompass(int state) { if (m_imuThread->getIMU() != NULL) m_imuThread->getIMU()->setCompassEnable(state == Qt::Checked); } void RTHostIMUGL::onEnableDebug(int state) { if (m_imuThread->getIMU() != NULL) m_imuThread->getIMU()->setDebugEnable(state == Qt::Checked); } void RTHostIMUGL::closeEvent(QCloseEvent *) { killTimer(m_displayTimer); killTimer(m_rateTimer); m_imuThread->exitThread(); } void RTHostIMUGL::timerEvent(QTimerEvent *event) { RTVector3 measuredPose; if (event->timerId() == m_displayTimer) { // Update the GUI m_gyroX->setText(QString::number(m_imuData.gyro.x(), 'f', 6)); m_gyroY->setText(QString::number(m_imuData.gyro.y(), 'f', 6)); m_gyroZ->setText(QString::number(m_imuData.gyro.z(), 'f', 6)); m_accelX->setText(QString::number(m_imuData.accel.x(), 'f', 6)); m_accelY->setText(QString::number(m_imuData.accel.y(), 'f', 6)); m_accelZ->setText(QString::number(m_imuData.accel.z(), 'f', 6)); m_compassX->setText(QString::number(m_imuData.compass.x(), 'f', 6)); m_compassY->setText(QString::number(m_imuData.compass.y(), 'f', 6)); m_compassZ->setText(QString::number(m_imuData.compass.z(), 'f', 6)); m_fusionPoseX->setText(QString::number(m_imuData.fusionPose.x() * RTMATH_RAD_TO_DEGREE, 'f', 6)); m_fusionPoseY->setText(QString::number(m_imuData.fusionPose.y() * RTMATH_RAD_TO_DEGREE, 'f', 6)); m_fusionPoseZ->setText(QString::number(m_imuData.fusionPose.z() * RTMATH_RAD_TO_DEGREE, 'f', 6)); m_fusionQPoseScalar->setText(QString::number(m_imuData.fusionQPose.scalar(), 'f', 6)); m_fusionQPoseX->setText(QString::number(m_imuData.fusionQPose.x(), 'f', 6)); m_fusionQPoseY->setText(QString::number(m_imuData.fusionQPose.y(), 'f', 6)); m_fusionQPoseZ->setText(QString::number(m_imuData.fusionQPose.z(), 'f', 6)); updateComRXTX(m_comRXLabel, false); updateComRXTX(m_comTXLabel, false); m_accelMagnitude->setText(QString::number(m_imuData.accel.length(), 'f', 6)); m_compassMagnitude->setText(QString::number(m_imuData.compass.length(), 'f', 6)); RTVector3 residuals = m_imuThread->getIMU()->getAccelResiduals(); m_accelResidualX->setText(QString::number(residuals.x(), 'f', 6)); m_accelResidualY->setText(QString::number(residuals.y(), 'f', 6)); m_accelResidualZ->setText(QString::number(residuals.z(), 'f', 6)); int index; RTVector3 vec; if ((index = m_displaySelect->currentIndex()) == -1) { m_view->updateIMU(m_imuData.fusionPose); } else { switch (m_displaySelect->itemData(index).toInt()) { case DISPLAY_MEASURED: if (m_imuThread->getIMU() != NULL) { measuredPose = m_imuThread->getIMU()->getMeasuredPose(); m_view->updateIMU(measuredPose); } break; case DISPLAY_ACCELONLY: m_imuData.accel.accelToEuler(vec); m_view->updateIMU(vec); break; case DISPLAY_COMPASSONLY: vec = RTMath::poseFromAccelMag(m_imuData.accel, m_imuData.compass); vec.setX(0); vec.setY(0); m_view->updateIMU(vec); break; default: m_view->updateIMU(m_imuData.fusionPose); break; } } } else { // Update the sample rate float rate = (float)m_sampleCount / (float(RATE_TIMER_INTERVAL)); m_sampleCount = 0; m_rateStatus->setText(QString("Sample rate: %1 per second").arg(rate)); if (m_imuThread->getIMU() == NULL) { m_calStatus->setText("No IMU found"); } else { m_calStatus->setText(QString("Accel %1 : Compass %2 : Ellipsoid %3") .arg(m_imuThread->getIMU()->getAccelCalibrationValid() ? "calibrated" : "uncalibrated") .arg(m_imuThread->getIMU()->getCompassCalibrationValid() ? "calibrated" : "uncalibrated") .arg(m_imuThread->getIMU()->getCompassCalibrationEllipsoidValid() ? "in use" : "not in use")); } if (m_imuThread->getIMU() != NULL) { m_imuType->setText(m_imuThread->getIMU()->IMUName()); if (!m_imuThread->getIMU()->IMUGyroBiasValid()) m_biasStatus->setText("Gyro bias being calculated - keep IMU still!"); else m_biasStatus->setText("Gyro bias valid"); } } } void RTHostIMUGL::layoutWindow() { QHBoxLayout *mainLayout = new QHBoxLayout(); mainLayout->setContentsMargins(3, 3, 3, 3); mainLayout->setSpacing(1); QVBoxLayout *vLayout = new QVBoxLayout(); vLayout->addSpacing(10); // Set up com port line and controls QHBoxLayout *commLayout = new QHBoxLayout(); commLayout->setAlignment(Qt::AlignLeft); vLayout->addLayout(commLayout); m_comLabel = new QLabel("Com port: "); m_comLabel->setAlignment(Qt::AlignLeft); m_comLabel->setAlignment(Qt::AlignCenter); commLayout->addWidget(m_comLabel); m_comPort = new QComboBox(this); populateComPorts(); connect(m_comPort, SIGNAL(currentIndexChanged(int)), this, SLOT(newComSetting(int))); commLayout->addWidget(m_comPort); commLayout->addSpacing(10); QLabel *label = new QLabel("Port speed: "); label->setAlignment(Qt::AlignLeft); label->setAlignment(Qt::AlignCenter); commLayout->addWidget(label); m_comSpeed = new QComboBox(this); for (int i = 0; i < 5; i++) m_comSpeed->insertItem(i, speedString[i]); m_comSpeed->setCurrentIndex(4); commLayout->addWidget(m_comSpeed); commLayout->addSpacing(10); connect(m_comSpeed, SIGNAL(currentIndexChanged(int)), this, SLOT(newComSetting(int))); m_comRXLabel = new QLabel(QString("RX")); m_comRXLabel->setAlignment(Qt::AlignLeft); m_comRXLabel->setAlignment(Qt::AlignCenter); m_comRXLabel->setMaximumHeight(20); commLayout->addWidget(m_comRXLabel); commLayout->addSpacing(10); m_comTXLabel = new QLabel(QString("TX")); m_comTXLabel->setAlignment(Qt::AlignLeft); m_comTXLabel->setAlignment(Qt::AlignCenter); m_comTXLabel->setMaximumHeight(20); commLayout->addWidget(m_comTXLabel); vLayout->addSpacing(10); // Set up subsystem QHBoxLayout *subLayout = new QHBoxLayout(); subLayout->setAlignment(Qt::AlignLeft); vLayout->addLayout(subLayout); label = new QLabel("Subsystem: "); label->setAlignment(Qt::AlignLeft); label->setAlignment(Qt::AlignCenter); subLayout->addWidget(label); m_subsystem = new QLabel(); m_subsystem->setMinimumWidth(200); subLayout->addWidget(m_subsystem); vLayout->addSpacing(10); QHBoxLayout *imuLayout = new QHBoxLayout(); vLayout->addLayout(imuLayout); imuLayout->setAlignment(Qt::AlignLeft); imuLayout->addWidget(new QLabel("IMU type: ")); m_imuType = new QLabel(); imuLayout->addWidget(m_imuType); vLayout->addSpacing(10); QHBoxLayout *biasLayout = new QHBoxLayout(); vLayout->addLayout(biasLayout); biasLayout->setAlignment(Qt::AlignLeft); biasLayout->addWidget(new QLabel("Gyro bias status: ")); m_biasStatus = new QLabel(); biasLayout->addWidget(m_biasStatus); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Fusion state (quaternion): ")); QHBoxLayout *dataLayout = new QHBoxLayout(); dataLayout->setAlignment(Qt::AlignLeft); m_fusionQPoseScalar = getFixedPanel("1"); m_fusionQPoseX = getFixedPanel("0"); m_fusionQPoseY = getFixedPanel("0"); m_fusionQPoseZ = getFixedPanel("0"); dataLayout->addSpacing(30); dataLayout->addWidget(m_fusionQPoseScalar); dataLayout->addWidget(m_fusionQPoseX); dataLayout->addWidget(m_fusionQPoseY); dataLayout->addWidget(m_fusionQPoseZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Pose - roll, pitch, yaw (degrees): ")); m_fusionPoseX = getFixedPanel("0"); m_fusionPoseY = getFixedPanel("0"); m_fusionPoseZ = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->setAlignment(Qt::AlignLeft); dataLayout->addSpacing(30); dataLayout->addWidget(m_fusionPoseX); dataLayout->addWidget(m_fusionPoseY); dataLayout->addWidget(m_fusionPoseZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Gyros (radians/s): ")); m_gyroX = getFixedPanel("0"); m_gyroY = getFixedPanel("0"); m_gyroZ = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->setAlignment(Qt::AlignLeft); dataLayout->addSpacing(30); dataLayout->addWidget(m_gyroX); dataLayout->addWidget(m_gyroY); dataLayout->addWidget(m_gyroZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Accelerometers (g): ")); m_accelX = getFixedPanel("0"); m_accelY = getFixedPanel("0"); m_accelZ = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->addSpacing(30); dataLayout->setAlignment(Qt::AlignLeft); dataLayout->addWidget(m_accelX); dataLayout->addWidget(m_accelY); dataLayout->addWidget(m_accelZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Accelerometer magnitude (g): ")); m_accelMagnitude = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->addSpacing(30); dataLayout->addWidget(m_accelMagnitude); dataLayout->setAlignment(Qt::AlignLeft); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Accelerometer residuals (g): ")); m_accelResidualX = getFixedPanel("0"); m_accelResidualY = getFixedPanel("0"); m_accelResidualZ = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->addSpacing(30); dataLayout->setAlignment(Qt::AlignLeft); dataLayout->addWidget(m_accelResidualX); dataLayout->addWidget(m_accelResidualY); dataLayout->addWidget(m_accelResidualZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Magnetometers (uT): ")); m_compassX = getFixedPanel("0"); m_compassY = getFixedPanel("0"); m_compassZ = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->setAlignment(Qt::AlignLeft); dataLayout->addSpacing(30); dataLayout->addWidget(m_compassX); dataLayout->addWidget(m_compassY); dataLayout->addWidget(m_compassZ); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Compass magnitude (uT): ")); m_compassMagnitude = getFixedPanel("0"); dataLayout = new QHBoxLayout(); dataLayout->addSpacing(30); dataLayout->addWidget(m_compassMagnitude); dataLayout->setAlignment(Qt::AlignLeft); vLayout->addLayout(dataLayout); vLayout->addSpacing(10); QHBoxLayout *fusionBox = new QHBoxLayout(); QLabel *fusionTypeLabel = new QLabel("Fusion algorithm: "); fusionBox->addWidget(fusionTypeLabel); fusionTypeLabel->setMaximumWidth(150); m_fusionType = new QLabel(); fusionBox->addWidget(m_fusionType); vLayout->addLayout(fusionBox); vLayout->addSpacing(10); vLayout->addWidget(new QLabel("Fusion controls: ")); m_enableGyro = new QCheckBox("Enable gyros"); m_enableGyro->setChecked(true); vLayout->addWidget(m_enableGyro); m_enableAccel = new QCheckBox("Enable accels"); m_enableAccel->setChecked(true); vLayout->addWidget(m_enableAccel); m_enableCompass = new QCheckBox("Enable compass"); m_enableCompass->setChecked(true); vLayout->addWidget(m_enableCompass); m_enableDebug = new QCheckBox("Enable debug messages"); m_enableDebug->setChecked(false); vLayout->addWidget(m_enableDebug); vLayout->addStretch(1); mainLayout->addLayout(vLayout); vLayout = new QVBoxLayout(); vLayout->setContentsMargins(3, 3, 3, 3); vLayout->setSpacing(3); QHBoxLayout *displayLayout = new QHBoxLayout(); QLabel *displayLabel = new QLabel("Display type: "); displayLayout->addWidget(displayLabel); displayLayout->setAlignment(displayLabel, Qt::AlignRight); m_displaySelect = new QComboBox(); m_displaySelect->addItem("Fusion pose", DISPLAY_FUSION); m_displaySelect->addItem("Measured pose", DISPLAY_MEASURED); m_displaySelect->addItem("Accels only", DISPLAY_ACCELONLY); m_displaySelect->addItem("Compass only", DISPLAY_COMPASSONLY); displayLayout->addWidget(m_displaySelect); vLayout->addLayout(displayLayout); m_view = new IMUView(this); vLayout->addWidget(m_view); mainLayout->addLayout(vLayout, 1); centralWidget()->setLayout(mainLayout); setMinimumWidth(1000); setMinimumHeight(700); loadSettings(); updateComState(false); } QLabel* RTHostIMUGL::getFixedPanel(QString text) { QLabel *label = new QLabel(text); label->setFrameStyle(QFrame::Panel); label->setFixedSize(QSize(100, 16)); return label; } void RTHostIMUGL::layoutStatusBar() { m_rateStatus = new QLabel(this); m_rateStatus->setAlignment(Qt::AlignLeft); ui.statusBar->addWidget(m_rateStatus, 1); m_calStatus = new QLabel(this); m_calStatus->setAlignment(Qt::AlignLeft); ui.statusBar->addWidget(m_calStatus, 0); } void RTHostIMUGL::IMURunning() { RTHostIMUClient *client = (RTHostIMUClient *)m_imuThread->getIMU(); connect(client, SIGNAL(RTArduLinkStatus(int, int, bool, QString, qint64, qint64)), this, SLOT(RTArduLinkStatus(int, int, bool, QString, qint64, qint64))); connect(client, SIGNAL(RTArduLinkPortOpen(int)), SLOT(RTArduLinkPortOpen(int))); connect(client, SIGNAL(RTArduLinkPortClosed(int)), SLOT(RTArduLinkPortClosed(int))); connect(client, SIGNAL(RTArduLinkPortRX(int)), SLOT(RTArduLinkPortRX(int))); connect(client, SIGNAL(RTArduLinkPortTX(int)), SLOT(RTArduLinkPortTX(int))); } void RTHostIMUGL::populateComPorts() { QList ports = QextSerialEnumerator::getPorts(); m_comPort->clear(); m_comPort->insertItem(0, "Off"); for (int i = 0; i < ports.size(); i++) #if defined(Q_OS_WIN) || defined(Q_OS_MAC) m_comPort->insertItem(i + 1, ports.at(i).portName); #else m_comPort->insertItem(i + 1, ports.at(i).physName); #endif } void RTHostIMUGL::newComSetting(int) { saveSettings(); emit newIMU(); } void RTHostIMUGL::RTArduLinkPortOpen(int ) { updateComState(true); } void RTHostIMUGL::RTArduLinkPortClosed(int ) { updateComState(false); } void RTHostIMUGL::RTArduLinkPortRX(int ) { updateComRXTX(m_comRXLabel, true); } void RTHostIMUGL::RTArduLinkPortTX(int ) { updateComRXTX(m_comTXLabel, true); } void RTHostIMUGL::RTArduLinkStatus(int, int /* address */, bool /* active */, QString identity, qint64, qint64) { m_subsystem->setText(identity); } void RTHostIMUGL::updateComState(bool open) { QPalette pal = m_comLabel->palette(); if (open) pal.setColor(m_comLabel->backgroundRole(), Qt::green); else pal.setColor(m_comLabel->backgroundRole(), Qt::red); m_comLabel->setPalette(pal); m_comLabel->setAutoFillBackground(true); } void RTHostIMUGL::updateComRXTX(QLabel *label, bool active) { QPalette pal = label->palette(); if (active) pal.setColor(label->backgroundRole(), Qt::green); else pal.setColor(label->backgroundRole(), Qt::lightGray); label->setPalette(pal); label->setAutoFillBackground(true); } void RTHostIMUGL::loadSettings() { int speed; int port; QString portString; m_settings = new QSettings("RTHostIMU.ini", QSettings::IniFormat); // See if need to set defaults if (!m_settings->contains(RTARDULINKHOST_SETTINGS_PORT)) m_settings->setValue(RTARDULINKHOST_SETTINGS_PORT, "Off"); if (!m_settings->contains(RTARDULINKHOST_SETTINGS_SPEED)) m_settings->setValue(RTARDULINKHOST_SETTINGS_SPEED, 4); // Now read in values portString = m_settings->value(RTARDULINKHOST_SETTINGS_PORT).toString(); if ((port = m_comPort->findText(portString)) != -1) { m_comPort->setCurrentIndex(port); } else { m_comPort->addItem(portString); m_comPort->setCurrentIndex(m_comPort->count()-1); } speed = m_settings->value(RTARDULINKHOST_SETTINGS_SPEED).toInt(); if ((speed >= 0) && (speed <= 4)) m_comSpeed->setCurrentIndex(speed); } void RTHostIMUGL::saveSettings() { m_settings->setValue(RTARDULINKHOST_SETTINGS_PORT, m_comPort->currentText()); m_settings->setValue(RTARDULINKHOST_SETTINGS_SPEED, m_comSpeed->currentIndex()); } rtimulib-7.2.1/RTHost/RTHostIMUGL/RTHostIMUGL.h000066400000000000000000000104641254201074400206520ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef _RTHOSTIMUGL_H #define _RTHOSTIMUGL_H #include #include #include #include #include #include #include "ui_RTHostIMUGL.h" #include "RTIMULib.h" // Display type codes #define DISPLAY_FUSION 0 // displays fusion algorithm output #define DISPLAY_MEASURED 1 // measured from accels and compass #define DISPLAY_ACCELONLY 2 // just the accel data #define DISPLAY_COMPASSONLY 3 // just the compass data class RTHostIMUThread; class IMUView; class RTHostIMUGL : public QMainWindow { Q_OBJECT public: RTHostIMUGL(); ~RTHostIMUGL(); public slots: void onSelectFusionAlgorithm(); void onCalibrateAccelerometers(); void onCalibrateMagnetometers(); void onEnableGyro(int); void onEnableAccel(int); void onEnableCompass(int); void onEnableDebug(int); void newIMUData(const RTIMU_DATA&); void IMURunning(); void RTArduLinkStatus(int port, int address, bool active, QString identity, qint64 pollsIn, qint64 pollsOut); void RTArduLinkPortOpen(int port); void RTArduLinkPortClosed(int port); void RTArduLinkPortTX(int port); void RTArduLinkPortRX(int port); void newComSetting(int); signals: void newIMU(); protected: void timerEvent(QTimerEvent *event); void closeEvent(QCloseEvent *event); private: void layoutStatusBar(); void layoutWindow(); void updateComState(bool open); void updateComRXTX(QLabel *label, bool active); void populateComPorts(); void loadSettings(); void saveSettings(); QSettings *m_settings; RTHostIMUThread *m_imuThread; // the thread that operates the imu RTIMU_DATA m_imuData; // this holds the IMU information and funsion output // Qt GUI stuff Ui::RTHostIMUGLClass ui; QLabel *getFixedPanel(QString text); QLabel *m_comLabel; QLabel *m_comRXLabel; QLabel *m_comTXLabel; QComboBox *m_comPort; QComboBox *m_comSpeed; QLabel *m_subsystem; QLabel *m_fusionQPoseScalar; QLabel *m_fusionQPoseX; QLabel *m_fusionQPoseY; QLabel *m_fusionQPoseZ; QLabel *m_fusionPoseX; QLabel *m_fusionPoseY; QLabel *m_fusionPoseZ; QLabel *m_gyroX; QLabel *m_gyroY; QLabel *m_gyroZ; QLabel *m_accelX; QLabel *m_accelY; QLabel *m_accelZ; QLabel *m_accelMagnitude; QLabel *m_accelResidualX; QLabel *m_accelResidualY; QLabel *m_accelResidualZ; QLabel *m_compassX; QLabel *m_compassY; QLabel *m_compassZ; QLabel *m_compassMagnitude; QLabel *m_fusionType; QCheckBox *m_enableGyro; QCheckBox *m_enableAccel; QCheckBox *m_enableCompass; QCheckBox *m_enableDebug; QLabel *m_imuType; QLabel *m_biasStatus; QLabel *m_rateStatus; QLabel *m_calStatus; IMUView *m_view; QComboBox *m_displaySelect; int m_rateTimer; int m_displayTimer; int m_sampleCount; }; #endif // _RTHOSTIMUGL_H rtimulib-7.2.1/RTHost/RTHostIMUGL/RTHostIMUGL.pri000066400000000000000000000026021254201074400212100ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014, richards-tech #// #// Permission 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. INCLUDEPATH += $$PWD DEPENDPATH += $$PWD HEADERS += \ RTHostIMUGL.h \ SOURCES += main.cpp \ RTHostIMUGL.cpp \ FORMS += RTHostIMUGL.ui rtimulib-7.2.1/RTHost/RTHostIMUGL/RTHostIMUGL.pro000066400000000000000000000036041254201074400212210ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014, richards-tech #// #// Permission 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. greaterThan(QT_MAJOR_VERSION, 4): cache() TEMPLATE = app TARGET = RTHostIMUGL DESTDIR = Output QT += core gui opengl greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += debug_and_release target.path = /usr/local/bin INSTALLS += target DEFINES += QT_NETWORK_LIB INCLUDEPATH += GeneratedFiles MOC_DIR += GeneratedFiles/moc OBJECTS_DIR += objects UI_DIR += GeneratedFiles RCC_DIR += GeneratedFiles include(RTHostIMUGL.pri) include(../RTSerialPort/src/qextserialport.pri) include(../../RTIMULib/RTIMULib.pri) include(../RTHostIMUCommon/RTHostIMUCommon.pri) include(../RTArduLinkHost/RTArduLinkHost.pri) include(../RTIMULibGL/RTIMULibGL.pri) rtimulib-7.2.1/RTHost/RTHostIMUGL/RTHostIMUGL.sln000066400000000000000000000063071254201074400212200ustar00rootroot00000000000000 Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RTHostIMUGL", "RTHostIMUGL.vcxproj", "{8DE49D40-0CE4-4E17-9377-543DDF0C3A57}" ProjectSection(ProjectDependencies) = postProject {88277939-80B3-4156-9CD7-D2DE7A339BFB} = {88277939-80B3-4156-9CD7-D2DE7A339BFB} {183C43F1-DC99-467E-8364-AA7E69E8E1A8} = {183C43F1-DC99-467E-8364-AA7E69E8E1A8} EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "QTGLLib", "..\RTIMULibGL\QtGLLib\QTGLLib.vcxproj", "{88277939-80B3-4156-9CD7-D2DE7A339BFB}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VRWidgetLib", "..\RTIMULibGL\VRWidgetLib\VRWidgetLib.vcxproj", "{183C43F1-DC99-467E-8364-AA7E69E8E1A8}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 Release|Win32 = Release|Win32 StaticDebug|Win32 = StaticDebug|Win32 StaticRelease|Win32 = StaticRelease|Win32 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {8DE49D40-0CE4-4E17-9377-543DDF0C3A57}.Debug|Win32.ActiveCfg = Debug|Win32 {8DE49D40-0CE4-4E17-9377-543DDF0C3A57}.Debug|Win32.Build.0 = Debug|Win32 {8DE49D40-0CE4-4E17-9377-543DDF0C3A57}.Release|Win32.ActiveCfg = Release|Win32 {8DE49D40-0CE4-4E17-9377-543DDF0C3A57}.Release|Win32.Build.0 = Release|Win32 {8DE49D40-0CE4-4E17-9377-543DDF0C3A57}.StaticDebug|Win32.ActiveCfg = Debug|Win32 {8DE49D40-0CE4-4E17-9377-543DDF0C3A57}.StaticDebug|Win32.Build.0 = Debug|Win32 {8DE49D40-0CE4-4E17-9377-543DDF0C3A57}.StaticRelease|Win32.ActiveCfg = Release|Win32 {8DE49D40-0CE4-4E17-9377-543DDF0C3A57}.StaticRelease|Win32.Build.0 = Release|Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB}.Debug|Win32.ActiveCfg = Debug|Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB}.Debug|Win32.Build.0 = Debug|Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB}.Release|Win32.ActiveCfg = Release|Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB}.Release|Win32.Build.0 = Release|Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB}.StaticDebug|Win32.ActiveCfg = StaticDebug|Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB}.StaticDebug|Win32.Build.0 = StaticDebug|Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB}.StaticRelease|Win32.ActiveCfg = StaticRelease|Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB}.StaticRelease|Win32.Build.0 = StaticRelease|Win32 {183C43F1-DC99-467E-8364-AA7E69E8E1A8}.Debug|Win32.ActiveCfg = Debug|Win32 {183C43F1-DC99-467E-8364-AA7E69E8E1A8}.Debug|Win32.Build.0 = Debug|Win32 {183C43F1-DC99-467E-8364-AA7E69E8E1A8}.Release|Win32.ActiveCfg = Release|Win32 {183C43F1-DC99-467E-8364-AA7E69E8E1A8}.Release|Win32.Build.0 = Release|Win32 {183C43F1-DC99-467E-8364-AA7E69E8E1A8}.StaticDebug|Win32.ActiveCfg = Debug|Win32 {183C43F1-DC99-467E-8364-AA7E69E8E1A8}.StaticDebug|Win32.Build.0 = Debug|Win32 {183C43F1-DC99-467E-8364-AA7E69E8E1A8}.StaticRelease|Win32.ActiveCfg = Release|Win32 {183C43F1-DC99-467E-8364-AA7E69E8E1A8}.StaticRelease|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution Qt5Version = $(DefaultQtVersion) EndGlobalSection EndGlobal rtimulib-7.2.1/RTHost/RTHostIMUGL/RTHostIMUGL.ui000066400000000000000000000051001254201074400210270ustar00rootroot00000000000000 RTHostIMUGLClass 0 0 658 562 0 0 RTHostIMUGL 400 300 0 0 658 21 Actions toolBar TopToolBarArea false Select IMU Select fusion algorithm Calibrate magnetometers Calibrate accelerometers Exit rtimulib-7.2.1/RTHost/RTHostIMUGL/RTHostIMUGL.vcxproj000066400000000000000000001266771254201074400221340ustar00rootroot00000000000000 Debug Win32 Release Win32 {8DE49D40-0CE4-4E17-9377-543DDF0C3A57} Qt4VSv1.0 Application v120 Application v120 <_ProjectFileVersion>10.0.40219.1 $(SolutionDir)$(Platform)\$(Configuration)\ $(SolutionDir)$(Platform)\$(Configuration)\ UNICODE;WIN32;WIN64;QT_DLL;QT_CORE_LIB;QT_GUI_LIB;QT_OPENGL_LIB;QT_WIDGETS_LIB;%(PreprocessorDefinitions) ..\RTIMULibGL;..\RTSerialPort\src;..\RTArduLinkHost;..\RTHostIMUCommon;..\..\RTIMULib;..\RTIMULibGL\VRWidgetLib;..\RTIMULibGL\QtGLLib;.\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtOpenGL;$(QTDIR)\include\QtWidgets;%(AdditionalIncludeDirectories) Disabled ProgramDatabase MultiThreadedDebugDLL false Windows $(OutDir)\$(ProjectName).exe $(SolutionDir)$(Platform)\$(Configuration);$(QTDIR)\lib;%(AdditionalLibraryDirectories) true QTGLLib.lib;VRWidgetLib.lib;Setupapi.lib;qtmaind.lib;Qt5Cored.lib;Qt5Guid.lib;Qt5OpenGLd.lib;opengl32.lib;glu32.lib;Qt5Widgetsd.lib;%(AdditionalDependencies) UNICODE;WIN32;WIN64;QT_DLL;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_GUI_LIB;QT_OPENGL_LIB;QT_WIDGETS_LIB;%(PreprocessorDefinitions) ..\RTIMULibGL;..\RTSerialPort\src;..\RTArduLinkHost;..\RTHostIMUCommon;..\..\RTIMULib;..\RTIMULibGL\VRWidgetLib;..\RTIMULibGL\QtGLLib;.\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtGui;$(QTDIR)\include\QtOpenGL;$(QTDIR)\include\QtWidgets;%(AdditionalIncludeDirectories) MultiThreadedDLL false Windows $(OutDir)\$(ProjectName).exe $(SolutionDir)$(Platform)\$(Configuration);$(QTDIR)\lib;%(AdditionalLibraryDirectories) false QTGLLib.lib;VRWidgetLib.lib;Setupapi.lib;qtmain.lib;Qt5Core.lib;Qt5Gui.lib;Qt5OpenGL.lib;opengl32.lib;glu32.lib;Qt5Widgets.lib;%(AdditionalDependencies) true true true true true true true true true true true true true true true true true true true true true true true true $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing RTHostIMUGL.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB "-I.\..\RTIMULibGL" "-I.\..\RTSerialPort\src" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\..\..\RTIMULib" "-I.\..\RTIMULibGL\VRWidgetLib" "-I.\..\RTIMULibGL\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing RTHostIMUGL.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB "-I.\..\RTIMULibGL" "-I.\..\RTSerialPort\src" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\..\..\RTIMULib" "-I.\..\RTIMULibGL\VRWidgetLib" "-I.\..\RTIMULibGL\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" Document $(QTDIR)\bin\uic.exe;%(AdditionalInputs) Uic%27ing %(Identity)... .\GeneratedFiles\ui_%(Filename).h;%(Outputs) "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" $(QTDIR)\bin\uic.exe;%(AdditionalInputs) Uic%27ing %(Identity)... .\GeneratedFiles\ui_%(Filename).h;%(Outputs) "$(QTDIR)\bin\uic.exe" -o ".\GeneratedFiles\ui_%(Filename).h" "%(FullPath)" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing RTArduLinkHost.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB "-I.\..\RTIMULibGL" "-I.\..\RTSerialPort\src" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\..\..\RTIMULib" "-I.\..\RTIMULibGL\VRWidgetLib" "-I.\..\RTIMULibGL\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing RTArduLinkHost.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB "-I.\..\RTIMULibGL" "-I.\..\RTSerialPort\src" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\..\..\RTIMULib" "-I.\..\RTIMULibGL\VRWidgetLib" "-I.\..\RTIMULibGL\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing AccelCalDlg.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB "-I.\..\RTIMULibGL" "-I.\..\RTSerialPort\src" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\..\..\RTIMULib" "-I.\..\RTIMULibGL\VRWidgetLib" "-I.\..\RTIMULibGL\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing AccelCalDlg.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB "-I.\..\RTIMULibGL" "-I.\..\RTSerialPort\src" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\..\..\RTIMULib" "-I.\..\RTIMULibGL\VRWidgetLib" "-I.\..\RTIMULibGL\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing MagCalDlg.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB "-I.\..\RTIMULibGL" "-I.\..\RTSerialPort\src" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\..\..\RTIMULib" "-I.\..\RTIMULibGL\VRWidgetLib" "-I.\..\RTIMULibGL\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing MagCalDlg.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB "-I.\..\RTIMULibGL" "-I.\..\RTSerialPort\src" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\..\..\RTIMULib" "-I.\..\RTIMULibGL\VRWidgetLib" "-I.\..\RTIMULibGL\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing RTHostIMUClient.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB "-I.\..\RTIMULibGL" "-I.\..\RTSerialPort\src" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\..\..\RTIMULib" "-I.\..\RTIMULibGL\VRWidgetLib" "-I.\..\RTIMULibGL\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing RTHostIMUClient.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB "-I.\..\RTIMULibGL" "-I.\..\RTSerialPort\src" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\..\..\RTIMULib" "-I.\..\RTIMULibGL\VRWidgetLib" "-I.\..\RTIMULibGL\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing RTHostIMUThread.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB "-I.\..\RTIMULibGL" "-I.\..\RTSerialPort\src" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\..\..\RTIMULib" "-I.\..\RTIMULibGL\VRWidgetLib" "-I.\..\RTIMULibGL\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing RTHostIMUThread.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB "-I.\..\RTIMULibGL" "-I.\..\RTSerialPort\src" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\..\..\RTIMULib" "-I.\..\RTIMULibGL\VRWidgetLib" "-I.\..\RTIMULibGL\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing SelectFusionDlg.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB "-I.\..\RTIMULibGL" "-I.\..\RTSerialPort\src" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\..\..\RTIMULib" "-I.\..\RTIMULibGL\VRWidgetLib" "-I.\..\RTIMULibGL\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing SelectFusionDlg.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB "-I.\..\RTIMULibGL" "-I.\..\RTSerialPort\src" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\..\..\RTIMULib" "-I.\..\RTIMULibGL\VRWidgetLib" "-I.\..\RTIMULibGL\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing IMUView.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB "-I.\..\RTIMULibGL" "-I.\..\RTSerialPort\src" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\..\..\RTIMULib" "-I.\..\RTIMULibGL\VRWidgetLib" "-I.\..\RTIMULibGL\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing IMUView.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB "-I.\..\RTIMULibGL" "-I.\..\RTSerialPort\src" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\..\..\RTIMULib" "-I.\..\RTIMULibGL\VRWidgetLib" "-I.\..\RTIMULibGL\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing qextserialenumerator.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB "-I.\..\RTIMULibGL" "-I.\..\RTSerialPort\src" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\..\..\RTIMULib" "-I.\..\RTIMULibGL\VRWidgetLib" "-I.\..\RTIMULibGL\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing qextserialenumerator.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB "-I.\..\RTIMULibGL" "-I.\..\RTSerialPort\src" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\..\..\RTIMULib" "-I.\..\RTIMULibGL\VRWidgetLib" "-I.\..\RTIMULibGL\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing qextserialport.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB "-I.\..\RTIMULibGL" "-I.\..\RTSerialPort\src" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\..\..\RTIMULib" "-I.\..\RTIMULibGL\VRWidgetLib" "-I.\..\RTIMULibGL\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing qextserialport.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB "-I.\..\RTIMULibGL" "-I.\..\RTSerialPort\src" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\..\..\RTIMULib" "-I.\..\RTIMULibGL\VRWidgetLib" "-I.\..\RTIMULibGL\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing qextwineventnotifier_p.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB "-I.\..\RTIMULibGL" "-I.\..\RTSerialPort\src" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\..\..\RTIMULib" "-I.\..\RTIMULibGL\VRWidgetLib" "-I.\..\RTIMULibGL\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing qextwineventnotifier_p.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_GUI_LIB -DQT_OPENGL_LIB -DQT_WIDGETS_LIB "-I.\..\RTIMULibGL" "-I.\..\RTSerialPort\src" "-I.\..\RTArduLinkHost" "-I.\..\RTHostIMUCommon" "-I.\..\..\RTIMULib" "-I.\..\RTIMULibGL\VRWidgetLib" "-I.\..\RTIMULibGL\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" Document %(FullPath);.\GL\VRWidgetLib\Images\Compass.png;.\GL\VRWidgetLib\Images\CompassNeedle.png;.\GL\VRWidgetLib\Images\RedShade.png;.\GL\VRWidgetLib\Images\BlueShade.png;.\GL\VRWidgetLib\Images\GreenShade.png;%(AdditionalInputs) Rcc%27ing %(Identity)... .\GeneratedFiles\qrc_%(Filename).cpp;%(Outputs) "$(QTDIR)\bin\rcc.exe" -name "%(Filename)" -no-compress "%(FullPath)" -o .\GeneratedFiles\qrc_%(Filename).cpp %(FullPath);.\GL\VRWidgetLib\Images\Compass.png;.\GL\VRWidgetLib\Images\CompassNeedle.png;.\GL\VRWidgetLib\Images\RedShade.png;.\GL\VRWidgetLib\Images\BlueShade.png;.\GL\VRWidgetLib\Images\GreenShade.png;%(AdditionalInputs) Rcc%27ing %(Identity)... .\GeneratedFiles\qrc_%(Filename).cpp;%(Outputs) "$(QTDIR)\bin\rcc.exe" -name "%(Filename)" -no-compress "%(FullPath)" -o .\GeneratedFiles\qrc_%(Filename).cpp true true true true true true true true true true rtimulib-7.2.1/RTHost/RTHostIMUGL/RTHostIMUGL.vcxproj.filters000066400000000000000000000407421254201074400235670ustar00rootroot00000000000000 {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;cxx;c;def {93995380-89BD-4b04-88EB-625FBE52EBFB} h {99349809-55BA-4b9d-BF79-8FDBB0286EB3} ui {D9D6E242-F8AF-46E4-B9FD-80ECBC20BA3E} qrc;* false {71ED8ED8-ACB9-4CE9-BBE1-E00B30144E11} moc;h;cpp False {e957c9d6-fb47-4d2d-9580-fa5aa500c86f} cpp;moc False {d7a28fc1-e2bb-4e1b-929e-14c0947e4451} cpp;moc False {23b65940-c2bb-47ac-87d3-ed6f6e486e59} {d4bc46d4-86cd-438f-a198-4288ae31c157} {88a33f2e-0443-486f-bba9-0e33ecb85631} {6e37a376-d6bb-42a0-ad3e-c8c34a97cb25} {7e59dc07-5c9e-4eda-9618-87068eb1ffe4} {4566c7df-2deb-4802-afa9-ef71a8e0e88a} Source Files Source Files Generated Files\Debug Generated Files\Release RTHostIMUCommon Generated Files\Debug Generated Files\Release RTHostIMUCommon Generated Files\Debug Generated Files\Release RTHostIMUCommon Generated Files\Debug Generated Files\Release RTArduLinkHost Generated Files\Debug Generated Files\Release RTArduLinkHost Generated Files RTHostIMUCommon Generated Files\Debug Generated Files\Release RTHostIMUCommon Generated Files\Debug Generated Files\Release RTSerialPort Generated Files\Debug Generated Files\Release RTSerialPort RTSerialPort Generated Files\Debug Generated Files\Release RTSerialPort RTSerialPort Generated Files\Debug Generated Files\Release RTIMULibGL Generated Files\Debug Generated Files\Release RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers Header Files Form Files RTHostIMUCommon RTHostIMUCommon RTHostIMUCommon RTArduLinkHost RTHostIMUCommon RTHostIMUCommon RTSerialPort RTSerialPort RTSerialPort Resource Files RTIMULibGL Generated Files RTHostIMUCommon RTArduLinkHost RTArduLinkHost RTArduLinkHost RTSerialPort RTSerialPort RTSerialPort RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers RTIMULib\IMUDrivers Resource Files Resource Files Resource Files Resource Files Resource Files RTIMULib RTIMULib rtimulib-7.2.1/RTHost/RTHostIMUGL/main.cpp000066400000000000000000000027031254201074400201450ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "RTHostIMUGL.h" #include // This is standard Qt startup int main(int argc, char *argv[]) { QApplication a(argc, argv); RTHostIMUGL *w = new RTHostIMUGL(); w->show(); return a.exec(); } rtimulib-7.2.1/RTHost/RTIMULibGL/000077500000000000000000000000001254201074400163045ustar00rootroot00000000000000rtimulib-7.2.1/RTHost/RTIMULibGL/CMakeLists.txt000066400000000000000000000105201254201074400210420ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014, richards-tech #// #// Permission 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. #// The cmake support was based on work by Moritz Fischer at ettus.com. #// Original copyright notice: # # Copyright 2014 Ettus Research LLC # INCLUDE(${CMAKE_CURRENT_SOURCE_DIR}/../../RTIMULibVersion.txt) SET(LIBGL_SRCS IMUView.cpp) SET(GL_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib/QtGLADSShader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib/QtGLADSTextureShader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib/QtGLComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib/QtGL.cpp ${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib/QtGLCylinderComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib/QtGLDiskComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib/QtGLFlatShader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib/QtGLPlaneComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib/QtGLSphereComponent.cpp ${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib/QtGLTextureShader.cpp ${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib/QtGLWireCubeComponent.cpp) SET(VRIMU_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/VRWidgetLib/VRIMUWidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/VRWidgetLib/VRWidget.cpp) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/QtGLLib) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/VRWidgetLib) SET(CMAKE_AUTOMOC ON) IF(WIN32) SET(EXTRA_LIBS opengl32) ENDIF(WIN32) IF (UNIX AND (NOT APPLE)) SET(EXTRA_LIBS GL) ENDIF(UNIX AND (NOT APPLE)) IF(DEFINED QT5) FIND_PACKAGE(Qt5Widgets REQUIRED) FIND_PACKAGE(Qt5Core REQUIRED) FIND_PACKAGE(Qt5OpenGL REQUIRED) FIND_PACKAGE(Qt5Gui REQUIRED) qt5_add_resources(RCC_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/VRWidgetLib/VRWidgetLib.qrc) IF(WIN32) ADD_LIBRARY(RTIMULibGL STATIC ${LIBGL_SRCS} ${GL_SRCS} ${VRIMU_SRCS} ${GL_HEADERS} ${RCC_HEADERS}) ELSE(WIN32) ADD_LIBRARY(RTIMULibGL SHARED ${LIBGL_SRCS} ${GL_SRCS} ${VRIMU_SRCS} ${GL_HEADERS} ${RCC_HEADERS}) SET_PROPERTY(TARGET RTIMULibGL PROPERTY VERSION ${RTIMULIB_VERSION}) SET_PROPERTY(TARGET RTIMULibGL PROPERTY SOVERSION ${RTIMULIB_VERSION_MAJOR}) ENDIF(WIN32) TARGET_LINK_LIBRARIES(RTIMULibGL RTIMULib RTHostIMUCommon RTArduLinkHost RTSerialPort ${Qt5Gui_OPENGL_LIBRARIES} ${Qt5Core_QTMAIN_LIBRARIES} ${EXTRA_LIBS}) qt5_use_modules(RTIMULibGL Widgets Core OpenGL) ELSE(DEFINED QT5) FIND_PACKAGE(Qt4 REQUIRED) SET(QT_USE_QTOPENGL TRUE) INCLUDE(${QT_USE_FILE}) ADD_DEFINITIONS(${QT_DEFINITIONS}) QT4_ADD_RESOURCES(RCC_HEADERS ${CMAKE_CURRENT_SOURCE_DIR}/VRWidgetLib/VRWidgetLib.qrc) IF(WIN32) ADD_LIBRARY(RTIMULibGL STATIC ${LIBGL_SRCS} ${GL_SRCS} ${VRIMU_SRCS} ${GL_HEADERS} ${RCC_HEADERS}) ELSE(WIN32) ADD_LIBRARY(RTIMULibGL SHARED ${LIBGL_SRCS} ${GL_SRCS} ${VRIMU_SRCS} ${GL_HEADERS} ${RCC_HEADERS}) SET_PROPERTY(TARGET RTIMULibGL PROPERTY VERSION ${RTIMULIB_VERSION}) SET_PROPERTY(TARGET RTIMULibGL PROPERTY SOVERSION ${RTIMULIB_VERSION_MAJOR}) ENDIF(WIN32) TARGET_LINK_LIBRARIES(RTIMULibGL RTIMULib RTHostIMUCommon RTArduLinkHost ${QT_LIBRARIES} ${EXTRA_LIBS}) ENDIF(DEFINED QT5) INSTALL(TARGETS RTIMULibGL DESTINATION lib) rtimulib-7.2.1/RTHost/RTIMULibGL/IMUView.cpp000066400000000000000000000076331254201074400203060ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "IMUView.h" #include #include #include #include IMUView::IMUView(QWidget *parent) : QGLWidget(QGLFormat(QGL::SampleBuffers), parent) { globalGLWidget = this; QGLFormat fmt = context()->format(); m_IMUWidget = NULL; globalTransforms.viewportFieldOfView = 45.0; globalTransforms.tanViewportFOV = tan(globalTransforms.viewportFieldOfView * QTGL_DEGREE_TO_RAD / 2.0); setAutoFillBackground(false); } void IMUView::closeEvent(QCloseEvent *) { if (m_IMUWidget) { delete m_IMUWidget; m_IMUWidget = NULL; } for (int index = 0; index < QTGLSHADER_COUNT; index++) delete globalShader[index]; } void IMUView::updateIMU(RTVector3& pose) { m_IMUWidget->setRotation( QTGL_RAD_TO_DEGREE * pose.x(), -QTGL_RAD_TO_DEGREE * pose.z(), QTGL_RAD_TO_DEGREE * pose.y()); updateGL(); } void IMUView::initializeGL() { initializeGLFunctions(); QtGLInit(this); QtGLAddLight(QVector4D(20.0f, 5.0f, 0.0f, 1.0f), QVector3D(0.2f, 0.2f, 0.2f), QVector3D(0.2f, 0.2f, 0.2f), QVector3D(0.4f, 0.4f, 0.4f)); QtGLAddLight(QVector4D(-20.0f, 5.0f, 0.0f, 1.0f), QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.2f, 0.2f, 0.2f), QVector3D(0.8f, 0.8f, 0.8f)); QtGLAddLight(QVector4D(0.0f, 0.0f, 0.0f, 1.0f), QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.2f, 0.2f, 0.2f), QVector3D(0.5f, 0.5f, 0.5f)); m_IMUWidget = new VRIMUWidget(globalGLWidget); if (m_IMUWidget) { m_IMUWidget->setRotation(QVector3D()); m_IMUWidget->VRWidgetInit(); } } void IMUView::paintGL() { QMutexLocker lock(&m_lock); qglClearColor(QColor(20, 40, 80)); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); m_IMUWidget->VRWidgetRender(); } void IMUView::resizeGL(int width, int height) { setupViewport(width, height); } void IMUView::setupViewport(int width, int height) { float w2 = width / 2; float tanFOV = globalTransforms.tanViewportFOV; globalTransforms.width = width; globalTransforms.height = height; globalTransforms.nearPlane = 1.0f; globalTransforms.farPlane = w2 / tanFOV; globalTransforms.halfWidth = width / 2.0f; globalTransforms.halfHeight = height / 2.0f; globalTransforms.viewportAspect = (float)width / (float)height; glViewport(0, 0, width, height); globalTransforms.projectionMatrix.setToIdentity(); globalTransforms.modelViewMatrix.setToIdentity(); globalTransforms.projectionMatrix.frustum(-tanFOV, +tanFOV, -tanFOV/globalTransforms.viewportAspect, tanFOV/globalTransforms.viewportAspect, globalTransforms.nearPlane, globalTransforms.farPlane); m_IMUWidget->setCenter(0, 0, IMUVIEW_DEPTH); } rtimulib-7.2.1/RTHost/RTIMULibGL/IMUView.h000066400000000000000000000036531254201074400177510ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef IMUVIEW_H #define IMUVIEW_H #include #include #include #include "VRIMUWidget.h" #include "RTMath.h" #define IMUVIEW_DEPTH -15 // normal IMU position #define IMUVIEW_RESTART_INTERVAL 1000 // length of time in restart interval class VRIMUWidget; class IMUView : public QGLWidget, protected QGLFunctions { Q_OBJECT public: IMUView(QWidget *parent = NULL); void updateIMU(RTVector3& pose); protected: void initializeGL(); void resizeGL(int width, int height); void paintGL(); void closeEvent(QCloseEvent *); private: void setupViewport(int width, int height); VRIMUWidget *m_IMUWidget; QMutex m_lock; }; #endif // IMUVIEW_H rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/000077500000000000000000000000001254201074400175425ustar00rootroot00000000000000rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QTGLLib.sln000066400000000000000000000024301254201074400214550ustar00rootroot00000000000000 Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "QTGLLib", "QTGLLib.vcxproj", "{88277939-80B3-4156-9CD7-D2DE7A339BFB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 Release|Win32 = Release|Win32 StaticDebug|Win32 = StaticDebug|Win32 StaticRelease|Win32 = StaticRelease|Win32 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {88277939-80B3-4156-9CD7-D2DE7A339BFB}.Debug|Win32.ActiveCfg = Debug|Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB}.Debug|Win32.Build.0 = Debug|Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB}.Release|Win32.ActiveCfg = Release|Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB}.Release|Win32.Build.0 = Release|Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB}.StaticDebug|Win32.ActiveCfg = StaticDebug|Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB}.StaticDebug|Win32.Build.0 = StaticDebug|Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB}.StaticRelease|Win32.ActiveCfg = StaticRelease|Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB}.StaticRelease|Win32.Build.0 = StaticRelease|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QTGLLib.vcxproj000066400000000000000000000224321254201074400223600ustar00rootroot00000000000000 Debug Win32 Release Win32 StaticDebug Win32 StaticRelease Win32 {88277939-80B3-4156-9CD7-D2DE7A339BFB} Qt4VSv1.0 StaticLibrary v120 StaticLibrary v120 StaticLibrary v120 StaticLibrary v120 <_ProjectFileVersion>10.0.40219.1 $(SolutionDir)$(Platform)\$(Configuration)\ $(Configuration)\ $(SolutionDir)$(Platform)\$(Configuration)\ $(Configuration)\ $(ProjectName)d UNICODE;WIN32;QT_LARGEFILE_SUPPORT;QT_CORE_LIB;QT_OPENGL_LIB;QTGLLIB_LIB;%(PreprocessorDefinitions) .\GeneratedFiles;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtOpenGL;.;%(AdditionalIncludeDirectories) Disabled ProgramDatabase MultiThreadedDebugDLL false $(OutDir)\$(ProjectName).lib $(QTDIR)\lib;%(AdditionalLibraryDirectories) UNICODE;WIN32;QT_LARGEFILE_SUPPORT;QT_CORE_LIB;QT_OPENGL_LIB;QTGLLIB_LIB;%(PreprocessorDefinitions) .\GeneratedFiles;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtOpenGL;.;%(AdditionalIncludeDirectories) Disabled ProgramDatabase MultiThreadedDebugDLL false $(OutDir)\$(ProjectName)d.lib $(QTDIR)\lib;%(AdditionalLibraryDirectories) UNICODE;WIN32;QT_LARGEFILE_SUPPORT;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_OPENGL_LIB;QTGLLIB_LIB;%(PreprocessorDefinitions) .\GeneratedFiles;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtOpenGL;.;%(AdditionalIncludeDirectories) MultiThreadedDLL false $(OutDir)\$(ProjectName).lib $(QTDIR)\lib;%(AdditionalLibraryDirectories) UNICODE;WIN32;QT_LARGEFILE_SUPPORT;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_OPENGL_LIB;QTGLLIB_LIB;%(PreprocessorDefinitions) .\GeneratedFiles;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtOpenGL;.;%(AdditionalIncludeDirectories) MultiThreadedDLL false $(OutDir)\$(ProjectName).lib $(QTDIR)\lib;%(AdditionalLibraryDirectories) rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QTGLLib.vcxproj.filters000066400000000000000000000065631254201074400240360ustar00rootroot00000000000000 {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;cxx;c;def {93995380-89BD-4b04-88EB-625FBE52EBFB} h {99349809-55BA-4b9d-BF79-8FDBB0286EB3} ui {D9D6E242-F8AF-46E4-B9FD-80ECBC20BA3E} qrc;* false {71ED8ED8-ACB9-4CE9-BBE1-E00B30144E11} moc;h;cpp False Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Header Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files Source Files rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGL.cpp000066400000000000000000000101341254201074400210540ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // This file contains OpenGL and Qt includes. This file should be included first // and the order of includes is very important (especially on Linux). #include QGLWidget *globalGLWidget; // the global GL widget TRANSFORM_MATRICES globalTransforms; // the various viewport transforms LIGHT_SOURCES globalLights; // the lights QtGLShader *globalShader[QTGLSHADER_COUNT]; // the shaders void QtGLInit(QGLWidget *widget) { globalLights.lightCount = 0; // no lights globalShader[QTGLSHADER_FLAT] = new QtGLFlatShader(widget); globalShader[QTGLSHADER_TEXTURE] = new QtGLTextureShader(widget); globalShader[QTGLSHADER_ADS] = new QtGLADSShader(widget); globalShader[QTGLSHADER_ADSTEXTURE] = new QtGLADSTextureShader(widget); } void QtGLAddLight(const QVector4D& position, const QVector3D& ambient, const QVector3D& diffuse, const QVector3D& specular) { int count; if ((count = globalLights.lightCount) == QTGL_MAX_LIGHTS) { qDebug() << "Too many lights"; return; } globalLights.ambient[count] = ambient; globalLights.diffuse[count] = diffuse; globalLights.specular[count] = specular; globalLights.position[count] = position; globalLights.lightCount++; } bool QtGLRayRectangleIntersection(QVector3D& intersection, const QVector3D& ray0, const QVector3D& ray1, const QVector3D& plane0, const QVector3D& plane1, const QVector3D& plane2, const QVector3D& plane3, bool checkInside) { QVector3D normal; QVector3D test; float dist0, dist1; // Compute normal to plane normal = QVector3D::crossProduct(plane1 - plane0, plane2 - plane0); normal.normalize(); // find distance from points defining line to plane dist0 = QVector3D::dotProduct(ray0 - plane0, normal); dist1 = QVector3D::dotProduct(ray1 - plane0, normal); if (qFuzzyCompare(dist0, dist1)) return false; // line and plane are parallel intersection = ray0 + (ray1 - ray0) * (-dist0 / (dist1 - dist0)); if (!checkInside) return true; // check if intersection point lies within the rectangle test = QVector3D::crossProduct(normal, plane1 - plane0); if (QVector3D::dotProduct(test, intersection - plane0) < 0.0f) return false; test = QVector3D::crossProduct(normal, plane2 - plane1); if (QVector3D::dotProduct(test, intersection - plane1) < 0.0f) return false; test = QVector3D::crossProduct(normal, plane3 - plane2); if (QVector3D::dotProduct(test, intersection - plane2) < 0.0f) return false; test = QVector3D::crossProduct(normal, plane0 - plane3); if (QVector3D::dotProduct(test, intersection - plane3) < 0.0f) return false; return true; } rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGL.h000066400000000000000000000100611254201074400205200ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef QTGL_H #define QTGL_H #include #define QTGL_PI 3.1415926535 #define QTGL_DEGREE_TO_RAD (QTGL_PI / 180.0) #define QTGL_RAD_TO_DEGREE (180.0 / QTGL_PI) #define QTGL_MAX_LIGHTS 4 typedef struct { QMatrix4x4 modelViewMatrix; // the model view matrix QMatrix4x4 projectionMatrix; // the projection matrix QMatrix4x4 modelViewProjectionMatrix; // the model view projection matrix QMatrix3x3 normalMatrix; // the normal matrix float viewportAspect; // the viewport aspect ratio float viewportFieldOfView; // angular field of view in x direction float tanViewportFOV; // tan of the field of view float width; // viewport width float height; // viewport height float halfWidth; // half viewport width float halfHeight; // half viewport height float nearPlane; // where the near plane is float farPlane; // viewport distance } TRANSFORM_MATRICES; typedef struct { int lightCount; QVector4D position[QTGL_MAX_LIGHTS]; QVector3D ambient[QTGL_MAX_LIGHTS]; QVector3D diffuse[QTGL_MAX_LIGHTS]; QVector3D specular[QTGL_MAX_LIGHTS]; } LIGHT_SOURCES; typedef struct { QVector3D ambientReflectivity; QVector3D diffuseReflectivity; QVector3D specularReflectivity; float shininess; } COMPONENT_MATERIAL; #include "QtGLShader.h" #include "QtGLTextureShader.h" #include "QtGLFlatShader.h" #include "QtGLADSShader.h" #include "QtGLADSTextureShader.h" #include "QtGLComponent.h" #include "QtGLPlaneComponent.h" #include "QtGLCylinderComponent.h" #include "QtGLDiskComponent.h" #include "QtGLWireCubeComponent.h" #include "QtGLSphereComponent.h" extern QGLWidget *globalGLWidget; // the global GL widget extern TRANSFORM_MATRICES globalTransforms; // the various viewport transforms extern LIGHT_SOURCES globalLights; // the lights extern QtGLShader *globalShader[QTGLSHADER_COUNT]; // the shaders void QtGLInit(QGLWidget *widget); void QtGLAddLight(const QVector4D& position, const QVector3D& ambient, const QVector3D& diffuse, const QVector3D& specular); bool QtGLRayRectangleIntersection(QVector3D& intersection, const QVector3D& ray0, const QVector3D& ray1, const QVector3D& plane0, const QVector3D& plane1, const QVector3D& plane2, const QVector3D& plane3, bool checkInside); #endif // QTGL_H rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGLADSShader.cpp000066400000000000000000000115171254201074400225410ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "QtGL.h" QtGLADSShader::QtGLADSShader(QObject *parent) : QtGLShader(parent) { #define PROGRAM_VERTEX_ATTRIBUTE 0 #define PROGRAM_NORMAL_ATTRIBUTE 1 m_type = QTGLSHADER_ADS; m_vshader = new QGLShader(QGLShader::Vertex, parent); const char *vsrc = "attribute highp vec3 vertex;\n" "attribute mediump vec3 normal;\n" "varying mediump vec3 lightI;\n" "uniform mediump mat4 MV;\n" "uniform mediump mat4 MVP\n;" "uniform mediump mat3 normalMatrix;\n" "uniform mediump int lightCount;\n" "uniform mediump vec4 posL[4];\n" "uniform mediump vec3 ambientL[4];\n" "uniform mediump vec3 diffuseL[4];\n" "uniform mediump vec3 specularL[4];\n" "uniform mediump vec3 ambientM;\n" "uniform mediump vec3 diffuseM;\n" "uniform mediump vec3 specularM;\n" "uniform mediump float shininess;\n" "void main(void)\n" "{\n" " gl_Position = MVP * vec4(vertex, 1.0);\n" " vec3 tnorm = normalize(normalMatrix * normal);\n" " vec4 eyeCoords = MV * vec4(vertex, 1.0);\n" " vec3 v = normalize(-eyeCoords.xyz);\n" " lightI = vec3(0.0, 0.0, 0.0);\n" " for (int i = 0; i < lightCount; i++) {\n" " vec3 s = normalize(vec3(posL[i] - eyeCoords));\n" " float sDotN = max(dot(s, tnorm), 0.0);\n" " vec3 r = reflect(-s, tnorm);\n" " vec3 ambient = ambientL[i] * ambientM;\n" " vec3 diffuse = diffuseL[i] * diffuseM * sDotN;\n" " vec3 spec = vec3(0.0);\n" " if (sDotN > 0.0) spec = specularL[i] * specularM * pow(max(dot(r,v), 0.0), shininess);\n" " lightI += ambient + diffuse + spec;\n" " }\n" "}\n"; if (!m_vshader->compileSourceCode(vsrc)) qDebug() << "Failed to compile ADS shader"; m_fshader = new QGLShader(QGLShader::Fragment, parent); const char *fsrc = "varying mediump vec3 lightI;\n" "void main(void)\n" "{\n" " gl_FragColor = vec4(lightI, 1.0);\n" "}\n"; if (!m_fshader->compileSourceCode(fsrc)) qDebug() << "Failed to compile ADS fragment shader"; addShader(m_vshader); addShader(m_fshader); bindAttributeLocation("vertex", PROGRAM_VERTEX_ATTRIBUTE); bindAttributeLocation("normal", PROGRAM_NORMAL_ATTRIBUTE); link(); } QtGLADSShader::~QtGLADSShader() { } void QtGLADSShader::load(const QVector3D *vertices, const QVector3D *normals, const COMPONENT_MATERIAL& material) { bind(); setUniformValue("MV", globalTransforms.modelViewMatrix); setUniformValue("MVP", globalTransforms.modelViewProjectionMatrix); setUniformValue("normalMatrix", globalTransforms.normalMatrix); setUniformValue("lightCount", globalLights.lightCount); setUniformValueArray("posL", globalLights.position, globalLights.lightCount); setUniformValueArray("ambientL", globalLights.ambient, globalLights.lightCount); setUniformValueArray("diffuseL", globalLights.diffuse, globalLights.lightCount); setUniformValueArray("specularL", globalLights.specular, globalLights.lightCount); setUniformValue("ambientM", material.ambientReflectivity); setUniformValue("diffuseM", material.diffuseReflectivity); setUniformValue("specularM", material.specularReflectivity); setUniformValue("shininess", material.shininess); enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); enableAttributeArray(PROGRAM_NORMAL_ATTRIBUTE); setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, vertices); setAttributeArray(PROGRAM_NORMAL_ATTRIBUTE, normals); } rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGLADSShader.h000066400000000000000000000030111254201074400221740ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef QTGLADSSHADER_H #define QTGLADSSHADER_H class QtGLADSShader : public QtGLShader { public: QtGLADSShader(QObject *parent); ~QtGLADSShader(); void load(const QVector3D *vertices, const QVector3D *normals, const COMPONENT_MATERIAL& material); private: }; #endif // QTGLADSSHADER_H rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGLADSTextureShader.cpp000066400000000000000000000126151254201074400241220ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "QtGL.h" QtGLADSTextureShader::QtGLADSTextureShader(QObject *parent) : QtGLShader(parent) { #define PROGRAM_VERTEX_ATTRIBUTE 0 #define PROGRAM_NORMAL_ATTRIBUTE 1 #define PROGRAM_TEXTURE_ATTRIBUTE 2 m_type = QTGLSHADER_ADSTEXTURE; m_vshader = new QGLShader(QGLShader::Vertex, parent); const char *vsrc = "attribute highp vec3 vertex;\n" "attribute mediump vec3 normal;\n" "attribute mediump vec2 texCoord;\n" "varying mediump vec2 texc;\n" "varying mediump vec3 lightI;\n" "uniform mediump mat4 MV;\n" "uniform mediump mat4 MVP\n;" "uniform mediump mat3 normalMatrix;\n" "uniform mediump int lightCount;\n" "uniform mediump vec4 posL[4];\n" "uniform mediump vec3 ambientL[4];\n" "uniform mediump vec3 diffuseL[4];\n" "uniform mediump vec3 specularL[4];\n" "uniform mediump vec3 ambientM;\n" "uniform mediump vec3 diffuseM;\n" "uniform mediump vec3 specularM;\n" "uniform mediump float shininess;\n" "void main(void)\n" "{\n" " gl_Position = MVP * vec4(vertex, 1.0);\n" " texc = texCoord;\n" " vec3 tnorm = normalize(normalMatrix * normal);\n" " vec4 eyeCoords = MV * vec4(vertex, 1.0);\n" " vec3 v = normalize(-eyeCoords.xyz);\n" " lightI = vec3(0.0, 0.0, 0.0);\n" " for (int i = 0; i < lightCount; i++) {\n" " vec3 s = normalize(vec3(posL[i] - eyeCoords));\n" " float sDotN = max(dot(s, tnorm), 0.0);\n" " vec3 r = reflect(-s, tnorm);\n" " vec3 ambient = ambientL[i] * ambientM;\n" " vec3 diffuse = diffuseL[i] * diffuseM * sDotN;\n" " vec3 spec = vec3(0.0);\n" " if (sDotN > 0.0) spec = specularL[i] * specularM * pow(max(dot(r,v), 0.0), shininess);\n" " lightI += ambient + diffuse + spec;\n" " }\n" "}\n"; if (!m_vshader->compileSourceCode(vsrc)) qDebug() << "Failed to compile ADS shader"; m_fshader = new QGLShader(QGLShader::Fragment, parent); const char *fsrc = "uniform sampler2D texture;\n" "varying mediump vec2 texc;\n" "varying mediump vec3 lightI;\n" "void main(void)\n" "{\n" " gl_FragColor = texture2D(texture, texc.st) * vec4(lightI, 1.0);\n" "}\n"; if (!m_fshader->compileSourceCode(fsrc)) qDebug() << "Failed to compile ADS fragment shader"; addShader(m_vshader); addShader(m_fshader); bindAttributeLocation("vertex", PROGRAM_VERTEX_ATTRIBUTE); bindAttributeLocation("normal", PROGRAM_NORMAL_ATTRIBUTE); bindAttributeLocation("texCoord", PROGRAM_TEXTURE_ATTRIBUTE); link(); setUniformValue("texture", 0); } QtGLADSTextureShader::~QtGLADSTextureShader() { } void QtGLADSTextureShader::load(const QVector3D *vertices, const QVector3D *normals, const QVector2D* textureCoords, const COMPONENT_MATERIAL& material) { bind(); setUniformValue("MV", globalTransforms.modelViewMatrix); setUniformValue("MVP", globalTransforms.modelViewProjectionMatrix); setUniformValue("normalMatrix", globalTransforms.normalMatrix); setUniformValue("lightCount", globalLights.lightCount); setUniformValueArray("posL", globalLights.position, globalLights.lightCount); setUniformValueArray("ambientL", globalLights.ambient, globalLights.lightCount); setUniformValueArray("diffuseL", globalLights.diffuse, globalLights.lightCount); setUniformValueArray("specularL", globalLights.specular, globalLights.lightCount); setUniformValue("ambientM", material.ambientReflectivity); setUniformValue("diffuseM", material.diffuseReflectivity); setUniformValue("specularM", material.specularReflectivity); setUniformValue("shininess", material.shininess); enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); enableAttributeArray(PROGRAM_NORMAL_ATTRIBUTE); enableAttributeArray(PROGRAM_TEXTURE_ATTRIBUTE); setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, vertices); setAttributeArray(PROGRAM_NORMAL_ATTRIBUTE, normals); setAttributeArray(PROGRAM_TEXTURE_ATTRIBUTE, textureCoords); } rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGLADSTextureShader.h000066400000000000000000000031371254201074400235660ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef QTGLADSTEXTURESHADER_H #define QTGLADSTEXTURESHADER_H class QtGLADSTextureShader : public QtGLShader { public: QtGLADSTextureShader(QObject *parent); ~QtGLADSTextureShader(); void load(const QVector3D *vertices, const QVector3D *normals, const QVector2D *textureCoords, const COMPONENT_MATERIAL& material); private: }; #endif // QTGLADSTEXTURESHADER_H rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGLComponent.cpp000066400000000000000000000232511254201074400227430ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // Based on code from the OpenGL SuperBible: /* * OpenGL SuperBible * Copyright (c) 2007-2009, Richard S. Wright Jr. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Richard S. Wright Jr. nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "QtGL.h" QtGLComponent::QtGLComponent() { m_shader = NULL; m_texture = -1; m_indexBuffer = NULL; m_normalBuffer = NULL; m_useIndexBuffer = false; m_useNormalBuffer = false; } QtGLComponent::~QtGLComponent() { reset(); } void QtGLComponent::reset() { if (m_texture != -1) globalGLWidget->deleteTexture(m_texture); if (m_indexBuffer != NULL) delete m_indexBuffer; m_indexBuffer = NULL; if (m_normalBuffer != NULL) delete m_normalBuffer; m_normalBuffer = NULL; m_vertices.clear(); m_normals.clear(); m_textureCoords.clear(); m_indices.clear(); m_boundMinus = QVector3D(10000.0f, 10000.0f, 10000.0f); m_boundPlus = QVector3D(-10000.0f, -10000.0f, -10000.0f); } void QtGLComponent::addVertex(const QVector3D& vertex) { m_vertices.append(vertex); updateBoundingBox(vertex); } void QtGLComponent::addNormal(const QVector3D& normal) { m_normals.append(normal); } void QtGLComponent::addTextureCoord(const QVector2D& textureCoord) { m_textureCoords.append(textureCoord); } void QtGLComponent::addIndex(GLushort index) { m_indices.append(index); } void QtGLComponent::setShader(QtGLShader *shader) { m_shader = shader; } void QtGLComponent::setTexture(GLuint texture) { if (m_texture != -1) globalGLWidget->deleteTexture(m_texture); m_texture = texture; } void QtGLComponent::setTexture(const QImage& image) { int width, height; int newWidth, newHeight; width = image.width(); height = image.height(); newWidth = nearestPOT(width); // find nearest power of two newHeight = nearestPOT(height); if ((width == newWidth) && (height = newHeight)) { setTexture(globalGLWidget->bindTexture(image)); } else { QImage newImage = image.scaled(newWidth, newHeight, Qt::IgnoreAspectRatio); setTexture(globalGLWidget->bindTexture(newImage)); qDebug() << "New size " << newImage.width() << " " << newImage.height(); } } void QtGLComponent::setMaterial(const COMPONENT_MATERIAL& material) { m_material = material; } void QtGLComponent::setMaterial(const QVector3D& ambient, const QVector3D& diffuse, const QVector3D& specular, float shininess) { m_material.ambientReflectivity = ambient; m_material.diffuseReflectivity = diffuse; m_material.specularReflectivity = specular; m_material.shininess = shininess; } int QtGLComponent::nearestPOT(int val) { if (val <= 3) return 2; if (val <= 6) return 4; if (val <= 12) return 8; if (val <= 24) return 16; if (val <= 48) return 32; if (val <= 96) return 64; if (val <= 192) return 128; if (val <= 384) return 256; if (val <= 768) return 512; if (val <= 1536) return 1024; return 2048; } void QtGLComponent::useIndexBuffer(bool use) { if (m_indexBuffer != NULL) delete m_indexBuffer; m_indexBuffer = NULL; m_useIndexBuffer = use; if (!use) return; m_indexBuffer = new QGLBuffer(QGLBuffer::IndexBuffer); m_indexBuffer->create(); m_indexBuffer->bind(); m_indexBuffer->setUsagePattern(QGLBuffer::StaticDraw); m_indexBuffer->allocate(m_indices.constData(), m_indices.size() * sizeof(GLushort)); m_indexBuffer->release(); } void QtGLComponent::useNormalBuffer(bool use) { if (m_normalBuffer != NULL) delete m_normalBuffer; m_normalBuffer = NULL; m_useNormalBuffer = use; if (!use) return; m_normalBuffer = new QGLBuffer(QGLBuffer::VertexBuffer); m_normalBuffer->create(); m_normalBuffer->bind(); m_normalBuffer->setUsagePattern(QGLBuffer::StaticDraw); m_normalBuffer->allocate(m_normals.constData(), m_normals.size() * sizeof(QVector3D)); m_normalBuffer->release(); } void QtGLComponent::addTriangle(QVector3D *verts, QVector3D *norms, QVector2D *texCoords) { // Search for match - triangle consists of three verts for(GLuint iVertex = 0; iVertex < 3; iVertex++){ GLuint iMatch = 0; for(iMatch = 0; iMatch < (GLuint)m_vertices.size(); iMatch++) { if (!qFuzzyCompare(m_vertices.at(iMatch), verts[iVertex])) continue; if (!qFuzzyCompare(m_normals.at(iMatch), norms[iVertex])) continue; if (!qFuzzyCompare(m_textureCoords.at(iMatch), texCoords[iVertex])) continue; // Then add the index only addIndex(iMatch); break; } // No match for this vertex, add to end of list if((int)iMatch == m_vertices.size()) { addIndex(iMatch); addVertex(verts[iVertex]); addNormal(norms[iVertex]); addTextureCoord(texCoords[iVertex]); } } } void QtGLComponent::setColor(const QColor& color) { m_color = color; } void QtGLComponent::draw(GLenum drawMode) { if (m_shader == NULL) return; switch (m_shader->getType()) { case QTGLSHADER_FLAT: m_shader->load(m_vertices.constData(), m_color); break; case QTGLSHADER_TEXTURE: m_shader->load(m_vertices.constData(), m_textureCoords.constData()); break; case QTGLSHADER_ADS: m_shader->load(m_vertices.constData(), m_normals.constData(), m_material); break; case QTGLSHADER_ADSTEXTURE: m_shader->load(m_vertices.constData(), m_normals.constData(), m_textureCoords.constData(), m_material); break; default: qDebug() << "Invalidate shader type " << m_shader->getType(); return; } if (m_texture != -1) glBindTexture(GL_TEXTURE_2D, m_texture); if (m_useIndexBuffer) m_indexBuffer->bind(); if (m_useNormalBuffer) m_normalBuffer->bind(); switch (drawMode) { case GL_TRIANGLES: glDrawElements(GL_TRIANGLES, m_indices.size(), GL_UNSIGNED_SHORT, 0); break; case GL_TRIANGLE_FAN: glDrawArrays(GL_TRIANGLE_FAN, 0, m_vertices.size()); break; case GL_LINES: glDrawArrays(GL_LINES, 0, m_vertices.size()); break; default: qDebug() << "Invalid draw mode " << drawMode; break; } m_shader->release(); if (m_useIndexBuffer) m_indexBuffer->release(); if (m_useNormalBuffer) m_normalBuffer->release(); } void QtGLComponent::updateBoundingBox(const QVector3D& vert) { if (vert.x() < m_boundMinus.x()) m_boundMinus.setX(vert.x()); if (vert.y() < m_boundMinus.y()) m_boundMinus.setY(vert.y()); if (vert.z() < m_boundMinus.z()) m_boundMinus.setZ(vert.z()); if (vert.x() > m_boundPlus.x()) m_boundPlus.setX(vert.x()); if (vert.y() > m_boundPlus.y()) m_boundPlus.setY(vert.y()); if (vert.z() > m_boundPlus.z()) m_boundPlus.setZ(vert.z()); } rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGLComponent.h000066400000000000000000000120111254201074400224000ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // Based on code from the OpenGL SuperBible: /* * OpenGL SuperBible * Copyright (c) 2007-2009, Richard S. Wright Jr. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Richard S. Wright Jr. nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef QTGLCOMPONENT_H #define QTGLCOMPONENT_H class QtGLComponent { public: QtGLComponent(); ~QtGLComponent(); virtual void setShader(QtGLShader *shader); virtual void setTexture(GLuint texture); virtual void setTexture(const QImage& image); virtual void setMaterial(const COMPONENT_MATERIAL& material); virtual void setMaterial(const QVector3D& ambient, const QVector3D& diffuse, const QVector3D& specular, float shininess); virtual void draw(GLenum drawMode); virtual void setColor(const QColor& color); // sets color for flat shader inline QVector3D& getBoundMinus() { return m_boundMinus; }; inline QVector3D& getBoundPlus() { return m_boundPlus; }; protected: void reset(); // clears everything and starts again int nearestPOT(int val); // returns nearest power of 2 void addTriangle(QVector3D *verts, QVector3D *norms, QVector2D *texCoords); // adds a triangle void addVertex(const QVector3D& vertex); // adds a vertex to the component void addNormal(const QVector3D& normal); // adds a normal to the component void addTextureCoord(const QVector2D& textureCoord); // adds a texture coord to the component void addIndex(GLushort index); // adds an index to the component void useIndexBuffer(bool use); // says that index buffer is to be used in draw void useNormalBuffer(bool use); // says that normal buffer is to be used in draw void updateBoundingBox(const QVector3D& vert); // update the component bounding box private: QtGLShader *m_shader; int m_texture; QColor m_color; COMPONENT_MATERIAL m_material; QVector m_vertices; QVector m_normals; QVector m_textureCoords; QVector m_indices; QGLBuffer *m_indexBuffer; QGLBuffer *m_normalBuffer; bool m_useIndexBuffer; bool m_useNormalBuffer; QVector3D m_boundMinus; // lowest in each coord for box QVector3D m_boundPlus; // highest in each coord for box }; #endif // QTGLCOMPONENT_H rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGLCylinderComponent.cpp000066400000000000000000000175531254201074400244450ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // Based on code from the OpenGL SuperBible: /* * OpenGL SuperBible * Copyright (c) 2007-2009, Richard S. Wright Jr. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Richard S. Wright Jr. nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "QtGL.h" QtGLCylinderComponent::QtGLCylinderComponent() { } QtGLCylinderComponent::~QtGLCylinderComponent() { } void QtGLCylinderComponent::generate(GLfloat baseRadius, GLfloat topRadius, GLfloat length, GLint numSlices, GLint numStacks) { float radiusStep = (topRadius - baseRadius) / float(numStacks); GLfloat stepSizeSlice = (3.1415926536f * 2.0f) / float(numSlices); QVector3D vertex[4]; QVector3D normal[4]; QVector2D texture[4]; reset(); GLfloat ds = 1.0f / float(numSlices); GLfloat dt = 1.0f / float(numStacks); GLfloat s; GLfloat t; int vertA = 0; int vertB = 1; int vertC = 2; int vertD = 3; for (int i = 0; i < numStacks; i++) { if(i == 0) t = 1.0f; else t = 1.0f - float(i) * dt; float tNext; if(i == (numStacks - 1)) tNext = 0.0f; else tNext = 1.0f - float(i+1) * dt; float currentRadius = baseRadius + (radiusStep * float(i)); float nextRadius = baseRadius + (radiusStep * float(i+1)); float theta; float thetaNext; float currentZ = float(i) * (length / float(numStacks)); float nextZ = float(i+1) * (length / float(numStacks)); float zNormal = 0.0f; if(!qFuzzyCompare(baseRadius - topRadius, 0.0f)) { // Rise over run... zNormal = (baseRadius - topRadius); } for (int j = 0; j < numSlices; j++) { if(j == 0) s = 1.0f; else s = 1.0f - float(j) * ds; float sNext; if(j == (numSlices -1)) sNext = 0.0f; else sNext = 1.0f - float(j+1) * ds; theta = stepSizeSlice * float(j); if(j == (numSlices - 1)) thetaNext = 0.0f; else thetaNext = stepSizeSlice * (float(j+1)); // Inner First vertex[vertB].setX(cos(theta) * currentRadius); // X vertex[vertB].setY(sin(theta) * currentRadius); // Y vertex[vertB].setZ(currentZ); // Z normal[vertB].setX(vertex[vertB].x()); // Surface Normal, same for everybody normal[vertB].setY(vertex[vertB].y()); normal[vertB].setZ(zNormal); normal[vertB].normalize(); texture[vertB].setX(s); // Texture Coordinates, I have no idea... texture[vertB].setY(t); // Outer First vertex[vertA].setX(cos(theta) * nextRadius); // X vertex[vertA].setY(sin(theta) * nextRadius); // Y vertex[vertA].setZ(nextZ); // Z if(!qFuzzyCompare(nextRadius, 0.0f)) { normal[vertA].setX(vertex[vertA].x()); // Surface Normal, same for everybody normal[vertA].setY(vertex[vertA].y()); // For cones, tip is tricky normal[vertA].setZ(zNormal); normal[vertA].normalize(); } else { normal[vertA] = normal[vertB]; } texture[vertA].setX(s); // Texture Coordinates, I have no idea... texture[vertA].setY(tNext); // Inner second vertex[vertD].setX(cos(thetaNext) * currentRadius); // X vertex[vertD].setY(sin(thetaNext) * currentRadius); // Y vertex[vertD].setZ(currentZ); // Z normal[vertD].setX(vertex[vertD].x()); // Surface Normal, same for everybody normal[vertD].setY(vertex[vertD].y()); normal[vertD].setZ(zNormal); normal[vertD].normalize(); texture[vertD].setX(sNext); // Texture Coordinates, I have no idea... texture[vertD].setY(t); // Outer second vertex[vertC].setX(cos(thetaNext) * nextRadius); // X vertex[vertC].setY(sin(thetaNext) * nextRadius); // Y vertex[vertC].setZ(nextZ); // Z if(!qFuzzyCompare(nextRadius, 0.0f)) { normal[vertC].setX(vertex[vertC].x()); // Surface Normal, same for everybody normal[vertC].setY(vertex[vertC].y()); normal[vertC].setZ(zNormal); normal[vertC].normalize(); } else { normal[vertC] = normal[vertD]; } texture[vertC].setX(sNext); // Texture Coordinates, I have no idea... texture[vertC].setY(tNext); addTriangle(vertex, normal, texture); // Rearrange for next triangle vertex[vertA] = vertex[vertB]; normal[vertA] = normal[vertB]; texture[vertA] = texture[vertB]; vertex[vertB] = vertex[vertD]; normal[vertB] = normal[vertD]; texture[vertB] = texture[vertD]; addTriangle(vertex, normal, texture); } } useIndexBuffer(true); useNormalBuffer(true); } void QtGLCylinderComponent::draw() { QtGLComponent::draw(GL_TRIANGLES); } rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGLCylinderComponent.h000066400000000000000000000061241254201074400241020ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // Based on code from the OpenGL SuperBible: /* * OpenGL SuperBible * Copyright (c) 2007-2009, Richard S. Wright Jr. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Richard S. Wright Jr. nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef QTGLCYLINDERCOMPONENT #define QTGLCYLINDERCOMPONENT class QtGLCylinderComponent : public QtGLComponent { public: QtGLCylinderComponent(); ~QtGLCylinderComponent(); void generate(GLfloat baseRadius, GLfloat topRadius, GLfloat length, GLint numSlices, GLint numStacks); void draw(); }; #endif // QTGLCYLINDERCOMPONENT_H rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGLDiskComponent.cpp000066400000000000000000000147471254201074400235700ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // Based on code from the OpenGL SuperBible: /* * OpenGL SuperBible * Copyright (c) 2007-2009, Richard S. Wright Jr. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Richard S. Wright Jr. nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "QtGL.h" QtGLDiskComponent::QtGLDiskComponent() { } QtGLDiskComponent::~QtGLDiskComponent() { } void QtGLDiskComponent::generate(GLfloat innerRadius, GLfloat outerRadius, GLint numSlices, GLint numStacks) { // How much to step out each stack GLfloat stepSizeRadial = outerRadius - innerRadius; if(stepSizeRadial < 0.0f) // Dum dum... stepSizeRadial *= -1.0f; stepSizeRadial /= float(numStacks); GLfloat stepSizeSlice = (3.1415926536f * 2.0f) / float(numSlices); reset(); QVector3D vertex[4]; QVector3D normal[4]; QVector2D texture[4]; float radialScale = 1.0f / outerRadius; for(GLint i = 0; i < numStacks; i++) { float theta; float thetaNext; for (GLint j = 0; j < numSlices; j++) { float inner = innerRadius + (float(i)) * stepSizeRadial; float outer = innerRadius + (float(i+1)) * stepSizeRadial; theta = stepSizeSlice * float(j); if(j == (numSlices - 1)) thetaNext = 0.0f; else thetaNext = stepSizeSlice * (float(j+1)); // Inner First vertex[0].setX(cos(theta) * inner); // X vertex[0].setY(sin(theta) * inner); // Y vertex[0].setZ(0.0f); // Z normal[0].setX(0.0f); // Surface Normal, same for everybody normal[0].setY(0.0f); normal[0].setZ(1.0f); texture[0].setX(((vertex[0].x() * radialScale) + 1.0f) * 0.5f); texture[0].setY(((vertex[0].y() * radialScale) + 1.0f) * 0.5f); // Outer First vertex[1].setX(cos(theta) * outer); // X vertex[1].setY(sin(theta) * outer); // Y vertex[1].setZ(0.0f); // Z normal[1].setX(0.0f); // Surface Normal, same for everybody normal[1].setY(0.0f); normal[1].setZ(1.0f); texture[1].setX(((vertex[1].x() * radialScale) + 1.0f) * 0.5f); texture[1].setY(((vertex[1].y() * radialScale) + 1.0f) * 0.5f); // Inner Second vertex[2].setX(cos(thetaNext) * inner); // X vertex[2].setY(sin(thetaNext) * inner); // Y vertex[2].setZ(0.0f); // Z normal[2].setX(0.0f); // Surface Normal, same for everybody normal[2].setY(0.0f); normal[2].setZ(1.0f); texture[2].setX(((vertex[2].x() * radialScale) + 1.0f) * 0.5f); texture[2].setY(((vertex[2].y() * radialScale) + 1.0f) * 0.5f); // Outer Second vertex[3].setX(cos(thetaNext) * outer); // X vertex[3].setY(sin(thetaNext) * outer); // Y vertex[3].setZ(0.0f); // Z normal[3].setX(0.0f); // Surface Normal, same for everybody normal[3].setY(0.0f); normal[3].setZ(1.0f); texture[3].setX(((vertex[3].x() * radialScale) + 1.0f) * 0.5f); texture[3].setY(((vertex[3].y() * radialScale) + 1.0f) * 0.5f); addTriangle(vertex, normal, texture); // Rearrange for next triangle vertex[0] = vertex[1]; normal[0] = normal[1]; texture[0] = texture[1]; vertex[1] = vertex[3]; normal[1] = normal[3]; texture[1] = texture[3]; addTriangle(vertex, normal, texture); } } useIndexBuffer(true); useNormalBuffer(true); } void QtGLDiskComponent::draw() { QtGLComponent::draw(GL_TRIANGLES); } rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGLDiskComponent.h000066400000000000000000000060571254201074400232300ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // Based on code from the OpenGL SuperBible: /* * OpenGL SuperBible * Copyright (c) 2007-2009, Richard S. Wright Jr. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Richard S. Wright Jr. nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef QTGLDISKCOMPONENT #define QTGLDISKCOMPONENT class QtGLDiskComponent : public QtGLComponent { public: QtGLDiskComponent(); ~QtGLDiskComponent(); void generate(GLfloat innerRadius, GLfloat outerRadius, GLint numSlices, GLint numStacks); void draw(); }; #endif // QTGLDISKCOMPONENT_H rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGLFlatShader.cpp000066400000000000000000000047111254201074400230160ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "QtGL.h" QtGLFlatShader::QtGLFlatShader(QObject *parent) : QtGLShader(parent) { #define PROGRAM_VERTEX_ATTRIBUTE 0 m_type = QTGLSHADER_FLAT; m_vshader = new QGLShader(QGLShader::Vertex, parent); const char *vsrc = "attribute highp vec4 vertex;\n" "uniform mediump mat4 matrix;\n" "void main(void)\n" "{\n" " gl_Position = matrix * vertex;\n" "}\n"; m_vshader->compileSourceCode(vsrc); m_fshader = new QGLShader(QGLShader::Fragment, parent); const char *fsrc = "uniform mediump vec4 color;\n" "void main(void)\n" "{\n" " gl_FragColor = color;\n" "}\n"; m_fshader->compileSourceCode(fsrc); addShader(m_vshader); addShader(m_fshader); bindAttributeLocation("vertex", PROGRAM_VERTEX_ATTRIBUTE); link(); setUniformValue("color", QVector4D(1.0f, 0, 0, 1.0f)); } QtGLFlatShader::~QtGLFlatShader() { } void QtGLFlatShader::load(const QVector3D *vertices, const QColor& color) { bind(); setUniformValue("matrix", globalTransforms.modelViewProjectionMatrix); setUniformValue("color", color); enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, vertices); } rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGLFlatShader.h000066400000000000000000000027461254201074400224710ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef QTGLFLATSHADER_H #define QTGLFLATSHADER_H class QtGLFlatShader : public QtGLShader { public: QtGLFlatShader(QObject *parent); ~QtGLFlatShader(); void load(const QVector3D *vertices, const QColor& color); private: }; #endif // QTGLFLATSHADER_H rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGLLib.pri000066400000000000000000000036731254201074400215250ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014, richards-tech #// #// Permission 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. INCLUDEPATH += $$PWD DEPENDPATH += $$PWD HEADERS += $$PWD/QtGL.h \ $$PWD/QtGLShader.h \ $$PWD/QtGLTextureShader.h \ $$PWD/QtGLFlatShader.h \ $$PWD/QtGLADSShader.h \ $$PWD/QtGLADSTextureShader.h \ $$PWD/QtGLComponent.h \ $$PWD/QtGLPlaneComponent.h \ $$PWD/QtGLCylinderComponent.h \ $$PWD/QtGLWireCubeComponent.h \ $$PWD/QtGLDiskComponent.h SOURCES += $$PWD/QtGL.cpp \ $$PWD/QtGLTextureShader.cpp \ $$PWD/QtGLFlatShader.cpp \ $$PWD/QtGLADSShader.cpp \ $$PWD/QtGLADSTextureShader.cpp \ $$PWD/QtGLComponent.cpp \ $$PWD/QtGLPlaneComponent.cpp \ $$PWD/QtGLCylinderComponent.cpp \ $$PWD/QtGLWireCubeComponent.cpp \ $$PWD/QtGLDiskComponent.cpp rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGLPlaneComponent.cpp000066400000000000000000000034521254201074400237240ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "QtGL.h" QtGLPlaneComponent::QtGLPlaneComponent() { } QtGLPlaneComponent::~QtGLPlaneComponent() { } void QtGLPlaneComponent::generate(float width, float height) { static const float coords[4][2] = {{+0.5f, +0.5f}, {-0.5f, +0.5f}, {-0.5f, -0.5f}, {+0.5f, -0.5f}}; reset(); for (int vert = 0; vert < 4; vert++) { addTextureCoord(QVector2D(vert == 0 || vert == 3, vert == 0 || vert == 1)); addVertex(QVector3D(width * coords[vert][0], height * coords[vert][1], 0)); } } void QtGLPlaneComponent::draw() { QtGLComponent::draw(GL_TRIANGLE_FAN); } rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGLPlaneComponent.h000066400000000000000000000032031254201074400233630ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // // This file contains OpenGL and Qt includes. This file should be included first // and the order of includes is very important (especially on Linux). #ifndef QTGLPLANECOMPONENT_H #define QTGLPLANECOMPONENT_H class QtGLPlaneComponent : public QtGLComponent { public: QtGLPlaneComponent(); ~QtGLPlaneComponent(); void generate(float width, float height); void draw(); }; #endif // QTGLPLANECOMPONENT_H rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGLShader.h000066400000000000000000000053441254201074400216570ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // This file contains OpenGL and Qt includes. This file should be included first // and the order of includes is very important (especially on Linux). #ifndef QTGLSHADER_H #define QTGLSHADER_H typedef enum { QTGLSHADER_FLAT, QTGLSHADER_TEXTURE, QTGLSHADER_ADS, QTGLSHADER_ADSTEXTURE, QTGLSHADER_COUNT } QTGLSHADER_TYPE; class QtGLShader : public QGLShaderProgram { public: QtGLShader(QObject *parent) : QGLShaderProgram(parent) {}; virtual ~QtGLShader() { removeAllShaders(); }; inline QTGLSHADER_TYPE getType() {return m_type;}; // virtual void load(const QVector3D *vertices, const QVector2D *textureCoords); virtual void load(const QVector3D *, const QVector2D *) { qDebug() << "No shader load";}; // virtual void load(const QVector3D *vertices, const QColor& color); virtual void load(const QVector3D *, const QColor&) {qDebug() << "No shader load";}; // virtual void load(const QVector3D *vertices, const QVector3D *normals, const COMPONENT_MATERIAL& material); virtual void load(const QVector3D *, const QVector3D *, const COMPONENT_MATERIAL& ) {qDebug() << "No shader load";}; // virtual void load(const QVector3D *vertices, const QVector3D *normals, // const QVector2D *textureCoords, const COMPONENT_MATERIAL& material); virtual void load(const QVector3D *, const QVector3D *, const QVector2D*, const COMPONENT_MATERIAL& ) {qDebug() << "No shader load";}; protected: QGLShader *m_vshader; QGLShader *m_fshader; QTGLSHADER_TYPE m_type; }; #endif // QTGLSHADER_H rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGLSphereComponent.cpp000066400000000000000000000145301254201074400241120ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // Based on code from the OpenGL SuperBible: /* * OpenGL SuperBible * Copyright (c) 2007-2009, Richard S. Wright Jr. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Richard S. Wright Jr. nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "QtGL.h" QtGLSphereComponent::QtGLSphereComponent() { } QtGLSphereComponent::~QtGLSphereComponent() { } void QtGLSphereComponent::generate(GLfloat radius, GLint numSlices, GLint numStacks) { GLfloat drho = (GLfloat)(3.141592653589) / (GLfloat) numStacks; GLfloat dtheta = 2.0f * (GLfloat)(3.141592653589) / (GLfloat) numSlices; GLfloat ds = 1.0f / (GLfloat) numSlices; GLfloat dt = 1.0f / (GLfloat) numStacks; GLfloat t = 1.0f; GLfloat s = 0.0f; GLint i, j; // Looping variables GLfloat stepSizeSlice = (3.1415926536f * 2.0f) / float(numSlices); QVector3D vertex[4]; QVector3D normal[4]; QVector2D texture[4]; reset(); int vertA = 0; int vertB = 1; int vertC = 2; int vertD = 3; for (i = 0; i < numStacks; i++) { GLfloat rho = (GLfloat)i * drho; GLfloat srho = (GLfloat)(sin(rho)); GLfloat crho = (GLfloat)(cos(rho)); GLfloat srhodrho = (GLfloat)(sin(rho + drho)); GLfloat crhodrho = (GLfloat)(cos(rho + drho)); // Many sources of OpenGL sphere drawing code uses a triangle fan // for the caps of the sphere. This however introduces texturing // artifacts at the poles on some OpenGL implementations s = 0.0f; for ( j = 0; j < numSlices; j++) { GLfloat theta = (j == numSlices) ? 0.0f : j * dtheta; GLfloat stheta = (GLfloat)(-sin(theta)); GLfloat ctheta = (GLfloat)(cos(theta)); GLfloat x = stheta * srho; GLfloat y = ctheta * srho; GLfloat z = crho; texture[vertA].setX(s); texture[vertA].setY(t); normal[vertA].setX(x); normal[vertA].setY(y); normal[vertA].setZ(z); vertex[vertA].setX(x * radius); vertex[vertA].setY(y * radius); vertex[vertA].setZ(z * radius); x = stheta * srhodrho; y = ctheta * srhodrho; z = crhodrho; texture[vertB].setX(s); texture[vertB].setY(t - dt); normal[vertB].setX(x); normal[vertB].setY(y); normal[vertB].setZ(z); vertex[vertB].setX(x * radius); vertex[vertB].setY(y * radius); vertex[vertB].setZ(z * radius); theta = ((j+1) == numSlices) ? 0.0f : (j+1) * dtheta; stheta = (GLfloat)(-sin(theta)); ctheta = (GLfloat)(cos(theta)); x = stheta * srho; y = ctheta * srho; z = crho; s += ds; texture[vertC].setX(s); texture[vertC].setY(t); normal[vertC].setX(x); normal[vertC].setY(y); normal[vertC].setZ(z); vertex[vertC].setX(x * radius); vertex[vertC].setY(y * radius); vertex[vertC].setZ(z * radius); x = stheta * srhodrho; y = ctheta * srhodrho; z = crhodrho; texture[vertD].setX(s); texture[vertD].setY(t - dt); normal[vertD].setX(x); normal[vertD].setY(y); normal[vertD].setZ(z); vertex[vertD].setX(x * radius); vertex[vertD].setY(y * radius); vertex[vertD].setZ(z * radius); addTriangle(vertex, normal, texture); // Rearrange for next triangle vertex[vertA] = vertex[vertB]; normal[vertA] = normal[vertB]; texture[vertA] = texture[vertB]; vertex[vertB] = vertex[vertD]; normal[vertB] = normal[vertD]; texture[vertB] = texture[vertD]; addTriangle(vertex, normal, texture); } t -= dt; } useIndexBuffer(true); useNormalBuffer(true); } void QtGLSphereComponent::draw() { QtGLComponent::draw(GL_TRIANGLES); } rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGLSphereComponent.h000066400000000000000000000060511254201074400235560ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // Based on code from the OpenGL SuperBible: /* * OpenGL SuperBible * Copyright (c) 2007-2009, Richard S. Wright Jr. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Richard S. Wright Jr. nor the names of other contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef QTGLSPHERECOMPONENT #define QTGLSPHERECOMPONENT class QtGLSphereComponent : public QtGLComponent { public: QtGLSphereComponent(); ~QtGLSphereComponent(); void generate(GLfloat radius, GLint numSlices, GLint numStacks); void draw(); }; #endif // QTGLSPHERECOMPONENT_H rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGLTextureShader.cpp000066400000000000000000000055241254201074400235730ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "QtGL.h" QtGLTextureShader::QtGLTextureShader(QObject *parent) : QtGLShader(parent) { #define PROGRAM_VERTEX_ATTRIBUTE 0 #define PROGRAM_TEXCOORD_ATTRIBUTE 1 m_type = QTGLSHADER_TEXTURE; m_vshader = new QGLShader(QGLShader::Vertex, parent); const char *vsrc = "attribute highp vec3 vertex;\n" "attribute mediump vec2 texCoord;\n" "varying mediump vec2 texc;\n" "uniform mediump mat4 matrix;\n" "void main(void)\n" "{\n" " gl_Position = matrix * vec4(vertex, 1.0);\n" " texc = texCoord;\n" "}\n"; m_vshader->compileSourceCode(vsrc); m_fshader = new QGLShader(QGLShader::Fragment, parent); const char *fsrc = "uniform sampler2D texture;\n" "varying mediump vec2 texc;\n" "void main(void)\n" "{\n" " gl_FragColor = texture2D(texture, texc.st);\n" "}\n"; m_fshader->compileSourceCode(fsrc); addShader(m_vshader); addShader(m_fshader); bindAttributeLocation("vertex", PROGRAM_VERTEX_ATTRIBUTE); bindAttributeLocation("texCoord", PROGRAM_TEXCOORD_ATTRIBUTE); link(); setUniformValue("texture", 0); } QtGLTextureShader::~QtGLTextureShader() { } void QtGLTextureShader::load(const QVector3D *vertices, const QVector2D *textureCoords) { bind(); setUniformValue("matrix", globalTransforms.modelViewProjectionMatrix); enableAttributeArray(PROGRAM_VERTEX_ATTRIBUTE); enableAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE); setAttributeArray(PROGRAM_VERTEX_ATTRIBUTE, vertices); setAttributeArray(PROGRAM_TEXCOORD_ATTRIBUTE, textureCoords); } rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGLTextureShader.h000066400000000000000000000030031254201074400232260ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef QTGLTEXTURESHADER_H #define QTGLTEXTURESHADER_H class QtGLTextureShader : public QtGLShader { public: QtGLTextureShader(QObject *parent); ~QtGLTextureShader(); void load(const QVector3D *vertices, const QVector2D *textureCoords); private: }; #endif // QTGLTEXTURESHADER_H rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGLWireCubeComponent.cpp000066400000000000000000000050241254201074400243670ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "QtGL.h" QtGLWireCubeComponent::QtGLWireCubeComponent() { } QtGLWireCubeComponent::~QtGLWireCubeComponent() { } void QtGLWireCubeComponent::generate(float width, float height, float depth) { float w2 = width / 2.0f; float h2 = height / 2.0f; float d2 = depth / 2.0f; reset(); addVertex(QVector3D(-w2, h2, d2)); addVertex(QVector3D(w2, h2, d2)); addVertex(QVector3D(-w2, -h2, d2)); addVertex(QVector3D(w2, -h2, d2)); addVertex(QVector3D(w2, -h2, d2)); addVertex(QVector3D(w2, h2, d2)); addVertex(QVector3D(-w2, -h2, d2)); addVertex(QVector3D(-w2, h2, d2)); addVertex(QVector3D(-w2, h2, -d2)); addVertex(QVector3D(w2, h2, -d2)); addVertex(QVector3D(-w2, -h2, -d2)); addVertex(QVector3D(w2, -h2, -d2)); addVertex(QVector3D(w2, -h2, -d2)); addVertex(QVector3D(w2, h2, -d2)); addVertex(QVector3D(-w2, -h2, -d2)); addVertex(QVector3D(-w2, h2, -d2)); addVertex(QVector3D(-w2, -h2, -d2)); addVertex(QVector3D(-w2, -h2, d2)); addVertex(QVector3D(w2, -h2, -d2)); addVertex(QVector3D(w2, -h2, d2)); addVertex(QVector3D(-w2, h2, -d2)); addVertex(QVector3D(-w2, h2, d2)); addVertex(QVector3D(w2, h2, -d2)); addVertex(QVector3D(w2, h2, d2)); } void QtGLWireCubeComponent::draw() { QtGLComponent::draw(GL_LINES); } rtimulib-7.2.1/RTHost/RTIMULibGL/QtGLLib/QtGLWireCubeComponent.h000066400000000000000000000032371254201074400240400ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. // This file contains OpenGL and Qt includes. This file should be included first // and the order of includes is very important (especially on Linux). #ifndef QTGLWIRECUBECOMPONENT_H #define QTGLWIRECUBECOMPONENT_H class QtGLWireCubeComponent : public QtGLComponent { public: QtGLWireCubeComponent(); ~QtGLWireCubeComponent(); void generate(float width, float height, float depth); void draw(); }; #endif // QTGLWIRECUBECOMPONENT_H rtimulib-7.2.1/RTHost/RTIMULibGL/RTIMULibGL.pri000066400000000000000000000026301254201074400205730ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014, richards-tech #// #// Permission 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. INCLUDEPATH += $$PWD DEPENDPATH += $$PWD include(QtGLLib/QtGLLib.pri) include(VRWidgetLib/VRWidgetLib.pri) HEADERS += $$PWD/IMUView.h SOURCES += $$PWD/IMUView.cpp rtimulib-7.2.1/RTHost/RTIMULibGL/VRWidgetLib/000077500000000000000000000000001254201074400204265ustar00rootroot00000000000000rtimulib-7.2.1/RTHost/RTIMULibGL/VRWidgetLib/Images/000077500000000000000000000000001254201074400216335ustar00rootroot00000000000000rtimulib-7.2.1/RTHost/RTIMULibGL/VRWidgetLib/Images/BlueShade.png000066400000000000000000000042111254201074400241730ustar00rootroot00000000000000PNG  IHDR?1 pHYsnu> IDATx=r=aA`b ۠  @AU@* 2/h㙞V+'{~θΕn z0|] !+0,VѵZOB6zcZW­[;uNj^7oq_Vk`VkkjuMi`XMϭ joMC''!`@3V+٦]cЎco Z٧O7-~cނߖ8w|pqpŽO5 Po ƴ~cQ7~cF\O4qHg7k18q Q q]7_;;~ݾ6~c:uxh~cztxh>> ڿ>/_=} nGGv>sA(իe ?>O7M 1C)HH'@;>s`?zDOEbٙ[`Oh 'OhQ=QC'@OO0 0 Oh6؂O1?3}l9`=,BkR /| 'e{q8ޯO _F\"O;/O5گOЯ[5FS{??OУؽgOН '{} z]U'G>'h_O'h>A8z}6~' D'h wE)}v~ A}'h[[qeg~ xUM'p[OW66D}Ohs ܠ-'3Oo>Ah F[OP/}J>ԈwC:'u'>A-bt&T!GGz\1}17}%l> :];>`wמ?SnI1'(}Bh?'(c}즷7}o{F >3'%7n38},h?'Hc2}h?bDI ڏxi~̢O\K~,O2~,O0G s~$Os<>A~$O#5}h?2'#}h?'؂#'}1lD>zEAQ>h? '`o$}s(NLl_g!O`퇄:퇔9Q~Aş&e2>JEVbQޏe{h<.G>p"ÃÏԏYnp$Ŷz8%z R< [óepnk=фYwhE}8 {?sk~4gM9-v;D&܈#k- /:ph ~E}4]\.}'Α:_h?8 ] h?Eѳ}~ۅ}`ϙhώm,}< _n-i]z jxz ~#zTXtCompanyxs,JO+QHK/=aIENDB`rtimulib-7.2.1/RTHost/RTIMULibGL/VRWidgetLib/Images/Compass.png000066400000000000000000000251611254201074400237530ustar00rootroot00000000000000PNG  IHDR  pHYsnu> IDATxw\G玣H({!#Q5FblFE-`Ĩ kW4(( `FGDAz{~;;<D_5.mڴYxqrr 8cƌy:tܜ[.66ƍ111G\KK-Z9nݺ.]lڴi^^^A/ӦM\4RF޽{111>>>lVf"""Ξ=BO9tPLLL\\\RRRDDD&MެY3g-Z8|JJJRRR\\oLsW^`ar֖*s-,,DRYk?)? ,Ƶ U333 ۹}<;884hРvڜ,BT~}N!>֬Yj*"cիw _rZj0cqvv޲eݻwccc[6i۶mnZbP ,,,._M6mJwtt?~ҥKNdɒݻ\BD"qtt|5{oϳӟIsrr Tm۶7ue6LJm RԬY? .]dddE{6kѢEl4kÆ 4=11Q" ^m4^TTFFFO?[QQg}&hz+++l2͛c٬krJraaa?w޷n²ԩ&&$$@ii)}W+PNƎ?rtt4hЊ+'^po߾`ggӠAN;J>"J]\\\˱\UϞ=RIlܸwKi߾NNN>>> KQ-nll̦dnݺyzz .5hРaÆMGƍoҤ𠒝f8pv=c ??=zhPDboo_\++l~m[ K,1O<atQIQF޽(۷/GСٳOi(gϞ=cƌ)SM4? `ܹAAA+VX|y@@_U!W^wDDDY=oݺu#@FFСCU\3rHdW\Ie2&ӧUVиqcBƶnz׮]zj +%%!Kx۶mеkWBHn`׮]#GӧExxxjj*YK.E#c+qUcǎl0`9r'_xѣO>]p*I&-믿޽{wѢE[vuu2e t&| 3gجÇ.XXXBڴi>|ѣGnӧԩ[-ZįG0|v& &tvwmcOq0c٧@ǚUO̼y)^^^OڵkYϞ=cӯ_Y>䓩S.\pΜ9ӧO(g3f5j6o޼ٳgÆ ZzիWZj*2<++ ϘPv=tvMGųΟ?iii?޷o&cPR9{l%.]Js;۷o&&&* _@JN$%%Am-[rdWGwa۷izjj* L/--U9;R~89}4MYp!{ʽ{ԪUkʕ/^ ;rCBBL?]vG~͛7֯__^=!ūI&P}Ν;O^|vgwgooo_XXh\CMӧ@ZGK?Hxܹsǎ#}[lI1'Oӧ8`kky^zBի/^,/\0gΜCjn2fǾC?Ξ=&NJ9kwTlذ!!244~Pp%KYC ٹs'/:yƍ9*'7oDvjsCョ'cD cmmhѢ_~8R)V`ᜳg͚5n8M~%jHxy\\\zz:غu٭_pa6ԩSP>~g> 4O:Wͷ7999`رc 0/b;88D墝*Qvm,h˖-t8vS@jjj``s/㈈Ǐ +ի/F+GUjժt ӃQʡC/},5LJ H xҥxyy +{wΝ;gϞY2 Y+*VZyf={AT{hѢɓ'kh~H$hxܸq̙3Qx999K,I2ݴi8qbݺuxzM839|#996&Կ(..>{#G6l7R^VVj*:tN:5m4P/vܹuV011pל3gJr O=uThh3N!u֥.7={TyK4}flYYYwXзo?{KC}i\H@ܹsi:{^Ҋdɒ=zM`\SDP=7Æ :m߾=IIIݾ)͚5އseggg++ڵkٹ;L4I;SA <!!!l޽{ihqJLL$pIMMe+V%׮]K1*D>sCO:  fff<9@6m!F:<=rss7 ׭[G R~p<8iذ!0J  8JuӦM߼y ? ZqtD?x̘11 s@7FΝ[2!D*^r5,͛7]v޽{H{){Gj׮liif8F5DBBO<a5433C=UjՊz 99ysuttVnzгG.HpqL'O~ ua oooO?G量ϙCCCnݪɻO!2͍fQ9X"|B:88K. 6oAJ34} **:=z4l۶ K ""Çsjii&*O>':6?3{;3x`hhȦܼys̙k׮ `5 !(<)J`515a?U)dff&~!Gec4d .RI&_~%d*V턓~8*'֭[ };pĦ#&~Y9u>}:J]QQ*'NY;vHOOf͚Қ Yc4) }gQ ߽{w,*enݺu)SX`ͱM6_}03=B{!Mt#?/א~ ?~\XJ'Niaak֬k=T8hݺ Mc"ׯ_m͚5 DaG84r)`аaCٖ/_)G{̝;wb#FTlٲsǎcݻ/zRiggg`zM~9Q^ܹy(K )..)PwqJPƎXTs'(~b``gϨ)wp܆)?cnE+4mOW^x֋ѬSRR"ɰ  t9Sl@+88/O<XVVV\\c`=[n 3gb*o߮ Ȝ9sT RGy| +( 8ORWhBpݚ={6'8 l+WBrp fF#YY/_ tȨR4aϞ=1j?R娡v94PhcDzoDE6ṋ~/\:#*;44hǝ6ldU4)%V[?ؚ9sf۷G/Rp8c9ضuJJ kJ]Ԅ`RJ@8Ңݺuk۶-!} Ҭ A;\-[:v9 4 C?c_vE/ \@YpPGIY=z tصkמ6mZZZG ۷V '7@bՊ%ݾ 9~焐gsӸqcظq]]]qǎAqBu1/^d{TMӦMƧ~Z1h/h7n8fw9 A/>"4Ϫ~TqQ"fzSa4+prK^~re1Aꘪ98b~O%III׶Cڢ>i!ڷoI???(a.X#l*WkRne?]Ci֬deeiZ$sJlzjj*f"77gӦMC"^:th„ 8JP**"7???99ʕ+#թS5jB͛7 SVG~J޽{nBwօWLj+گ6mڼ~?_>r @TTU׮]رct.X.uEp̮PwsD+ٳ޼ypDCmAg>l*윖_ZZIǡ'ycٳgf xQ:tSYo,(%%;gšBZZZtt4](SӂW1 طod:@BB•+Wl۶mJǥP\\{͛7SEZ˖-׬Y'_f 52rܹ+V2.WTi=}wfjjwݺujb!;uDC1cM_‚ q6k֬;vPsaA`Ȑ!aQtxwnܸqÆ {&dÇg8'ܧO>x@p(bh5P8>c"|RXXK4MaaaTTH͞111YYY/_υ*IѦMovvq0ǏDK4T5oޜ.TE100x9/EN:,'QX,,,޾}KJRד}1 q6ekklٲcm׮-V\lԣѵn@}#Wh۶-vuuuuvvfz[n=qoYf-{4씎.+,, .޻w={XkÄ """tS^=;+*Lo.:t(Aݾ}Çњ.aL8ҍ7*ꤢV:tԩtןCÆ 5j_t_&iw4|A&iSlllnz̙3Ǐ笱7o^\\\LLȑ#iȑ#RSSoݺU^0l,2 Rvz3gΜ>}СC4z"""ovZJJJTTCY 1115sLT*Eu#05C(_,\B4F,==\iR a#3n3&K)Tq}wNW͛7 iAX;:\z#1W@Sŋ)N.'N8qѣG8N'j֬u֘ӧOOiӦOII 0 F͚5q x*hnjjG@WVV}ر#fQ/#4^rss뎀nnnD,))?''ӧ4lmm@ZYaycN4s|||AA'O^zUy)jƝ;wW^M6MII tƌCbu@캋\\\J%gޞ(氿K+񣛉Tjdddcc @XX:888"WXl/$_ G*bݺu\ C?w[ ٔ?OT.K366611ѵpp5wfe8w:fEDǏ7^-gZ IDATU,uXXX,Z.SV8*?ա7?fwfxtňHґ LX]jF600aԫWqf\\ s/_?!$ ]ۅ ZZZJ$ pbBT^GR%z*;޽!CNQ qƝ;w,D1KDDZ8!w-::z?M֭/--UyyyW0aD")aˌh8_)**LWrt@rX1;B 2RIC2`) X*JRD+W|r*KǑΝ;t(###\.+j> c 0ꨩ) vM HApu2Kf͚a{d&&&r"*u*<?RԿw򟛛[\\,J7nL/褬L"} b```hhhddK?!dڵk׮Uy/K.]t hHVBHEpr俸v%A !r8LJJ͞ !&&& #N@ee*ٞ'77ruO`;J{? XQ5x`(͛7`7n#e2BP(DK>,?RsZR%JKKsssJu#""P0R)a444d7+  ;w=_B8{sbiܹs= .]:ydV6 BB\.pQqB%N>y_(8755ڵ+_v-''_.؃_:$W% ..J 8rN}@vù}L/((/|埽MztH&1"x"~% ʿ1-6L&300F(..Vw AhYMtP'J/ 2DuѾR:!+[Jqӧ3VN_'Z`g_3v1uq"?~ȬRhт?122[szeD]ZVd*U>|kTkԴ3˲qjFgNϪٶ:%%Ewa_zY>fKvaQ~Ig$&&rڵk|g4n߮EDh\{@ϊܹsII R=K9~5Q%납u+Јo|*;s~T3*_oooÇǏEzoCW[K,aUW8;`h#8V@Ю];?VZ*iڴ)wD;{hQM>Sֿ۲eˀׯ_?//e˖ӝE>;&k׮e1t zo#ׄp7o:hOmBV`˖-ZлJTED_w_ |Ɋ}^^Dr Ӡ D_/OcFFFVtBrJ]TCQ5 ֭;zhv/'***11~77)SBbbbWH}*c59*WhHBB¤I =g0!ӈ:rx;]%+%%~nnn}uwwp4ԣGEGGzo#?f0`AHy #//OGh"NWsG]#yjt|zoW@|/R޾(HG{k^@/Q`?h XZt;TAQRQEK>D_E玓&M4hÇ_zUjժ8Ǐ^ !/^8vBV8/ʿ(DQQE?tGX:!A ,UG(̈/nj(VZ+!o5#zTXtCompanyxs,JO+QHK/=aIENDB`rtimulib-7.2.1/RTHost/RTIMULibGL/VRWidgetLib/Images/CompassNeedle.png000066400000000000000000000005671254201074400250730ustar00rootroot00000000000000PNG  IHDR  pHYsnu>IDATxױ 0A-I܇=$:xyXv}߽J)0վzRj}8D@AD@AD@AD@AD@AD@AD@Ar#zTXtCompanyxs,JO+QHK/=aIENDB`rtimulib-7.2.1/RTHost/RTIMULibGL/VRWidgetLib/Images/GreenShade.png000066400000000000000000000113401254201074400243450ustar00rootroot00000000000000PNG  IHDR?1 pHYsnu>bIDATxwxUohQ@"*8p"**Cl a%8 Yƶ'{Զnm "v㴔 7s}| &II")F?Z~v5qP 7!ˈ87)񽍈z{ KᘽE( @\h.֏ w-ei:g5 lG X >ԋ^!Bi+s~PG:ڞ|5yYI08|$/_?yfՏ >Q{~ փ!Bs0HE^/ja=&|'؞<`h]raQ OiIQ @ D'ۓVh1aM:ԏ ҝal{r kڏi^CIq:WنoUSlOZbU ҕ5Ԙ$Oi"em& @ܣ+]ÄZfOj gpFj{zR?^CIyY+Y/HET&x tPsٞ֏ qtʭ&c]YXH|ʩoGBjNtۓXM<h.t[ ۓ_E6ُH.߰,bZ?8N+]mO:[4qyYC<4qUi{r=~8dN6?GX;k9lO~ɗ>ȃj/ @b$N 6?x[KXF7ۓ|h#Vgsɯ*߁[O;=Q?Dy_u.q: @3CG(L0s87|3r щN!B{8p+[DA': ˹'%H !Bb V8p+C &܃'nneN3F3mH!B/g uaٞtshRq\POzڞ<|{Xh8.Lؤo6˵HD:Dhb#Ä|ۓVwrGCrlI٘O'GVe{ҪDhbB.ۓ81Xv;{~4E:,e\h{r#2sX_E'7i,co6Տ t ,aG1KYz1۞Ħqhh 7KXbR8?z~4ٗUoz۞GhUox_@\hڼ?ԏ>8wUz x߼} ЬmOnf&P'@"kJ2~4ՈF߇>'Äwہ9,9jլՈF hTl.OԈFԿ-~4DӐp#76q',qh ! (LoԿHH i87sm=iKX$IL{ p';snnYeyy}˷%4߲꿅[Ф{sw哟hUd&Կym`3ws ` cĬ 0"D g H$@ "+X1Qs3 '+XGDHHo&?c4I&9HpsLUina_Bad~\4WH&94÷%+RQğU %׶W2̵ub@dSI5 PѥS2#HdRJPOd&K(@|XQfX?48_*8-븮=Ud|ʧWLpQfRS`;$v4C,6Uhqof*oX.,nG;ۓg4.j O#-rVD%\b[iɛJKnO{ۓo@G:SnX*o%~:$bg}5՗pwx'4c ЎvYlX*xI]5՗rwy74{ 48j1W 2.=i*:p+U؞Ħ|UE,2+['1^pVx_ V Tb'U?ٞ_b՟h|;#9r! 뿑Ubv! /rۓv7-bOL>@/dOX~@[.`\a{Ҫ T"Jۓv37~ưlWbPf>vObRUzOִ*=_~hM̿mO_Ⱦ<<7nyS<_jXdɝ, /TĂ7Ip+ZUR9A'ws S)Wbh5yl{RK>8rG.&oZۓ;9ie~{?Zr.s/TK\:rCz-LWR7n_Bꗺq2԰9QRg@KZVPaX f~' %-)0/q3ܤ̜Üb-hQF?dlf~8Ó.%<ΤY̚,/QE)fLf~ P-zG~PuZJF~8 9K(1s3]K8ǡ|<ߤJTĔ?i3w*K)UМp5W?QN z /t*SwӁ|h6`~qRlЌf daSI1@DSy@SP0A&WPQLdMiZH`~qzZ25DLDy?!&WRYD8߼LV_QUP3?c7|1,Ob2~q(qh76nl:3vnJMܣ^!\WRWB G0°U6u|RI = *{>N"zm۹~Ed_F~q>ФoRJ˙~4D&fрJ M )4U~XoY&+l`bxHmrLᅢ;f2St_C'\l3])sOQ2|g0cbs="Ӷ5f@cR8 iXL?oiIDATxMr;q^ cưPW*P n[ѽ~'Թ]&XbfZ6}x?mM kulׯݻ] X֩'5:O +"'ve?1+/nBts,\_]^?ڧO:.Pѱdߒ<1PK(uhU7ylݮWWAmn1FKo l~cLOjѠ674k1]5n1WAui1ng};T?so׶jw xP|8Z}# $Y@lۙ۩~L~c SJyM9~c }9? fk1E(Đ7=~c7m~c7xh1hI@}~o *s~cPxw:,'@}E k}`+17o1X 0g:lOg~cP \@.'Ao m1XRW&h18mi1xig&k1x~?U)o ,>BHJ׶{׻>dBjH?%c&E7>rO-' ?E=3 ?OmM|O}44}>9 ϠO&xGl'@M> P_E @m ^Q'ݲ>V 7O,Eo ]`iIpoLg=}F' }ߗ> p+'''W'>h fy oO`:E'pcs'H= }YpÐ @GO>Ap36}ȸO ,N4@ &>A@}hh+p7qpCApI >{3}hs~ !D'3'pk,p (j?['|'pǥO0:O04>h y>s'QѾ^ D`,2DOI`h2+}=>ퟛ>ퟞ> Ǽ@M: zxB+ڏ xEN)=pey>S'h}H!b>A+B}&x<~d'w>(OPG!}j~dq3>Aklʼn&@`ڏm +ˍ}5AQ>A1ڏz ~TOP6}\jGY }ek'X-\Bј>YA{ ]ܰ>K逎 K;ѝ> }K2BRҗPӽ6D/L1ūr%9 #zߏt|1ڏz%ڏ!uyڏQ ڏ5~ڏ|vڏ5{j#U1h@ @KwsVDDyGT7~ĵ]2GtF13_év~է?~xw>|@1:|0d]Wi?cNo,]^C%ަNYp}e #zTXtCompanyxs,JO+QHK/=aIENDB`rtimulib-7.2.1/RTHost/RTIMULibGL/VRWidgetLib/VRIMUWidget.cpp000066400000000000000000000125321254201074400232030ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "VRIMUWidget.h" // Do simpler shading on ARMs #if defined(__arm__) || defined(__ARMEL__) #define VRIMUWIDGET_SHADER QTGLSHADER_TEXTURE #else #define VRIMUWIDGET_SHADER QTGLSHADER_ADSTEXTURE #endif VRIMUWidget::VRIMUWidget(QObject *parent, VRWIDGET_TYPE widgetType) : VRWidget(parent, widgetType) { } VRIMUWidget::~VRIMUWidget() { } void VRIMUWidget::VRWidgetInit() { m_IMULength = m_width; m_IMURadius = m_IMULength / 10.0; m_IMUXCylinder.generate(m_IMURadius / 100.0, m_IMURadius, m_IMULength, 50, 1); m_IMUXCylinder.setShader(globalShader[VRIMUWIDGET_SHADER]); m_IMUXCylinder.setMaterial(QVector3D(1.0, 1.0, 1.0), QVector3D(0.8, 0.8, 0.8), QVector3D(1.0, 1.0, 1.0), 3.0); m_IMUXCylinder.setTexture(QPixmap(":/Images/RedShade.png").toImage()); m_IMUXTop.generate(0, m_IMURadius, 50, 1); m_IMUXTop.setShader(globalShader[VRIMUWIDGET_SHADER]); m_IMUXTop.setMaterial(QVector3D(1.0, 1.0, 1.0), QVector3D(0.8, 0.8, 0.8), QVector3D(1.0, 1.0, 1.0), 3.0); m_IMUXTop.setTexture(QPixmap(":/Images/RedShade.png").toImage()); m_IMUYCylinder.generate(m_IMURadius / 100.0, m_IMURadius, m_IMULength, 50, 1); m_IMUYCylinder.setShader(globalShader[VRIMUWIDGET_SHADER]); m_IMUYCylinder.setMaterial(QVector3D(1.0, 1.0, 1.0), QVector3D(0.8, 0.8, 0.8), QVector3D(1.0, 1.0, 1.0), 3.0); m_IMUYCylinder.setTexture(QPixmap(":/Images/GreenShade.png").toImage()); m_IMUYTop.generate(0, m_IMURadius, 50, 1); m_IMUYTop.setShader(globalShader[VRIMUWIDGET_SHADER]); m_IMUYTop.setMaterial(QVector3D(1.0, 1.0, 1.0), QVector3D(0.8, 0.8, 0.8), QVector3D(1.0, 1.0, 1.0), 3.0); m_IMUYTop.setTexture(QPixmap(":/Images/GreenShade.png").toImage()); m_IMUZCylinder.generate(m_IMURadius / 100.0, m_IMURadius, m_IMULength, 50, 1); m_IMUZCylinder.setShader(globalShader[VRIMUWIDGET_SHADER]); m_IMUZCylinder.setMaterial(QVector3D(1.0, 1.0, 1.0), QVector3D(0.8, 0.8, 0.8), QVector3D(1.0, 1.0, 1.0), 3.0); m_IMUZCylinder.setTexture(QPixmap(":/Images/BlueShade.png").toImage()); m_IMUZTop.generate(0, m_IMURadius, 50, 1); m_IMUZTop.setShader(globalShader[VRIMUWIDGET_SHADER]); m_IMUZTop.setMaterial(QVector3D(1.0, 1.0, 1.0), QVector3D(0.8, 0.8, 0.8), QVector3D(1.0, 1.0, 1.0), 3.0); m_IMUZTop.setTexture(QPixmap(":/Images/BlueShade.png").toImage()); m_cube.generate(m_IMULength / 2.0, m_IMULength / 2.0, m_IMULength / 2.0); m_cube.setShader(globalShader[QTGLSHADER_FLAT]); m_cube.setMaterial(QVector3D(1.0, 1.0, 1.0), QVector3D(0.8, 0.8, 0.8), QVector3D(1.0, 1.0, 1.0), 6.0); m_cube.setColor(QColor(255, 255, 0)); setRotationOrder(VRWidgetRotationXZY); // so that roll/pitch/yaw works } void VRIMUWidget::VRWidgetRender() { startWidgetRender(); // do cube startComponentRender(0, 0, 0, 0, 0, 0); m_cube.draw(); endComponentRender(m_IMUXCylinder.getBoundMinus(), m_IMUXCylinder.getBoundPlus()); // do X IMU cylinder startComponentRender(0, 0, 0, 0, 90, 0); m_IMUXCylinder.draw(); endComponentRender(m_IMUXCylinder.getBoundMinus(), m_IMUXCylinder.getBoundPlus()); // do X IMU top startComponentRender(m_IMULength, 0, 0, 0, 90, 0); m_IMUXTop.draw(); endComponentRender(m_IMUXTop.getBoundMinus(), m_IMUXTop.getBoundPlus()); // do Y IMU cylinder startComponentRender(0, 0, 0, 0, 0, 0); m_IMUYCylinder.draw(); endComponentRender(m_IMUYCylinder.getBoundMinus(), m_IMUYCylinder.getBoundPlus()); // do Y IMU top startComponentRender(0, 0, m_IMULength, 0, 0, -90); m_IMUYTop.draw(); endComponentRender(m_IMUYTop.getBoundMinus(), m_IMUYTop.getBoundPlus()); // do Z IMU cylinder startComponentRender(0, 0, 0, 90, 0, 0); m_IMUZCylinder.draw(); endComponentRender(m_IMUZCylinder.getBoundMinus(), m_IMUZCylinder.getBoundPlus()); // do Z IMU top startComponentRender(0, -m_IMULength, 0, 90, 0, 0); m_IMUZTop.draw(); endComponentRender(m_IMUZTop.getBoundMinus(), m_IMUZTop.getBoundPlus()); endWidgetRender(); } rtimulib-7.2.1/RTHost/RTIMULibGL/VRWidgetLib/VRIMUWidget.h000066400000000000000000000035141254201074400226500ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef VRIMUWIDGET_H #define VRIMUWIDGET_H #include "VRWidget.h" class VRIMUWidget : public VRWidget { Q_OBJECT public: VRIMUWidget(QObject *parent, VRWIDGET_TYPE = VRWIDGET_IMU); ~VRIMUWidget(); virtual void VRWidgetInit(); virtual void VRWidgetRender(); private: QtGLCylinderComponent m_IMUXCylinder; QtGLCylinderComponent m_IMUYCylinder; QtGLCylinderComponent m_IMUZCylinder; QtGLDiskComponent m_IMUXTop; QtGLDiskComponent m_IMUYTop; QtGLDiskComponent m_IMUZTop; QtGLWireCubeComponent m_cube; qreal m_IMURadius; qreal m_IMULength; }; #endif // VRIMUWIDGET_H rtimulib-7.2.1/RTHost/RTIMULibGL/VRWidgetLib/VRWidget.cpp000066400000000000000000000607541254201074400226410ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #include "VRWidget.h" #include VRWidget::VRWidget(QObject *parent, VRWIDGET_TYPE widgetType) : QObject(parent) { m_widgetType = widgetType; m_selected = false; m_center = QVector3D(VRWIDGET_DEFAULT_X, VRWIDGET_DEFAULT_Y, VRWIDGET_DEFAULT_Z / globalTransforms.tanViewportFOV); m_rotation = QVector3D(0, 0, 0); m_width = VRWIDGET_DEFAULT_WIDTH; m_height = VRWIDGET_DEFAULT_HEIGHT; m_depth = VRWIDGET_DEFAULT_DEPTH; setModelMatrix(); m_icon = "IconDefault.png"; m_enableMoveX = m_enableMoveY = m_enableMoveZ = true; m_enableRotX = m_enableRotY = m_enableRotZ = true; m_drawSelectBox = true; m_rotationOrder = VRWidgetRotationZYX; } VRWidget::~VRWidget() { } void VRWidget::startWidgetRender() { pushModelMatrix(); globalTransforms.modelViewMatrix = m_modelMatrix * globalTransforms.modelViewMatrix; m_boundMinus = QVector3D(10000.0f, 10000.0f, 10000.0f); m_boundPlus = QVector3D(-10000.0f, -10000.0f, -10000.0f); m_offsetMatrix.setToIdentity(); } void VRWidget::startCompositeRender(const QVector3D& posOffset, const QVector3D& angleOffset) { startComponentRender(posOffset.x(), posOffset.y(), posOffset.z(), angleOffset.x(), angleOffset.y(), angleOffset.z()); } void VRWidget::startCompositeRender(float offsetX, float offsetY, float offsetZ, float rotOffsetX, float rotOffsetY, float rotOffsetZ) { startComponentRender(offsetX, offsetY, offsetZ, rotOffsetX, rotOffsetY, rotOffsetZ); } void VRWidget::startComponentRender(const QVector3D& posOffset, const QVector3D& angleOffset) { startComponentRender(posOffset.x(), posOffset.y(), posOffset.z(), angleOffset.x(), angleOffset.y(), angleOffset.z()); } void VRWidget::startComponentRender(float offsetX, float offsetY, float offsetZ, float rotOffsetX, float rotOffsetY, float rotOffsetZ) { pushModelMatrix(); pushOffsetMatrix(); globalTransforms.modelViewMatrix.translate(offsetX, offsetY, offsetZ); m_offsetMatrix.translate(offsetX, offsetY, offsetZ); switch (m_rotationOrder) { case VRWidgetRotationXYZ: globalTransforms.modelViewMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); m_offsetMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); break; case VRWidgetRotationXZY: globalTransforms.modelViewMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); m_offsetMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); break; case VRWidgetRotationYXZ: globalTransforms.modelViewMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); m_offsetMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); break; case VRWidgetRotationYZX: globalTransforms.modelViewMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); m_offsetMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); break; case VRWidgetRotationZXY: globalTransforms.modelViewMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); m_offsetMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); break; case VRWidgetRotationZYX: globalTransforms.modelViewMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); globalTransforms.modelViewMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); m_offsetMatrix.rotate(rotOffsetX, 1.0f, 0.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetY, 0.0f, 1.0f, 0.0f); m_offsetMatrix.rotate(rotOffsetZ, 0.0f, 0.0f, 1.0f); break; } globalTransforms.modelViewProjectionMatrix = globalTransforms.projectionMatrix * globalTransforms.modelViewMatrix; globalTransforms.normalMatrix = globalTransforms.modelViewMatrix.normalMatrix(); } void VRWidget::endComponentRender(const QVector3D& boundMinus, const QVector3D& boundPlus) { QVector3D mappedMinus, mappedPlus; mappedMinus = m_offsetMatrix.map(boundMinus); mappedPlus = m_offsetMatrix.map(boundPlus); if (mappedMinus.x() < m_boundMinus.x()) m_boundMinus.setX(mappedMinus.x()); if (mappedMinus.y() < m_boundMinus.y()) m_boundMinus.setY(mappedMinus.y()); if (mappedMinus.z() < m_boundMinus.z()) m_boundMinus.setZ(mappedMinus.z()); if (mappedPlus.x() > m_boundPlus.x()) m_boundPlus.setX(mappedPlus.x()); if (mappedPlus.y() > m_boundPlus.y()) m_boundPlus.setY(mappedPlus.y()); if (mappedPlus.z() > m_boundPlus.z()) m_boundPlus.setZ(mappedPlus.z()); popOffsetMatrix(); popModelMatrix(); } void VRWidget::endCompositeRender() { popOffsetMatrix(); popModelMatrix(); } void VRWidget::endWidgetRender() { float border = 0.1f; if (m_selected && m_drawSelectBox) { QtGLWireCubeComponent box; box.generate( m_boundPlus.x() - m_boundMinus.x() + border, m_boundPlus.y() - m_boundMinus.y() + border, m_boundPlus.z() - m_boundMinus.z() + border); globalTransforms.modelViewMatrix.translate((m_boundPlus.x() + m_boundMinus.x()) / 2.0f, (m_boundPlus.y() + m_boundMinus.y()) / 2.0f, (m_boundPlus.z() + m_boundMinus.z()) / 2.0f); box.setShader(globalShader[QTGLSHADER_FLAT]); box.setColor(Qt::red); globalTransforms.modelViewProjectionMatrix = globalTransforms.projectionMatrix * globalTransforms.modelViewMatrix; box.draw(); } ARCustomSelectBox(); popModelMatrix(); if (m_modelMatrixStack.size() != 0) { qDebug() << m_widgetType << " leaving with non empty model stack"; return; } if (m_offsetMatrixStack.size() != 0) { qDebug() << m_widgetType << " leaving with non empty offset stack"; return; } } void VRWidget::pushModelMatrix() { m_modelMatrixStack.push(globalTransforms.modelViewMatrix); } void VRWidget::popModelMatrix() { if (m_modelMatrixStack.size() == 0) { qDebug() << m_widgetType << " tried to pop empty matrix stack"; return; } globalTransforms.modelViewMatrix = m_modelMatrixStack.pop(); } void VRWidget::pushOffsetMatrix() { m_offsetMatrixStack.push(m_offsetMatrix); } void VRWidget::popOffsetMatrix() { if (m_offsetMatrixStack.size() == 0) { qDebug() << m_widgetType << " tried to pop empty offset matrix stack"; return; } m_offsetMatrix = m_offsetMatrixStack.pop(); } void VRWidget::setCenter(float x, float y, float z, bool unconditional) { QVector3D newCenter(x, y, z); conditionalMove(newCenter, unconditional); } void VRWidget::setCenter(const QVector3D center, bool unconditional) { conditionalMove(center, unconditional); } void VRWidget::getCenter(float& x, float& y, float& z) { x = m_center.x(); y = m_center.y(); z = m_center.z(); } void VRWidget::getCenter(QVector3D& center) { center = m_center; } void VRWidget::moveCenter(float deltaX, float deltaY, float deltaZ, bool unconditional) { QVector3D newCenter = m_center + QVector3D(deltaX, deltaY, deltaZ); conditionalMove(newCenter, unconditional); } void VRWidget::moveCenter(const QVector3D delta, bool unconditional) { QVector3D newCenter = m_center + delta; conditionalMove(newCenter, unconditional); } void VRWidget::moveCenterTransform(const QVector3D& intersection, const QVector3D& newIntersection) { QVector3D mappedIntersection; QVector3D mappedNewIntersection; mappedIntersection = m_modelMatrix.map(intersection); mappedNewIntersection = m_modelMatrix.map(newIntersection); QVector3D newCenter = m_center + mappedNewIntersection - mappedIntersection; conditionalMove(newCenter, false); // qDebug() << "MCT " << mappedIntersection.x() << " -> " << mappedNewIntersection.x() << " " // << mappedIntersection.y() << " -> " << mappedNewIntersection.y() << " " // << mappedIntersection.z() << " -> " << mappedNewIntersection.z() << " "; moveTowardOrigin(mappedNewIntersection.length() - mappedIntersection.length()); } void VRWidget::moveTowardOrigin(float distance) { // qDebug() << "MTO " << distance; QVector3D origin = -m_center; origin.normalize(); origin *= distance; QVector3D newCenter = m_center + origin; conditionalMove(newCenter, false); } void VRWidget::setRotationOrder(VRWidgetRotationOrder order) { m_rotationOrder = order; } void VRWidget::setRotation(float x, float y, float z, bool unconditional) { QVector3D newRotation(x, y, z); conditionalRotate(newRotation, unconditional); } void VRWidget::setRotation(const QVector3D rotation, bool unconditional) { conditionalRotate(rotation, unconditional); } void VRWidget::getRotation(float& x, float& y, float& z) { x = m_rotation.x(); y = m_rotation.y(); z = m_rotation.z(); } void VRWidget::getRotation(QVector3D& rotation) { rotation = m_rotation; } void VRWidget::moveRotation(float deltaX, float deltaY, float deltaZ, bool unconditional) { QVector3D rotation; rotation.setX(m_rotation.x() + deltaX); rotation.setY(m_rotation.y() + deltaY); rotation.setZ(m_rotation.z() + deltaZ); conditionalRotate(rotation, unconditional); } void VRWidget::moveRotation(const QVector3D delta, bool unconditional) { QVector3D rotation(m_rotation); rotation += delta; conditionalRotate(rotation, unconditional); } void VRWidget::setSize(float width, float height, float depth) { m_width = width; m_height = height; m_depth = depth; } void VRWidget::getSize(float& width, float& height, float& depth) { width = m_width; height = m_height; depth = m_depth; } void VRWidget::setMoveMask(bool enableX, bool enableY, bool enableZ) { m_enableMoveX = enableX; m_enableMoveY = enableY; m_enableMoveZ = enableZ; } void VRWidget::setRotationMask(bool enableX, bool enableY, bool enableZ) { m_enableRotX = enableX; m_enableRotY = enableY; m_enableRotZ = enableZ; } void VRWidget::updateViewportBox() { // bottom left front m_viewportBoundBox[0] = m_modelMatrix.map(QVector3D(m_boundMinus.x() - VRWIDGET_BOUNDBOX_BORDER, m_boundMinus.y() - VRWIDGET_BOUNDBOX_BORDER, m_boundPlus.z() + VRWIDGET_BOUNDBOX_BORDER)); // top left front m_viewportBoundBox[1] = m_modelMatrix.map(QVector3D(m_boundMinus.x() - VRWIDGET_BOUNDBOX_BORDER, m_boundPlus.y() + VRWIDGET_BOUNDBOX_BORDER, m_boundPlus.z() + VRWIDGET_BOUNDBOX_BORDER)); // top right front m_viewportBoundBox[2] = m_modelMatrix.map(QVector3D(m_boundPlus.x() + VRWIDGET_BOUNDBOX_BORDER, m_boundPlus.y() + VRWIDGET_BOUNDBOX_BORDER, m_boundPlus.z() + VRWIDGET_BOUNDBOX_BORDER)); // bottom right front m_viewportBoundBox[3] = m_modelMatrix.map(QVector3D(m_boundPlus.x() + VRWIDGET_BOUNDBOX_BORDER, m_boundMinus.y() - VRWIDGET_BOUNDBOX_BORDER, m_boundPlus.z() + VRWIDGET_BOUNDBOX_BORDER)); // bottom left back m_viewportBoundBox[4] = m_modelMatrix.map(QVector3D(m_boundMinus.x() - VRWIDGET_BOUNDBOX_BORDER, m_boundMinus.y() - VRWIDGET_BOUNDBOX_BORDER, m_boundMinus.z() - VRWIDGET_BOUNDBOX_BORDER)); // top left back m_viewportBoundBox[5] = m_modelMatrix.map(QVector3D(m_boundMinus.x() - VRWIDGET_BOUNDBOX_BORDER, m_boundPlus.y() + VRWIDGET_BOUNDBOX_BORDER, m_boundMinus.z() - VRWIDGET_BOUNDBOX_BORDER)); // top right back m_viewportBoundBox[6] = m_modelMatrix.map(QVector3D(m_boundPlus.x() + VRWIDGET_BOUNDBOX_BORDER, m_boundPlus.y() + VRWIDGET_BOUNDBOX_BORDER, m_boundMinus.z() - VRWIDGET_BOUNDBOX_BORDER)); // bottom right back m_viewportBoundBox[7] = m_modelMatrix.map(QVector3D(m_boundPlus.x() + VRWIDGET_BOUNDBOX_BORDER, m_boundMinus.y() - VRWIDGET_BOUNDBOX_BORDER, m_boundMinus.z() - VRWIDGET_BOUNDBOX_BORDER)); } bool VRWidget::boundingBoxIntersection(QVector3D& intersection, int& rectIndex, const QVector3D& ray0, const QVector3D& ray1) { QVector3D bestIntersection; int bestFace = -1; float bestDistance = 100000000000; float length; updateViewportBox(); // Nearest face rectIndex = 0; if (QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[0], m_viewportBoundBox[1], m_viewportBoundBox[2], m_viewportBoundBox[3], true)) { bestIntersection = intersection; bestDistance = bestIntersection.length(); bestFace = rectIndex; } // Furthest face rectIndex++; if (QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[4], m_viewportBoundBox[5], m_viewportBoundBox[6], m_viewportBoundBox[7], true)) { if ((length = intersection.length()) < bestDistance) { bestIntersection = intersection; bestDistance = length; bestFace = rectIndex; } } // Top face rectIndex++; if (QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[1], m_viewportBoundBox[5], m_viewportBoundBox[6], m_viewportBoundBox[2], true)) { if ((length = intersection.length()) < bestDistance) { bestIntersection = intersection; bestDistance = length; bestFace = rectIndex; } } // Bottom face rectIndex++; if (QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[0], m_viewportBoundBox[4], m_viewportBoundBox[7], m_viewportBoundBox[3], true)) { if ((length = intersection.length()) < bestDistance) { bestIntersection = intersection; bestDistance = length; bestFace = rectIndex; } } // Left side face rectIndex++; if (QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[0], m_viewportBoundBox[4], m_viewportBoundBox[5], m_viewportBoundBox[1], true)) { if ((length = intersection.length()) < bestDistance) { bestIntersection = intersection; bestDistance = length; bestFace = rectIndex; } } // Right side face rectIndex++; if (QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[3], m_viewportBoundBox[2], m_viewportBoundBox[6], m_viewportBoundBox[7], true)) { if ((length = intersection.length()) < bestDistance) { bestIntersection = intersection; bestDistance = length; bestFace = rectIndex; } } if (bestFace == -1) return false; intersection = m_inverseModelMatrix.map(bestIntersection); rectIndex = bestFace; qDebug() << "Hit face " << rectIndex << " raw inter " << bestIntersection.x() << " " << bestIntersection.y() << " " << bestIntersection.z(); return true; } bool VRWidget::boundingRectIntersection(QVector3D& intersection, const int rectIndex, const QVector3D& ray0, const QVector3D& ray1) { bool success; updateViewportBox(); switch (rectIndex) { // Nearest face case 0: success = QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[0], m_viewportBoundBox[1], m_viewportBoundBox[2], m_viewportBoundBox[3], false); break; // Furthest face case 1: success = QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[4], m_viewportBoundBox[5], m_viewportBoundBox[6], m_viewportBoundBox[7], false); break; // Top face case 2: success = QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[1], m_viewportBoundBox[5], m_viewportBoundBox[6], m_viewportBoundBox[2], false); break; // Bottom face case 3: success = QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[0], m_viewportBoundBox[4], m_viewportBoundBox[7], m_viewportBoundBox[3], false); break; // Left side face case 4: success = QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[0], m_viewportBoundBox[4], m_viewportBoundBox[5], m_viewportBoundBox[1], false); break; // Right side face case 5: success = QtGLRayRectangleIntersection(intersection, ray0, ray1, m_viewportBoundBox[3], m_viewportBoundBox[2], m_viewportBoundBox[6], m_viewportBoundBox[7], false); break; default: success = false; } if (success) { // qDebug() << "raw inter " << intersection.x() << " " << intersection.y() << " " << intersection.z(); intersection = m_inverseModelMatrix.map(intersection); } return success; } void VRWidget::setModelMatrix() { m_modelMatrix.setToIdentity(); m_inverseModelMatrix.setToIdentity(); m_modelMatrix.translate(m_center); switch (m_rotationOrder) { case VRWidgetRotationXYZ: m_modelMatrix.rotate(m_rotation.z(), 0.0f, 0.0f, 1.0f); m_modelMatrix.rotate(m_rotation.y(), 0.0f, 1.0f, 0.0f); m_modelMatrix.rotate(m_rotation.x(), 1.0f, 0.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.x(), 1.0f, 0.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.y(), 0.0f, 1.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.z(), 0.0f, 0.0f, 1.0f); break; case VRWidgetRotationXZY: m_modelMatrix.rotate(m_rotation.y(), 0.0f, 1.0f, 0.0f); m_modelMatrix.rotate(m_rotation.z(), 0.0f, 0.0f, 1.0f); m_modelMatrix.rotate(m_rotation.x(), 1.0f, 0.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.x(), 1.0f, 0.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.z(), 0.0f, 0.0f, 1.0f); m_inverseModelMatrix.rotate(-m_rotation.y(), 0.0f, 1.0f, 0.0f); break; case VRWidgetRotationYXZ: m_modelMatrix.rotate(m_rotation.z(), 0.0f, 0.0f, 1.0f); m_modelMatrix.rotate(m_rotation.x(), 1.0f, 0.0f, 0.0f); m_modelMatrix.rotate(m_rotation.y(), 0.0f, 1.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.y(), 0.0f, 1.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.x(), 1.0f, 0.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.z(), 0.0f, 0.0f, 1.0f); break; case VRWidgetRotationYZX: m_modelMatrix.rotate(m_rotation.x(), 1.0f, 0.0f, 0.0f); m_modelMatrix.rotate(m_rotation.z(), 0.0f, 0.0f, 1.0f); m_modelMatrix.rotate(m_rotation.y(), 0.0f, 1.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.y(), 0.0f, 1.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.z(), 0.0f, 0.0f, 1.0f); m_inverseModelMatrix.rotate(-m_rotation.x(), 1.0f, 0.0f, 0.0f); break; case VRWidgetRotationZXY: m_modelMatrix.rotate(m_rotation.y(), 0.0f, 1.0f, 0.0f); m_modelMatrix.rotate(m_rotation.x(), 1.0f, 0.0f, 0.0f); m_modelMatrix.rotate(m_rotation.z(), 0.0f, 0.0f, 1.0f); m_inverseModelMatrix.rotate(-m_rotation.z(), 0.0f, 0.0f, 1.0f); m_inverseModelMatrix.rotate(-m_rotation.x(), 1.0f, 0.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.y(), 0.0f, 1.0f, 0.0f); break; case VRWidgetRotationZYX: m_modelMatrix.rotate(m_rotation.x(), 1.0f, 0.0f, 0.0f); m_modelMatrix.rotate(m_rotation.y(), 0.0f, 1.0f, 0.0f); m_modelMatrix.rotate(m_rotation.z(), 0.0f, 0.0f, 1.0f); m_inverseModelMatrix.rotate(-m_rotation.z(), 0.0f, 0.0f, 1.0f); m_inverseModelMatrix.rotate(-m_rotation.y(), 0.0f, 1.0f, 0.0f); m_inverseModelMatrix.rotate(-m_rotation.x(), 1.0f, 0.0f, 0.0f); break; } m_inverseModelMatrix.translate(-m_center); } void VRWidget::conditionalMove(const QVector3D& newCenter, bool unconditional) { QVector3D oldCenter; bool visible = true; oldCenter = m_center; if (m_enableMoveX) m_center.setX(newCenter.x()); if (m_enableMoveY) m_center.setY(newCenter.y()); if (m_enableMoveZ) m_center.setZ(newCenter.z()); setModelMatrix(); if (unconditional) return; updateViewportBox(); for (int i = 0; i < 8; i++) { if ((m_viewportBoundBox[i].z() >= -globalTransforms.nearPlane) || (m_viewportBoundBox[i].z() <= -globalTransforms.farPlane)) { visible = false; break; } } if (!visible) { m_center = oldCenter; setModelMatrix(); } } void VRWidget::conditionalRotate(const QVector3D& newRotation, bool unconditional) { QVector3D oldRotation; bool visible = true; oldRotation = m_rotation; if (m_enableRotX) m_rotation.setX(newRotation.x()); if (m_enableRotY) m_rotation.setY(newRotation.y()); if (m_enableRotZ) m_rotation.setZ(newRotation.z()); setModelMatrix(); if (unconditional) return; updateViewportBox(); for (int i = 0; i < 8; i++) { if ((m_viewportBoundBox[i].z() >= -globalTransforms.nearPlane) || (m_viewportBoundBox[i].z() <= -globalTransforms.farPlane)) { visible = false; break; } } if (!visible) { m_rotation = oldRotation; setModelMatrix(); } } rtimulib-7.2.1/RTHost/RTIMULibGL/VRWidgetLib/VRWidget.h000066400000000000000000000246351254201074400223040ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014, richards-tech // // Permission 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. #ifndef VRWIDGET_H #define VRWIDGET_H #include #include #include #include typedef enum { VRWIDGET_WIDGET, VRWIDGET_POINTER, VRWIDGET_SELECTOR, VRWIDGET_STATUS, VRWIDGET_PLANE, VRWIDGET_WEB, VRWIDGET_WEBPAGE, VRWIDGET_MAP, VRWIDGET_COMPASS, VRWIDGET_IMU, VRWIDGET_SPHERE } VRWIDGET_TYPE; // Default used for the default render #define VRWIDGET_DEFAULT_X 0.0f #define VRWIDGET_DEFAULT_Y 0.0f #define VRWIDGET_DEFAULT_Z -20.0f #define VRWIDGET_DEFAULT_RADIUS 1.0f #define VRWIDGET_DEFAULT_WIDTH 5.0f #define VRWIDGET_DEFAULT_HEIGHT 5.0f #define VRWIDGET_DEFAULT_DEPTH 5.0f #define VRWIDGET_BOUNDBOX_BORDER 0.1f typedef enum { VRWidgetRotationXYZ = 0, VRWidgetRotationXZY, VRWidgetRotationYXZ, VRWidgetRotationYZX, VRWidgetRotationZXY, VRWidgetRotationZYX } VRWidgetRotationOrder; class VRWidget : public QObject { Q_OBJECT public: VRWidget(QObject *parent, VRWIDGET_TYPE = VRWIDGET_WIDGET); ~VRWidget(); // VRWidgetInit() called to init OpenGL stuff - must be provided virtual void VRWidgetInit() = 0; // VRWidgetRender() called to render the widget - must be provided virtual void VRWidgetRender() = 0; // function to render the widget // ARHandleSingleClick() can be overriden to process single click events // virtual void ARHandleSingleClick(int x, int y, Qt::MouseButton button) virtual void ARHandleSingleClick(int , int , Qt::MouseButton ) {}; // ARHandleDoubleClick() can be overriden to process double click events // virtual void ARHandleDoubleClick(int x, int y, Qt::MouseButton button) virtual void ARHandleDoubleClick(int , int , Qt::MouseButton ) {}; // ARCustomSelectBox() allows a widget to draw a custom select box virtual void ARCustomSelectBox() {}; // enableSelectBox() controls whether select box is drawn inline void enableSelectBox(bool enable) {m_drawSelectBox = enable;}; // setIcon() sets the filename of the widget icon inline void setIcon(const char *icon) { m_icon = icon; }; // getIcon() sets the filename of the widget icon inline const QString& getIcon() { return m_icon; }; // getWidgetType() returns the type of the widget inline VRWIDGET_TYPE getWidgetType() { return m_widgetType; }; // setCenter() sets the position of the center of the object virtual void setCenter(float x, float y, float z, bool unconditional = false); virtual void setCenter(const QVector3D center, bool unconditional = false); // getCenter() sets the position of the center of the object virtual void getCenter(float& x, float& y, float& z); virtual void getCenter(QVector3D& center); // setSize() sets the width and height and depth of the object virtual void setSize(float width, float height, float depth); // getSize() gets the width and height and depth of the object virtual void getSize(float& width, float& height, float& depth); // moveCenter() moves the center of the widget virtual void moveCenter(float deltaX, float deltaY, float deltaZ, bool unconditional = false); virtual void moveCenter(const QVector3D delta, bool unconditional = false); // moveCenterTransform() moves the center of the widget using intersections // with the bounding box so as to maintain a constant intersection point virtual void moveCenterTransform(const QVector3D& intersection, const QVector3D& newIntersection); // moveTowardOrigin() moves object towards the eye point origin virtual void moveTowardOrigin(float distance); // setMoveMask() allows individual move directions to be controlled virtual void setMoveMask(bool enableX, bool enableY, bool enableZ); // setRotationOrder() sets the order of rotation virtual void setRotationOrder(VRWidgetRotationOrder order); // setRotation() sets the orientation of the object virtual void setRotation(float x, float y, float z, bool unconditional = false); virtual void setRotation(const QVector3D rotation, bool unconditional = false); // getRotation() sets the orientation of the object virtual void getRotation(float& x, float& y, float& z); virtual void getRotation(QVector3D& rotation); // moveRotation() changes the rotation angles of the widget virtual void moveRotation(float deltaPhi, float deltaTheta, float deltaPsi, bool unconditional = false); virtual void moveRotation(const QVector3D delta, bool unconditional = false); // setRotationMask() allows individual rotations to be controlled virtual void setRotationMask(bool enableX, bool enableY, bool enableZ); // setSelected() tell the widget to be in selected state if true inline void setSelected(bool selected) {m_selected = selected;} // selected() returns true if the widget is selected inline bool selected() { return m_selected; } // boundingBoxIntersection() returns the point where the ray defined by points ray0 and ray1 // intersect with the bounding box and which rectangle index. If it doesn't, the function returns false bool boundingBoxIntersection(QVector3D& intersection, int& rectIndex, const QVector3D& ray0, const QVector3D& ray1); // boundingBoxRectIntersection() looks for an intersection with a specific // rectangle in the bounding box bool boundingRectIntersection(QVector3D& intersection, const int rectIndex, const QVector3D& ray0, const QVector3D& ray1); protected: // startWidgetRender() is called at start of widget's render function void startWidgetRender(); // endWidgetRender() is called at end of widget's render function void endWidgetRender(); // startCompositeRender() is called at start of composite component render sequence // parameters are offset from widget center for this composite component void startCompositeRender(const QVector3D& posOffset, const QVector3D& angleOffset); void startCompositeRender(float offsetX = 0.0f, float offsetY = 0.0f, float offsetZ = 0.0f, float rotOffsetX = 0.0f, float rotOffsetY = 0.0f, float rotOffsetZ = 0.0f); // endCompositeRender() is called at end of a composite component render sequence void endCompositeRender(); // startComponentRender() is called at start of component render sequence // parameters are offset from widget center for this component void startComponentRender(const QVector3D& posOffset, const QVector3D& angleOffset); void startComponentRender(float offsetX = 0.0f, float offsetY = 0.0f, float offsetZ = 0.0f, float rotOffsetX = 0.0f, float rotOffsetY = 0.0f, float rotOffsetZ = 0.0f); // endComponentRender() is called at end of a component render sequence void endComponentRender(const QVector3D& boundMinus, const QVector3D& boundPlus); // pushModelMatrix() push the current globalModelMatrix on the stack void pushModelMatrix(); // popModelMatrix() pops the current globalModelMatrix from the stack void popModelMatrix(); // pushOffsetMatrix() push the current offset matrix on the stack void pushOffsetMatrix(); // popOffsetMatrix() pops the current offset matrix from the stack void popOffsetMatrix(); QVector3D m_center; QVector3D m_rotation; float m_width; float m_height; float m_depth; private: void updateViewportBox(); // update the viewport bounding box void conditionalMove(const QVector3D& newCenter, bool unconditional); // performs move if allowed void conditionalRotate(const QVector3D& newRotation, bool unconditional); // performs rotate if allowed bool m_enableMoveX; bool m_enableMoveY; bool m_enableMoveZ; bool m_enableRotX; bool m_enableRotY; bool m_enableRotZ; bool m_saveState; // true if save settings, false if not void setModelMatrix(); // sets the model matrix for the widget bool m_drawSelectBox; // true if draw select box automatically QString m_icon; // the icon filename VRWIDGET_TYPE m_widgetType; // type of widget QVector3D m_boundMinus; // lowest in each coord for box in widget coords QVector3D m_boundPlus; // highest in each coord for box in widget coords QVector3D m_viewportBoundBox[8]; // the bounding box in viewport coords QStack m_modelMatrixStack; // the model matrix stack QStack m_offsetMatrixStack; // the offset matrix stack QMatrix4x4 m_offsetMatrix; // the current offset matrix QMatrix4x4 m_modelMatrix; // the current model matrix QMatrix4x4 m_inverseModelMatrix; // the reverse transformation bool m_selected; // if in selected state VRWidgetRotationOrder m_rotationOrder; }; #endif // VRWIDGET_H rtimulib-7.2.1/RTHost/RTIMULibGL/VRWidgetLib/VRWidgetLib.pri000066400000000000000000000026601254201074400232700ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014, richards-tech #// #// Permission 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. INCLUDEPATH += $$PWD DEPENDPATH += $$PWD HEADERS += $$PWD/VRWidget.h \ $$PWD/VRIMUWidget.h SOURCES += $$PWD/VRWidget.cpp \ $$PWD/VRIMUWidget.cpp RESOURCES += $$PWD/VRWidgetLib.qrc rtimulib-7.2.1/RTHost/RTIMULibGL/VRWidgetLib/VRWidgetLib.qrc000066400000000000000000000004151254201074400232570ustar00rootroot00000000000000 Images/Compass.png Images/CompassNeedle.png Images/RedShade.png Images/BlueShade.png Images/GreenShade.png rtimulib-7.2.1/RTHost/RTIMULibGL/VRWidgetLib/VRWidgetLib.sln000066400000000000000000000015441254201074400232720ustar00rootroot00000000000000 Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "VRWidgetLib", "VRWidgetLib.vcxproj", "{183C43F1-DC99-467E-8364-AA7E69E8E1A8}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 Release|Win32 = Release|Win32 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {183C43F1-DC99-467E-8364-AA7E69E8E1A8}.Debug|Win32.ActiveCfg = Debug|Win32 {183C43F1-DC99-467E-8364-AA7E69E8E1A8}.Debug|Win32.Build.0 = Debug|Win32 {183C43F1-DC99-467E-8364-AA7E69E8E1A8}.Release|Win32.ActiveCfg = Release|Win32 {183C43F1-DC99-467E-8364-AA7E69E8E1A8}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal rtimulib-7.2.1/RTHost/RTIMULibGL/VRWidgetLib/VRWidgetLib.vcxproj000066400000000000000000000276701254201074400242010ustar00rootroot00000000000000 Debug Win32 Release Win32 true true true true $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing VRIMUWidget.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_OPENGL_LIB -DVRWIDGETLIB_LIB "-I.\..\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtOpenGL" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing VRIMUWidget.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_OPENGL_LIB -DVRWIDGETLIB_LIB "-I.\..\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtOpenGL" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing VRWidget.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_CORE_LIB -DQT_OPENGL_LIB -DVRWIDGETLIB_LIB "-I.\..\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtOpenGL" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing VRWidget.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -DUNICODE -DWIN32 -DWIN64 -DQT_DLL -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQT_OPENGL_LIB -DVRWIDGETLIB_LIB "-I.\..\QtGLLib" "-I.\GeneratedFiles" "-I." "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I$(QTDIR)\include\QtOpenGL" Document %(FullPath);.\Images\Compass.png;.\Images\CompassNeedle.png;.\Images\RedShade.png;.\Images\BlueShade.png;.\Images\GreenShade.png;%(AdditionalInputs) Rcc%27ing %(Identity)... .\GeneratedFiles\qrc_%(Filename).cpp;%(Outputs) "$(QTDIR)\bin\rcc.exe" -name "%(Filename)" -no-compress "%(FullPath)" -o .\GeneratedFiles\qrc_%(Filename).cpp %(FullPath);.\Images\Compass.png;.\Images\CompassNeedle.png;.\Images\RedShade.png;.\Images\BlueShade.png;.\Images\GreenShade.png;%(AdditionalInputs) Rcc%27ing %(Identity)... .\GeneratedFiles\qrc_%(Filename).cpp;%(Outputs) "$(QTDIR)\bin\rcc.exe" -name "%(Filename)" -no-compress "%(FullPath)" -o .\GeneratedFiles\qrc_%(Filename).cpp true true true true true true true true true true {183C43F1-DC99-467E-8364-AA7E69E8E1A8} Qt4VSv1.0 StaticLibrary v120 StaticLibrary v120 <_ProjectFileVersion>10.0.40219.1 $(SolutionDir)$(Platform)\$(Configuration)\ $(SolutionDir)$(Platform)\$(Configuration)\ UNICODE;WIN32;WIN64;QT_DLL;QT_CORE_LIB;QT_OPENGL_LIB;VRWIDGETLIB_LIB;%(PreprocessorDefinitions) ..\QtGLLib;.\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtOpenGL;%(AdditionalIncludeDirectories) Disabled ProgramDatabase MultiThreadedDebugDLL false $(OutDir)\$(ProjectName).lib $(QTDIR)\lib;%(AdditionalLibraryDirectories) UNICODE;WIN32;WIN64;QT_DLL;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QT_OPENGL_LIB;VRWIDGETLIB_LIB;%(PreprocessorDefinitions) ..\QtGLLib;.\GeneratedFiles;.;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;$(QTDIR)\include\QtOpenGL;%(AdditionalIncludeDirectories) MultiThreadedDLL false $(OutDir)\$(ProjectName).lib $(QTDIR)\lib;%(AdditionalLibraryDirectories) rtimulib-7.2.1/RTHost/RTIMULibGL/VRWidgetLib/VRWidgetLib.vcxproj.filters000066400000000000000000000062501254201074400256370ustar00rootroot00000000000000 {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;cxx;c;def {93995380-89BD-4b04-88EB-625FBE52EBFB} h {99349809-55BA-4b9d-BF79-8FDBB0286EB3} ui {D9D6E242-F8AF-46E4-B9FD-80ECBC20BA3E} qrc;* false {71ED8ED8-ACB9-4CE9-BBE1-E00B30144E11} moc;h;cpp False {e723335c-a449-4511-a0d2-9f617a161185} cpp;moc False {652d0b73-0946-4070-967c-d9ff11ba96ac} cpp;moc False Source Files Source Files Generated Files\Debug Generated Files\Release Generated Files\Debug Generated Files\Release Generated Files Header Files Header Files Form Files Resource Files Resource Files Resource Files Resource Files Resource Files rtimulib-7.2.1/RTHost/RTSerialPort/000077500000000000000000000000001254201074400170645ustar00rootroot00000000000000rtimulib-7.2.1/RTHost/RTSerialPort/CMakeLists.txt000066400000000000000000000061751254201074400216350ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014, richards-tech #// #// Permission 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. #// The cmake support was based on work by Moritz Fischer at ettus.com. #// Original copyright notice: # # Copyright 2014 Ettus Research LLC # IF(APPLE) SET(SERIAL_SRCS src/qextserialport.cpp src/qextserialenumerator.cpp src/qextserialport_unix.cpp src/qextserialenumerator_osx.cpp) FIND_LIBRARY(IOKIT_LIBRARY IOKit) FIND_LIBRARY(COREFOUNDATION_LIBRARY CoreFoundation ) MARK_AS_ADVANCED(IOKIT_LIBRARY COREFOUNDATION_LIBRARY) SET(EXTRA_LIBS ${IOKIT_LIBRARY} ${COREFOUNDATION_LIBRARY}) MESSAGE(STATUS "OS is Apple") ELSEIF(UNIX) SET(SERIAL_SRCS src/qextserialport.cpp src/qextserialenumerator.cpp src/qextserialport_unix.cpp src/qextserialenumerator_unix.cpp) SET(EXTRA_LIBS ) MESSAGE(STATUS "OS is Linux") ENDIF(APPLE) IF(WIN32) SET(SERIAL_SRCS src/qextserialport.cpp src/qextserialenumerator.cpp src/qextserialport_win.cpp src/qextserialenumerator_win.cpp) SET(EXTRA_LIBS setupapi advapi32 user32) MESSAGE(STATUS "OS is Windows") ENDIF(WIN32) SET(CMAKE_AUTOMOC ON) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/src) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) IF(DEFINED QT5) FIND_PACKAGE(Qt5Core REQUIRED) IF(WIN32) ADD_LIBRARY(RTSerialPort STATIC ${SERIAL_SRCS}) ELSE(WIN32) ADD_LIBRARY(RTSerialPort SHARED ${SERIAL_SRCS}) ENDIF(WIN32) qt5_use_modules(RTSerialPort Core) TARGET_LINK_LIBRARIES(RTSerialPort ${EXTRA_LIBS}) ELSE(DEFINED QT5) FIND_PACKAGE(Qt4 REQUIRED) INCLUDE(${QT_USE_FILE}) ADD_DEFINITIONS(${QT_DEFINITIONS}) IF(WIN32) ADD_LIBRARY(RTSerialPort STATIC ${SERIAL_SRCS}) ELSE(WIN32) ADD_LIBRARY(RTSerialPort SHARED ${SERIAL_SRCS}) ENDIF(WIN32) TARGET_LINK_LIBRARIES(RTSerialPort ${EXTRA_LIBS} ${QT_LIBRARIES}) ENDIF(DEFINED QT5) INSTALL(TARGETS RTSerialPort DESTINATION lib) rtimulib-7.2.1/RTHost/RTSerialPort/ChangeLog000066400000000000000000000317311254201074400206430ustar00rootroot00000000000000Change history for QextSerialPort (formerly QwSerialPort): (Lines beginning with + represent new functionality, * represent changed or fixed functionality, - represent removed or deprecated functionality) Version 1.2 beta1 (2012 Debao Zhang) * D-pointer and Q_PRIVATE_SLOT are used to moving private members from QextSerialPort to QextSerialPortPrivate * qdoc3 instead of doxygen is used for generating documents * MIT license header add to all sources files + add a helper class QextWinEventNotifier for windows user, when user's SDK doesnot contain Qt's private files, this class will be auto selected. + Support platform custom baudrate. Macros such as B230400 / B460800 can be used directly if you OS support it. Version 1.2win-alpha (2007 Michal Policht) + Added QextSerialEnumerator pre-alpha. Works under W2k and later versions of Windows. + Event driven mechanism (alternative to polling) is now available on Windows. - Removed default (=0) parameter from open() functions. * Fixed bug #1714917 in Win_QextSerialPort::close() method (by Kurt). * Fixed problem with lack of proper blocking in readData() on win32 (by Brandon Fosdick). * Removed QT_THREAD_SUPPORT option. Now QextSerialPort must be always compiled with threads support. * Mutexes are not static. * setTimeout() now accepts only one parameter. * bytesAvailable() on POSIX now shows 0 bytes instead of -1 when no bytes are available. * bytesAvailable() is const. * native POSIX file descriptors instead of QFile->handle() calls + POSIX: Save and restore original termios when opening and closing the device * POSIX: Only disable special characters on systems that support it * POSIX: Use cfmakeraw(3) to get a non-canonical termios + POSIX: Call close(2) in close() to actually close the device Version 1.1 (official release) Version 1.0.1 * Minor changes (mostly in test application) Version 1.0.0e (by Micha? Policht) * Fixed bytesAvailable(). Includes buffered bytes to the result. + Added isSequential() method. + Provided test application Version 1.0.0d ( changes by Micha? Policht ) - Removed isOpen() overriden declaration/implementation from qextserialport's classes. isOpen() relies on QIODevice now. - Removed bool portOpen variable. Replaced by internal QIODevice.openMode. - Removed getChar(), putChar() overriden declaration/implementation. QIODevice can handle this. * Calling open() with specified OpenMode invokes QIODevice::open() which result in proper openMode setting. * readData(), writeData() are protected as in QIODevice declaration. * QIODevice:: read() and write() function are working now (use them instead of readData() writeData()). * readData(), writeData() don't check if port is open any more (read() and write() assures that). The same behaviour can be found in QFile for example. * Fixed readLine(). * Fixed randomly crash on deletion bug on Windows ( by Stuart Nixon ) http://lists.trolltech.com/qt-interest/2007-02/thread00340-0.html#msg00351 Version 0.9 (March 3, 2005) Stefan Sander : + Added a new precompiler constant, _TTY_FREEBSD_ to support FreeBSD port names. + Added _TTY_WIN_ constant in qextserialport.pro win32:DEFINES to have Windows port names as default when compiling on it. - Removed construct() call from QextSerialBase constructors, it is called indirectly through Win_QextSerialPort::construct() and Posix_QextSerialPort::construct(). + Added construct() call to Win_QextSerialPort constructors. + Added setTimeout(0, 500) call to Win_QextSerialPort::construct(). - Removed setTimeout(0, 500) call from Win_QextSerialPort(const char* name). * Fixed Posix_QextSerialPort::open(int) control flow, now the port settings are only applied if the associated file could be opened. * Fixed masking CR to NL, in Posix_CommConfig.c_iflag Version 0.8 (, 2003) (Alpha release): * Added code to set the port timeouts in Win_QextSerialPort's default constructor. * Fixed Posix_QextSerialPort::construct() to set up the port correctly. * Fixed syntax errors in 2 ioctl() calls in posix_QextSerialPort. * lastError is now initialized to E_NO_ERROR in the QextSerialBase constructor. * The select() call in posix_QextSerialPort::bytesWaiting() is now properly coded. Previously it would always time out. * Fixed runtime errors in the ioctl() calls for Posix_QextSerialPort::setDtr() and Posix_QextSerialPort::setRts(). Thanks to Marc Pignat. Version 0.7 (June 15, 2002) : (0.61 - unofficial release) * Fixed a small bug in the initializations of the static members when QT_THREAD_SUPPORT was defined. * Fixed a bug that caused Borland's compiler to choke on Windows platforms (which perversely actually stemmed from a shortcoming of Visual C++ that Borland doesn't have). (0.62 - unofficial release) * Fixed a bug that gave Q_LONG the wrong typedef for QT versions prior to 3.0. (0.63 - unofficial release) * Fixed 2 incorrect references to Posix_Comm_Config. * Fixed scoping of Posix_QextSerialPort::operator=(). * Posix_QextSerialPort::construct should now be coded correctly. * Fixed return type for Posix_QextSerialPort::size(). (0.64 - unofficial release) * Fixed all the port settings functions to work properly when opening the port for the first time - previously none of the settings were being applied when the port was opened. * Fixed an oversight in Win_QextSerialPort::open() that caused the setting of port parameters to fail on NT and 2000 systems. (0.7 - official release) * Fixed some calls to QextSerialBase constructors that no longer exist on the POSIX side. * Fixed the bad memcpy()'s in the POSIX copy constructor. * Fixed the Offset scoping problem under gcc 2.95. * The CBAUD flag has been deprecated on some POSIX systems. Fixed Posix_QextSerialPort::setBaudRate() to reflect this. * Added construct() calls to all of the Posix_QextSerialPort constructors. * Fixed double (and conflicting) typedefs of Offset when using QT versions prior to 3.0 * Changed the call to CreateFile() to CreateFileA() in Win_QextSerialPort.cpp. This should get rid of problems for those using Unicode or other multibyte character sets for their string literals. * A few tweaks to the documentation. - Removed the protected Posix_Handle variable from Posix_QextSerialPort. Version 0.6 (March 11, 2002) : + Added a new precompiler constant, QTVER_PRE_30. QT3 changed the return types of some QIODevice functions. Therefore, if compiling on versions of QT prior to 3.0, you should always define QTVER_PRE_30 in your project. Also had to add some preprocessor blocks to support both 3.0 and earlier versions of QT. + Added implementations of 2 of the new constructors added in 0.5 to both Win_QextSerialPort and Posix_QextSerialPort. * The scoping of the enums used in the PortSettings struct has been fixed. * QObject inheritance has been removed. This should not affect the functionality of the classes. * Replaced a few stray references to mutex->unlock() with UNLOCK_MUTEX() in the Windows code. * Fixed several runtime errors caused by calling nonexistent members of QextSerialBase. * Fixed a whole bunch of little things that were causing MSVC to choke when compiling for Windows. Version 0.5 (February 15, 2002): + There are 4 new macros (LOCK_MUTEX, UNLOCK_MUTEX, TTY_WARNING, and TTY_PORTABILITY_WARNING) that replace most of those ugly #ifdef blocks in the code. + In place of the old namingConvention stuff, there is a new function, setName(). It is used to set the name of the device to be associated with the object. The new name() function can be used to retrieve the device name, which is stored in the new member variable portName. + There is a new version of open() that takes a const char* as a parameter. It can be used to specify the name of the device when it is opened rather than at construction time. * 3 constructors have been removed and 3 more added. There is now a copy constructor (and operator=()) as well as a constructor that takes a PortSettings structure as a parameter, and another that takes both a device name and a PortSettings structure. As a result of these changes the PortSettings structure declaration is no longer local to the QextSerialBase class. All of the removed constructors had to do with the setNamingConvention() system. * The static mutex member should now be reference-counted and only deleted when it is no longer referenced. * Most of the object construction duties have been pushed back into QextSerialBase * Fixed a couple resource leaks, mostly to do with unlocking the mutex properly - Removed the setNamingConvention() nonsense. - Removed all QStrings and calls to sprintf() for thread compatibility. - Removed setNumber() functions as well as the portNumber member variable, as they were only necessary under the setNamingConvention() system. I am grateful to Jorg Preiss (Preisz? Sorry, American keyboards don't have an ess-tset character ;)) for his invaluable input on most of the changes that went into this version. Version 0.4 (March 20, 2001): + All of the classes now derive from QObject as well as QIODevice. This is pretty much useless at the moment - signals and slots may be used to implement asynchronous communications in a future version + Added configurable timeouts via the setTimeout() function. The default timeout for read and write operations is now 500 milliseconds + There is now a functional .pro file for the library (thanks to Gunnstein Lye) + The prefixes for all of the classes have changed from Qw to Qext, in compliance with the qt-addons project standard * Fixed a bug that caused port settings to be restored incorrectly when switching ports with setNumber() * Minor changes to QextSerialBase::setNumber(). Functionality should now reflect the documentation, which has also been updated to reflect the changes that went in on version 0.3. * Some fixes to the documentation. The Posix_QextSerialPort and Win_QextSerialPort classes should no longer have any unnecessary references to inapplicable platforms, and the documentation for open() has been updated. * Should now compile without QT_THREAD_SUPPORT defined (ie, in single- threaded environments), although it will require slight changes to the makefile (tmake "CONFIG-=thread" should work) * Fixed a few compilation issues, especially on the POSIX side (should compile under Linux now :)) * POSIX code is a little cleaner and more efficient * Various small fixes to the documentation * Constants now follow a consistent naming convention, with underscores at the beginning and end of each. For example TTY_POSIX has become _TTY_POSIX_ Version 0.3 (Feb. 14, 2001): + Added a warning that appears when QwSerialPort is compiled on a POSIX platform that does not implement 76800 baud operation. In this situation QwSerialPort will also switch to 57600 baud. + Major code reorganization - there are now 4 classes instead of 1. This should remove a lot of the #ifdef...#else...#endif constructs and hopefully make the code easier to read. Including the class in your project is still done by including QwSerialPort.h and instantiating a QwSerialPort object. * The serial port associated with a QwSerialPort object is no longer opened on construction, or upon calling the setNumber() function. You must now explicitly call open() to open the port. Version 0.2 (Jan. 3, 2001): + Added lastError() function with rudimentary error codes + Better documentation + Added ability to examine the empty/not empty state of a port's input buffer with atEnd() + Added ability to retrieve the number of bytes in a port's input buffer with size() (thanks to Olivier Tubach) + Added ability to turn off portability warnings by defining TTY_NOWARN_PORT in your project + Added ability to turn off all warning messages by defining TTY_NOWARN in your project + Added ability to select POSIX serial functions in Windows NT/2000 by defining TTY_POSIX in your project (untested) + Added control over RTS and DTR lines with setRts() and setDtr() respectively + Added ability to query line status using lineStatus(). + Added readLine() functionality (thanks to Olivier Tubach) + Added bytesWaiting(), a non-const/thread-safe version of size() + The class should now be thread-safe through the use of a recursive QMutex (untested) * Fixed a bug that could cause hardware flow control not to work on some POSIX systems * Put in a few missing fileno() calls in the POSIX code * Fixed a few syntax errors that caused compilation to fail on POSIX systems - BAUD0 is no longer a valid baud rate setting - to drop the DTR line, call setDtr(FALSE) Version 0.1 (Dec. 11, 2000): Initial public release. rtimulib-7.2.1/RTHost/RTSerialPort/LICENSE000066400000000000000000000072421254201074400200760ustar00rootroot00000000000000From QextSerialPort 1.2-beta on, we use MIT license for QextSerialPort project. == License == Copyright (c) 2000-2003 Wayne Roth Copyright (c) 2004-2007 Stefan Sander Copyright (c) 2007 Michal Policht Copyright (c) 2008 Brandon Fosdick Copyright (c) 2009-2010 Liam Staskawicz Copyright (c) 2011 Debao Zhang Web: http://code.google.com/p/qextserialport/ Permission 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. == Why license needed? == Many users complains that, without a proper licence they can not use this library. * http://groups.google.com/group/qextserialport/browse_thread/thread/0e8756920b01da82 Hi, we are considering using a modified version of QExtSerialPort in one of our projects (Qt Creator, http://qt.gitorious.org/qt-creator). Would it be possible to add license header information or a license file to the QExtSerialPort code base? - This would make re-use of the code base easier. If that is not possible, could we redistribute the source code with BSD- license headers manually added? And I am also considering packaging the software for Debian, but I couldn't do it yet just because of the license. * http://code.google.com/p/qextserialport/issues/detail?id=8 Questions: Can I use qextserialport in a commercial product? If yes, how? Compile it in? I guess no. If I can use it as a library, how should the README be formulated? Is the "MIT license" from 2008 appropriate? == Why can we use MIT? == Form the history of [http://lists.trolltech.com/qt-interest/2004-12/msg01022.html qt-interest mail list] * Wayne Roth, the original author of the project, had said that: the code is in the public domain. Do whatever you like with it. Right now I have too many other things to do to put any serious time into fixing it. Trolltech should be aware of this already; they asked about a license when they offered to host the tarball. * Stefan Sander, the maintainer of qextserialport on sourceforge, said that Hello, My project registration at !SourceForge have been approved. http://www.sf.net/projects/qextserialport I thought an initial licence of Public Domain would be best solution. Someone wrote: - Because its public domain, some could fork it under different licenses - And from [http://groups.google.com/group/qextserialport/browse_thread/thread/fbcddbfb4a0b5a51?pli=1 this thread] on qesp mail list, we can see that, current maintainers and users agree with a MIT licence. * Brandon Fosdick, I would vote for BSD or MIT :) * Liam Staskawicz, That works for me - let's call it MIT and go for it :) rtimulib-7.2.1/RTHost/RTSerialPort/Qextserialport.sln000066400000000000000000000015521254201074400226330ustar00rootroot00000000000000 Microsoft Visual Studio Solution File, Format Version 11.00 # Visual Studio 2010 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Qextserialport", "Qextserialport.vcxproj", "{FD99181C-B9C8-46FA-A071-F0ABEFFADF75}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 Release|Win32 = Release|Win32 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {FD99181C-B9C8-46FA-A071-F0ABEFFADF75}.Debug|Win32.ActiveCfg = Debug|Win32 {FD99181C-B9C8-46FA-A071-F0ABEFFADF75}.Debug|Win32.Build.0 = Debug|Win32 {FD99181C-B9C8-46FA-A071-F0ABEFFADF75}.Release|Win32.ActiveCfg = Release|Win32 {FD99181C-B9C8-46FA-A071-F0ABEFFADF75}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection EndGlobal rtimulib-7.2.1/RTHost/RTSerialPort/Qextserialport.vcxproj000066400000000000000000000300671254201074400235350ustar00rootroot00000000000000 Debug Win32 Release Win32 true true true true true true true true $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing qextserialenumerator.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" -DUNICODE -DWIN32 -DQT_LARGEFILE_SUPPORT -DQT_CORE_LIB -DQEXTSERIALPORT_LIB -D_WINDLL "-I." "-I.\GeneratedFiles" "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I." "-I." "-I." "-I." "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing qextserialenumerator.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" -DUNICODE -DWIN32 -DQT_LARGEFILE_SUPPORT -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQEXTSERIALPORT_LIB -D_WINDLL "-I." "-I.\GeneratedFiles" "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I." "-I." "-I." "-I." "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing qextwineventnotifier_p.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" -DUNICODE -DWIN32 -DQT_LARGEFILE_SUPPORT -DQT_CORE_LIB -DQEXTSERIALPORT_LIB -D_WINDLL "-I." "-I.\GeneratedFiles" "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I." "-I." "-I." "-I." "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing qextwineventnotifier_p.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" -DUNICODE -DWIN32 -DQT_LARGEFILE_SUPPORT -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQEXTSERIALPORT_LIB -D_WINDLL "-I." "-I.\GeneratedFiles" "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I." "-I." "-I." "-I." "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing qextserialport.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" -DUNICODE -DWIN32 -DQT_LARGEFILE_SUPPORT -DQT_CORE_LIB -DQEXTSERIALPORT_LIB -D_WINDLL "-I." "-I.\GeneratedFiles" "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I." "-I." "-I." "-I." "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing qextserialport.h... .\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" -DUNICODE -DWIN32 -DQT_LARGEFILE_SUPPORT -DQT_NO_DEBUG -DNDEBUG -DQT_CORE_LIB -DQEXTSERIALPORT_LIB -D_WINDLL "-I." "-I.\GeneratedFiles" "-I$(QTDIR)\include" "-I.\GeneratedFiles\$(ConfigurationName)\." "-I$(QTDIR)\include\QtCore" "-I." "-I." "-I." "-I." "%(FullPath)" -o ".\GeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" {FD99181C-B9C8-46FA-A071-F0ABEFFADF75} Qt4VSv1.0 StaticLibrary StaticLibrary <_ProjectFileVersion>10.0.40219.1 $(SolutionDir)Deploy\ $(SolutionDir)Deploy\ $(Configuration)\ $(Configuration)\ $(ProjectName)d UNICODE;WIN32;QT_LARGEFILE_SUPPORT;QT_CORE_LIB;QEXTSERIALPORT_LIB;%(PreprocessorDefinitions) .\GeneratedFiles;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;.\;%(AdditionalIncludeDirectories) Disabled ProgramDatabase MultiThreadedDebugDLL false Windows $(OutDir)\$(ProjectName)d.dll $(QTDIR)\lib;%(AdditionalLibraryDirectories) true Setupapi.lib;qtmaind.lib;QtCored4.lib;%(AdditionalDependencies) $(SolutionDir)\Deploy\$(ProjectName)d.lib UNICODE;WIN32;QT_LARGEFILE_SUPPORT;QT_NO_DEBUG;NDEBUG;QT_CORE_LIB;QEXTSERIALPORT_LIB;%(PreprocessorDefinitions) .\GeneratedFiles;$(QTDIR)\include;.\GeneratedFiles\$(ConfigurationName);$(QTDIR)\include\QtCore;.\;%(AdditionalIncludeDirectories) MultiThreadedDLL false Windows $(OutDir)\$(ProjectName).dll $(QTDIR)\lib;%(AdditionalLibraryDirectories) false Setupapi.lib;qtmain.lib;QtCore4.lib;%(AdditionalDependencies) rtimulib-7.2.1/RTHost/RTSerialPort/Qextserialport.vcxproj.filters000066400000000000000000000071411254201074400252010ustar00rootroot00000000000000 {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;cxx;c;def {93995380-89BD-4b04-88EB-625FBE52EBFB} h {99349809-55BA-4b9d-BF79-8FDBB0286EB3} ui {D9D6E242-F8AF-46E4-B9FD-80ECBC20BA3E} qrc;* false {71ED8ED8-ACB9-4CE9-BBE1-E00B30144E11} moc;h;cpp False {5a1db9e3-2ded-4ca8-a9df-0e89a54238e9} cpp;moc False {92983d28-a8d9-4bd2-b2b2-472ca7f9b8a4} cpp;moc False Source Files Generated Files\Debug Generated Files\Release Source Files Source Files Generated Files\Debug Generated Files\Release Source Files Source Files Generated Files\Debug Generated Files\Release Header Files Header Files Header Files Header Files Header Files Header Files rtimulib-7.2.1/RTHost/RTSerialPort/README000066400000000000000000000045431254201074400177520ustar00rootroot00000000000000 = About QextSerialPort = QextSerialPort provides an interface to old fashioned serial ports for Qt-based applications. It currently supports Mac OS X, Windows, Linux, FreeBSD. http://code.google.com/p/qextserialport/ == How to use (1) == * Download the source code. Note: [http://mercurial.selenic.com/downloads/ Mercurial] is needed . hg clone https://code.google.com/r/dbzhang800-qextserialport/ * Put the source code in any directory you like. For example, 3rdparty: |-- project.pro |-- .... |-- 3rdparty\ | |-- qextserialport\ | | * Add following line to your qmake project file: include(3rdparty/qextserialport/src/qextserialport.pri) * Using QextSerialPort in your code. Enjoy it! #include "qextserialport.h" .... QextSerialPort * port = new QextSerialPort(); .... == How to use (2) == It's very easy to compile QextSerialPort directly into your application(see above section), however, we would prefer to use it as a static or shared library. * Download the source code, and put it in any location you like. |-- yourpath\ | |-- qextserialport\ | | * Create a config.pri file, and put into qextserialport's directory. |-- yourpath\ | |-- qextserialport\ | | |-- config.pri * Contents of config_example.pri # uncomment the following line if you want to use qextserialport as library # QEXTSERIALPORT_LIBRARY = yes # uncomment the following line too if you want to use it as static library # QEXTSERIALPORT_STATIC = yes * Goto qextserialport/buildlib, and run following command to generate library. qmake make (or nmake) * Add following line to your qmake project file. Enjoy it! include(pathToQextserialport/src/qextserialport.pri) == Build (optional) == * Run qmake from the toplevel directory.(If your has create a config.pri file properly, this will generate the library, and then all examples will use the library. Otherwise, qextserialport will be directly compiled into the examples) qmake (or qmake -r) make (or nmake) * Run qdoc3 from the doc directory. qdoc3 qextserialport.qdocconf rtimulib-7.2.1/RTHost/RTSerialPort/buildlib/000077500000000000000000000000001254201074400206525ustar00rootroot00000000000000rtimulib-7.2.1/RTHost/RTSerialPort/buildlib/buildlib.pro000066400000000000000000000011551254201074400231640ustar00rootroot00000000000000TEMPLATE=lib CONFIG += qt qextserialport-buildlib # Include .pri file before using "qextserialport-static" # and after CONFIG += "qextserialport-buildlib" include(../src/qextserialport.pri) qextserialport-static:CONFIG += static else:CONFIG += dll mac:CONFIG += absolute_library_soname win32|mac:!wince*:!win32-msvc:!macx-xcode:CONFIG += debug_and_release build_all TARGET = $$QEXTSERIALPORT_LIBNAME DESTDIR = $$QEXTSERIALPORT_LIBDIR win32:!qextserialport-static{ DLLDESTDIR = $$[QT_INSTALL_BINS] QMAKE_DISTCLEAN += $$[QT_INSTALL_BINS]\\$${QEXTSERIALPORT_LIBNAME}.dll } target.path = $$DESTDIR INSTALLS += target rtimulib-7.2.1/RTHost/RTSerialPort/buildlib/moc_qextserialenumerator.cpp000066400000000000000000000071211254201074400265000ustar00rootroot00000000000000/**************************************************************************** ** Meta object code from reading C++ file 'qextserialenumerator.h' ** ** Created: Sun Jun 3 19:01:58 2012 ** by: The Qt Meta Object Compiler version 63 (Qt 4.8.1) ** ** WARNING! All changes made in this file will be lost! *****************************************************************************/ #include "../src/qextserialenumerator.h" #if !defined(Q_MOC_OUTPUT_REVISION) #error "The header file 'qextserialenumerator.h' doesn't include ." #elif Q_MOC_OUTPUT_REVISION != 63 #error "This file was generated using the moc from 4.8.1. It" #error "cannot be used with the include files from this version of Qt." #error "(The moc has changed too much.)" #endif QT_BEGIN_MOC_NAMESPACE static const uint qt_meta_data_QextSerialEnumerator[] = { // content: 6, // revision 0, // classname 0, 0, // classinfo 2, 14, // methods 0, 0, // properties 0, 0, // enums/sets 0, 0, // constructors 0, // flags 2, // signalCount // signals: signature, parameters, type, tag, flags 27, 22, 21, 21, 0x05, 58, 22, 21, 21, 0x05, 0 // eod }; static const char qt_meta_stringdata_QextSerialEnumerator[] = { "QextSerialEnumerator\0\0info\0" "deviceDiscovered(QextPortInfo)\0" "deviceRemoved(QextPortInfo)\0" }; void QextSerialEnumerator::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) { if (_c == QMetaObject::InvokeMetaMethod) { Q_ASSERT(staticMetaObject.cast(_o)); QextSerialEnumerator *_t = static_cast(_o); switch (_id) { case 0: _t->deviceDiscovered((*reinterpret_cast< const QextPortInfo(*)>(_a[1]))); break; case 1: _t->deviceRemoved((*reinterpret_cast< const QextPortInfo(*)>(_a[1]))); break; default: ; } } } const QMetaObjectExtraData QextSerialEnumerator::staticMetaObjectExtraData = { 0, qt_static_metacall }; const QMetaObject QextSerialEnumerator::staticMetaObject = { { &QObject::staticMetaObject, qt_meta_stringdata_QextSerialEnumerator, qt_meta_data_QextSerialEnumerator, &staticMetaObjectExtraData } }; #ifdef Q_NO_DATA_RELOCATION const QMetaObject &QextSerialEnumerator::getStaticMetaObject() { return staticMetaObject; } #endif //Q_NO_DATA_RELOCATION const QMetaObject *QextSerialEnumerator::metaObject() const { return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; } void *QextSerialEnumerator::qt_metacast(const char *_clname) { if (!_clname) return 0; if (!strcmp(_clname, qt_meta_stringdata_QextSerialEnumerator)) return static_cast(const_cast< QextSerialEnumerator*>(this)); return QObject::qt_metacast(_clname); } int QextSerialEnumerator::qt_metacall(QMetaObject::Call _c, int _id, void **_a) { _id = QObject::qt_metacall(_c, _id, _a); if (_id < 0) return _id; if (_c == QMetaObject::InvokeMetaMethod) { if (_id < 2) qt_static_metacall(this, _c, _id, _a); _id -= 2; } return _id; } // SIGNAL 0 void QextSerialEnumerator::deviceDiscovered(const QextPortInfo & _t1) { void *_a[] = { 0, const_cast(reinterpret_cast(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 0, _a); } // SIGNAL 1 void QextSerialEnumerator::deviceRemoved(const QextPortInfo & _t1) { void *_a[] = { 0, const_cast(reinterpret_cast(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 1, _a); } QT_END_MOC_NAMESPACE rtimulib-7.2.1/RTHost/RTSerialPort/buildlib/moc_qextserialport.cpp000066400000000000000000000141721254201074400253070ustar00rootroot00000000000000/**************************************************************************** ** Meta object code from reading C++ file 'qextserialport.h' ** ** Created: Sun Jun 3 19:01:56 2012 ** by: The Qt Meta Object Compiler version 63 (Qt 4.8.1) ** ** WARNING! All changes made in this file will be lost! *****************************************************************************/ #include "../src/qextserialport.h" #if !defined(Q_MOC_OUTPUT_REVISION) #error "The header file 'qextserialport.h' doesn't include ." #elif Q_MOC_OUTPUT_REVISION != 63 #error "This file was generated using the moc from 4.8.1. It" #error "cannot be used with the include files from this version of Qt." #error "(The moc has changed too much.)" #endif QT_BEGIN_MOC_NAMESPACE static const uint qt_meta_data_QextSerialPort[] = { // content: 6, // revision 0, // classname 0, 0, // classinfo 14, 14, // methods 2, 84, // properties 1, 90, // enums/sets 0, 0, // constructors 0, // flags 1, // signalCount // signals: signature, parameters, type, tag, flags 23, 16, 15, 15, 0x05, // slots: signature, parameters, type, tag, flags 45, 40, 15, 15, 0x0a, 71, 66, 15, 15, 0x0a, 95, 15, 15, 15, 0x0a, 121, 15, 15, 15, 0x0a, 147, 15, 15, 15, 0x0a, 169, 15, 15, 15, 0x0a, 195, 15, 15, 15, 0x0a, 220, 15, 15, 15, 0x0a, 241, 237, 15, 15, 0x0a, 254, 15, 15, 15, 0x2a, 263, 237, 15, 15, 0x0a, 276, 15, 15, 15, 0x2a, 285, 15, 15, 15, 0x08, // properties: name, type, flags 306, 298, 0x0a095103, 325, 315, 0x0009510b, // enums: name, flags, count, data 315, 0x0, 2, 94, // enum data: key, value 335, uint(QextSerialPort::Polling), 343, uint(QextSerialPort::EventDriven), 0 // eod }; static const char qt_meta_stringdata_QextSerialPort[] = { "QextSerialPort\0\0status\0dsrChanged(bool)\0" "name\0setPortName(QString)\0mode\0" "setQueryMode(QueryMode)\0" "setBaudRate(BaudRateType)\0" "setDataBits(DataBitsType)\0" "setParity(ParityType)\0setStopBits(StopBitsType)\0" "setFlowControl(FlowType)\0setTimeout(long)\0" "set\0setDtr(bool)\0setDtr()\0setRts(bool)\0" "setRts()\0_q_canRead()\0QString\0portName\0" "QueryMode\0queryMode\0Polling\0EventDriven\0" }; void QextSerialPort::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) { if (_c == QMetaObject::InvokeMetaMethod) { Q_ASSERT(staticMetaObject.cast(_o)); QextSerialPort *_t = static_cast(_o); switch (_id) { case 0: _t->dsrChanged((*reinterpret_cast< bool(*)>(_a[1]))); break; case 1: _t->setPortName((*reinterpret_cast< const QString(*)>(_a[1]))); break; case 2: _t->setQueryMode((*reinterpret_cast< QueryMode(*)>(_a[1]))); break; case 3: _t->setBaudRate((*reinterpret_cast< BaudRateType(*)>(_a[1]))); break; case 4: _t->setDataBits((*reinterpret_cast< DataBitsType(*)>(_a[1]))); break; case 5: _t->setParity((*reinterpret_cast< ParityType(*)>(_a[1]))); break; case 6: _t->setStopBits((*reinterpret_cast< StopBitsType(*)>(_a[1]))); break; case 7: _t->setFlowControl((*reinterpret_cast< FlowType(*)>(_a[1]))); break; case 8: _t->setTimeout((*reinterpret_cast< long(*)>(_a[1]))); break; case 9: _t->setDtr((*reinterpret_cast< bool(*)>(_a[1]))); break; case 10: _t->setDtr(); break; case 11: _t->setRts((*reinterpret_cast< bool(*)>(_a[1]))); break; case 12: _t->setRts(); break; case 13: _t->d_func()->_q_canRead(); break; default: ; } } } const QMetaObjectExtraData QextSerialPort::staticMetaObjectExtraData = { 0, qt_static_metacall }; const QMetaObject QextSerialPort::staticMetaObject = { { &QIODevice::staticMetaObject, qt_meta_stringdata_QextSerialPort, qt_meta_data_QextSerialPort, &staticMetaObjectExtraData } }; #ifdef Q_NO_DATA_RELOCATION const QMetaObject &QextSerialPort::getStaticMetaObject() { return staticMetaObject; } #endif //Q_NO_DATA_RELOCATION const QMetaObject *QextSerialPort::metaObject() const { return QObject::d_ptr->metaObject ? QObject::d_ptr->metaObject : &staticMetaObject; } void *QextSerialPort::qt_metacast(const char *_clname) { if (!_clname) return 0; if (!strcmp(_clname, qt_meta_stringdata_QextSerialPort)) return static_cast(const_cast< QextSerialPort*>(this)); return QIODevice::qt_metacast(_clname); } int QextSerialPort::qt_metacall(QMetaObject::Call _c, int _id, void **_a) { _id = QIODevice::qt_metacall(_c, _id, _a); if (_id < 0) return _id; if (_c == QMetaObject::InvokeMetaMethod) { if (_id < 14) qt_static_metacall(this, _c, _id, _a); _id -= 14; } #ifndef QT_NO_PROPERTIES else if (_c == QMetaObject::ReadProperty) { void *_v = _a[0]; switch (_id) { case 0: *reinterpret_cast< QString*>(_v) = portName(); break; case 1: *reinterpret_cast< QueryMode*>(_v) = queryMode(); break; } _id -= 2; } else if (_c == QMetaObject::WriteProperty) { void *_v = _a[0]; switch (_id) { case 0: setPortName(*reinterpret_cast< QString*>(_v)); break; case 1: setQueryMode(*reinterpret_cast< QueryMode*>(_v)); break; } _id -= 2; } else if (_c == QMetaObject::ResetProperty) { _id -= 2; } else if (_c == QMetaObject::QueryPropertyDesignable) { _id -= 2; } else if (_c == QMetaObject::QueryPropertyScriptable) { _id -= 2; } else if (_c == QMetaObject::QueryPropertyStored) { _id -= 2; } else if (_c == QMetaObject::QueryPropertyEditable) { _id -= 2; } else if (_c == QMetaObject::QueryPropertyUser) { _id -= 2; } #endif // QT_NO_PROPERTIES return _id; } // SIGNAL 0 void QextSerialPort::dsrChanged(bool _t1) { void *_a[] = { 0, const_cast(reinterpret_cast(&_t1)) }; QMetaObject::activate(this, &staticMetaObject, 0, _a); } QT_END_MOC_NAMESPACE rtimulib-7.2.1/RTHost/RTSerialPort/common.pri000066400000000000000000000011651254201074400210730ustar00rootroot00000000000000infile(config.pri, QEXTSERIALPORT_LIBRARY, yes): CONFIG += qextserialport-library qextserialport-library{ infile(config.pri, QEXTSERIALPORT_STATIC, yes): CONFIG += qextserialport-static } # Though maybe you have been fimiliar with "TEMPLATE += fakelib" and "TEMPLATE -= fakelib", # but it don't work when you using "qmake -tp XXX". So I use another variable Here. SAVE_TEMPLATE = $$TEMPLATE TEMPLATE = fakelib QEXTSERIALPORT_LIBNAME = $$qtLibraryTarget(qextserialport-1.2) TEMPLATE = $$SAVE_TEMPLATE QEXTSERIALPORT_LIBDIR = $$PWD/lib unix:qextserialport-library:!qextserialport-buildlib:QMAKE_RPATHDIR += $$QEXTSERIALPORT_LIBDIR rtimulib-7.2.1/RTHost/RTSerialPort/config.pri000066400000000000000000000006651254201074400210540ustar00rootroot00000000000000# This is an example config.pri for building and using qextserialport. # # When using the qextserialport, all you need is to add following line # in your .pro file: # include(pathToQextserialport/src/qextserialport.pri) # # uncomment the following line if you want to use qextserialport as library #QEXTSERIALPORT_LIBRARY = yes # uncomment the following line too if you want to use it as static library #QEXTSERIALPORT_STATIC = yes rtimulib-7.2.1/RTHost/RTSerialPort/src/000077500000000000000000000000001254201074400176535ustar00rootroot00000000000000rtimulib-7.2.1/RTHost/RTSerialPort/src/qextserialenumerator.cpp000066400000000000000000000132211254201074400246410ustar00rootroot00000000000000/**************************************************************************** ** Copyright (c) 2000-2003 Wayne Roth ** Copyright (c) 2004-2007 Stefan Sander ** Copyright (c) 2007 Michal Policht ** Copyright (c) 2008 Brandon Fosdick ** Copyright (c) 2009-2010 Liam Staskawicz ** Copyright (c) 2011 Debao Zhang ** All right reserved. ** Web: http://code.google.com/p/qextserialport/ ** ** Permission 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. ** ****************************************************************************/ #include "qextserialenumerator.h" #include "qextserialenumerator_p.h" #include #include #include QextSerialEnumeratorPrivate::QextSerialEnumeratorPrivate(QextSerialEnumerator *enumrator) :q_ptr(enumrator) { platformSpecificInit(); } QextSerialEnumeratorPrivate::~QextSerialEnumeratorPrivate() { platformSpecificDestruct(); } /*! \class QextPortInfo \brief The QextPortInfo class containing port information. Structure containing port information. \code QString portName; ///< Port name. QString physName; ///< Physical name. QString friendName; ///< Friendly name. QString enumName; ///< Enumerator name. int vendorID; ///< Vendor ID. int productID; ///< Product ID \endcode */ /*! \class QextSerialEnumerator \brief The QextSerialEnumerator class provides list of ports available in the system. \section1 Usage To poll the system for a list of connected devices, simply use getPorts(). Each QextPortInfo structure will populated with information about the corresponding device. \bold Example \code QList ports = QextSerialEnumerator::getPorts(); foreach( QextPortInfo port, ports ) { // inspect port... } \endcode To enable event-driven notification of device connection events, first call setUpNotifications() and then connect to the deviceDiscovered() and deviceRemoved() signals. Event-driven behavior is currently available only on Windows and OS X. \bold Example \code QextSerialEnumerator* enumerator = new QextSerialEnumerator(); connect(enumerator, SIGNAL(deviceDiscovered(const QextPortInfo &)), myClass, SLOT(onDeviceDiscovered(const QextPortInfo &))); connect(enumerator, SIGNAL(deviceRemoved(const QextPortInfo &)), myClass, SLOT(onDeviceRemoved(const QextPortInfo &))); \endcode \section1 Credits Windows implementation is based on Zach Gorman's work from \l {http://www.codeproject.com}{The Code Project} (\l http://www.codeproject.com/system/setupdi.asp). OS X implementation, see \l http://developer.apple.com/documentation/DeviceDrivers/Conceptual/AccessingHardware/AH_Finding_Devices/chapter_4_section_2.html \bold author Michal Policht, Liam Staskawicz */ /*! \fn void QextSerialEnumerator::deviceDiscovered( const QextPortInfo & info ) A new device has been connected to the system. setUpNotifications() must be called first to enable event-driven device notifications. Currently only implemented on Windows and OS X. \a info The device that has been discovered. */ /*! \fn void QextSerialEnumerator::deviceRemoved( const QextPortInfo & info ); A device has been disconnected from the system. setUpNotifications() must be called first to enable event-driven device notifications. Currently only implemented on Windows and OS X. \a info The device that was disconnected. */ /*! Constructs a QextSerialEnumerator object with the given \a parent. */ QextSerialEnumerator::QextSerialEnumerator(QObject *parent) :QObject(parent), d_ptr(new QextSerialEnumeratorPrivate(this)) { if( !QMetaType::isRegistered( QMetaType::type("QextPortInfo") ) ) qRegisterMetaType("QextPortInfo"); } /*! Destructs the QextSerialEnumerator object. */ QextSerialEnumerator::~QextSerialEnumerator( ) { delete d_ptr; } /*! Get list of ports. return list of ports currently available in the system. */ QList QextSerialEnumerator::getPorts() { #if defined(Q_OS_UNIX) && !defined(Q_OS_LINUX) && !defined(Q_OS_MAC) qCritical("Enumeration for POSIX systems (except Linux) is not implemented yet."); #endif return QextSerialEnumeratorPrivate::getPorts_sys(); } /*! Enable event-driven notifications of board discovery/removal. */ void QextSerialEnumerator::setUpNotifications() { #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) qCritical("Notifications for *Nix/FreeBSD are not implemented yet"); #endif Q_D(QextSerialEnumerator); if (!d->setUpNotifications_sys(true)) QESP_WARNING("Setup Notification Failed..."); } rtimulib-7.2.1/RTHost/RTSerialPort/src/qextserialenumerator.h000066400000000000000000000047541254201074400243210ustar00rootroot00000000000000/**************************************************************************** ** Copyright (c) 2000-2003 Wayne Roth ** Copyright (c) 2004-2007 Stefan Sander ** Copyright (c) 2007 Michal Policht ** Copyright (c) 2008 Brandon Fosdick ** Copyright (c) 2009-2010 Liam Staskawicz ** Copyright (c) 2011 Debao Zhang ** All right reserved. ** Web: http://code.google.com/p/qextserialport/ ** ** Permission 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. ** ****************************************************************************/ #ifndef _QEXTSERIALENUMERATOR_H_ #define _QEXTSERIALENUMERATOR_H_ #include #include #include "qextserialport_global.h" struct QextPortInfo { QString portName; ///< Port name. QString physName; ///< Physical name. QString friendName; ///< Friendly name. QString enumName; ///< Enumerator name. int vendorID; ///< Vendor ID. int productID; ///< Product ID }; class QextSerialEnumeratorPrivate; class QEXTSERIALPORT_EXPORT QextSerialEnumerator : public QObject { Q_OBJECT Q_DECLARE_PRIVATE(QextSerialEnumerator) public: QextSerialEnumerator(QObject * parent=0); ~QextSerialEnumerator(); static QList getPorts(); void setUpNotifications(); Q_SIGNALS: void deviceDiscovered(const QextPortInfo & info); void deviceRemoved(const QextPortInfo & info); private: Q_DISABLE_COPY(QextSerialEnumerator) QextSerialEnumeratorPrivate *d_ptr; }; #endif /*_QEXTSERIALENUMERATOR_H_*/ rtimulib-7.2.1/RTHost/RTSerialPort/src/qextserialenumerator_osx.cpp000066400000000000000000000306631254201074400255430ustar00rootroot00000000000000/**************************************************************************** ** Copyright (c) 2000-2003 Wayne Roth ** Copyright (c) 2004-2007 Stefan Sander ** Copyright (c) 2007 Michal Policht ** Copyright (c) 2008 Brandon Fosdick ** Copyright (c) 2009-2010 Liam Staskawicz ** Copyright (c) 2011 Debao Zhang ** All right reserved. ** Web: http://code.google.com/p/qextserialport/ ** ** Permission 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. ** ****************************************************************************/ #include "qextserialenumerator.h" #include "qextserialenumerator_p.h" #include #include #include #include void QextSerialEnumeratorPrivate::platformSpecificInit() { } void QextSerialEnumeratorPrivate::platformSpecificDestruct() { IONotificationPortDestroy( notificationPortRef ); } // static QList QextSerialEnumeratorPrivate::getPorts_sys() { QList infoList; io_iterator_t serialPortIterator = 0; kern_return_t kernResult = KERN_FAILURE; CFMutableDictionaryRef matchingDictionary; // first try to get any serialbsd devices, then try any USBCDC devices if( !(matchingDictionary = IOServiceMatching(kIOSerialBSDServiceValue) ) ) { QESP_WARNING("IOServiceMatching returned a NULL dictionary."); return infoList; } CFDictionaryAddValue(matchingDictionary, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDAllTypes)); // then create the iterator with all the matching devices if( IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &serialPortIterator) != KERN_SUCCESS ) { qCritical() << "IOServiceGetMatchingServices failed, returned" << kernResult; return infoList; } iterateServicesOSX(serialPortIterator, infoList); IOObjectRelease(serialPortIterator); serialPortIterator = 0; if( !(matchingDictionary = IOServiceNameMatching("AppleUSBCDC")) ) { QESP_WARNING("IOServiceNameMatching returned a NULL dictionary."); return infoList; } if( IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDictionary, &serialPortIterator) != KERN_SUCCESS ) { qCritical() << "IOServiceGetMatchingServices failed, returned" << kernResult; return infoList; } iterateServicesOSX(serialPortIterator, infoList); IOObjectRelease(serialPortIterator); return infoList; } void QextSerialEnumeratorPrivate::iterateServicesOSX(io_object_t service, QList & infoList) { // Iterate through all modems found. io_object_t usbService; while( ( usbService = IOIteratorNext(service) ) ) { QextPortInfo info; info.vendorID = 0; info.productID = 0; getServiceDetailsOSX( usbService, &info ); infoList.append(info); } } bool QextSerialEnumeratorPrivate::getServiceDetailsOSX( io_object_t service, QextPortInfo* portInfo ) { bool retval = true; CFTypeRef bsdPathAsCFString = NULL; CFTypeRef productNameAsCFString = NULL; CFTypeRef vendorIdAsCFNumber = NULL; CFTypeRef productIdAsCFNumber = NULL; // check the name of the modem's callout device bsdPathAsCFString = IORegistryEntryCreateCFProperty(service, CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0); // wander up the hierarchy until we find the level that can give us the // vendor/product IDs and the product name, if available io_registry_entry_t parent; kern_return_t kernResult = IORegistryEntryGetParentEntry(service, kIOServicePlane, &parent); while( kernResult == KERN_SUCCESS && !vendorIdAsCFNumber && !productIdAsCFNumber ) { if(!productNameAsCFString) productNameAsCFString = IORegistryEntrySearchCFProperty(parent, kIOServicePlane, CFSTR("Product Name"), kCFAllocatorDefault, 0); vendorIdAsCFNumber = IORegistryEntrySearchCFProperty(parent, kIOServicePlane, CFSTR(kUSBVendorID), kCFAllocatorDefault, 0); productIdAsCFNumber = IORegistryEntrySearchCFProperty(parent, kIOServicePlane, CFSTR(kUSBProductID), kCFAllocatorDefault, 0); io_registry_entry_t oldparent = parent; kernResult = IORegistryEntryGetParentEntry(parent, kIOServicePlane, &parent); IOObjectRelease(oldparent); } io_string_t ioPathName; IORegistryEntryGetPath( service, kIOServicePlane, ioPathName ); portInfo->physName = ioPathName; if( bsdPathAsCFString ) { char path[MAXPATHLEN]; if( CFStringGetCString((CFStringRef)bsdPathAsCFString, path, PATH_MAX, kCFStringEncodingUTF8) ) portInfo->portName = path; CFRelease(bsdPathAsCFString); } if(productNameAsCFString) { char productName[MAXPATHLEN]; if( CFStringGetCString((CFStringRef)productNameAsCFString, productName, PATH_MAX, kCFStringEncodingUTF8) ) portInfo->friendName = productName; CFRelease(productNameAsCFString); } if(vendorIdAsCFNumber) { SInt32 vID; if(CFNumberGetValue((CFNumberRef)vendorIdAsCFNumber, kCFNumberSInt32Type, &vID)) portInfo->vendorID = vID; CFRelease(vendorIdAsCFNumber); } if(productIdAsCFNumber) { SInt32 pID; if(CFNumberGetValue((CFNumberRef)productIdAsCFNumber, kCFNumberSInt32Type, &pID)) portInfo->productID = pID; CFRelease(productIdAsCFNumber); } IOObjectRelease(service); return retval; } // IOKit callbacks registered via setupNotifications() void deviceDiscoveredCallbackOSX( void *ctxt, io_iterator_t serialPortIterator ) { QextSerialEnumeratorPrivate* d = (QextSerialEnumeratorPrivate*)ctxt; io_object_t serialService; while ((serialService = IOIteratorNext(serialPortIterator))) d->onDeviceDiscoveredOSX(serialService); } void deviceTerminatedCallbackOSX( void *ctxt, io_iterator_t serialPortIterator ) { QextSerialEnumeratorPrivate* d = (QextSerialEnumeratorPrivate*)ctxt; io_object_t serialService; while ((serialService = IOIteratorNext(serialPortIterator))) d->onDeviceTerminatedOSX(serialService); } /* A device has been discovered via IOKit. Create a QextPortInfo if possible, and emit the signal indicating that we've found it. */ void QextSerialEnumeratorPrivate::onDeviceDiscoveredOSX( io_object_t service ) { Q_Q(QextSerialEnumerator); QextPortInfo info; info.vendorID = 0; info.productID = 0; if( getServiceDetailsOSX( service, &info ) ) Q_EMIT q->deviceDiscovered( info ); } /* Notification via IOKit that a device has been removed. Create a QextPortInfo if possible, and emit the signal indicating that it's gone. */ void QextSerialEnumeratorPrivate::onDeviceTerminatedOSX( io_object_t service ) { Q_Q(QextSerialEnumerator); QextPortInfo info; info.vendorID = 0; info.productID = 0; if( getServiceDetailsOSX( service, &info ) ) Q_EMIT q->deviceRemoved( info ); } /* Create matching dictionaries for the devices we want to get notifications for, and add them to the current run loop. Invoke the callbacks that will be responding to these notifications once to arm them, and discover any devices that are currently connected at the time notifications are setup. */ bool QextSerialEnumeratorPrivate::setUpNotifications_sys(bool setup) { kern_return_t kernResult; mach_port_t masterPort; CFRunLoopSourceRef notificationRunLoopSource; CFMutableDictionaryRef classesToMatch; CFMutableDictionaryRef cdcClassesToMatch; io_iterator_t portIterator; kernResult = IOMasterPort(MACH_PORT_NULL, &masterPort); if (KERN_SUCCESS != kernResult) { qDebug() << "IOMasterPort returned:" << kernResult; return false; } classesToMatch = IOServiceMatching(kIOSerialBSDServiceValue); if (classesToMatch == NULL) qDebug("IOServiceMatching returned a NULL dictionary."); else CFDictionarySetValue(classesToMatch, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDAllTypes)); if( !(cdcClassesToMatch = IOServiceNameMatching("AppleUSBCDC") ) ) { QESP_WARNING("couldn't create cdc matching dict"); return false; } // Retain an additional reference since each call to IOServiceAddMatchingNotification consumes one. classesToMatch = (CFMutableDictionaryRef) CFRetain(classesToMatch); cdcClassesToMatch = (CFMutableDictionaryRef) CFRetain(cdcClassesToMatch); notificationPortRef = IONotificationPortCreate(masterPort); if(notificationPortRef == NULL) { qDebug("IONotificationPortCreate return a NULL IONotificationPortRef."); return false; } notificationRunLoopSource = IONotificationPortGetRunLoopSource(notificationPortRef); if (notificationRunLoopSource == NULL) { qDebug("IONotificationPortGetRunLoopSource returned NULL CFRunLoopSourceRef."); return false; } CFRunLoopAddSource(CFRunLoopGetCurrent(), notificationRunLoopSource, kCFRunLoopDefaultMode); kernResult = IOServiceAddMatchingNotification(notificationPortRef, kIOMatchedNotification, classesToMatch, deviceDiscoveredCallbackOSX, this, &portIterator); if (kernResult != KERN_SUCCESS) { qDebug() << "IOServiceAddMatchingNotification return:" << kernResult; return false; } // arm the callback, and grab any devices that are already connected deviceDiscoveredCallbackOSX( this, portIterator ); kernResult = IOServiceAddMatchingNotification(notificationPortRef, kIOMatchedNotification, cdcClassesToMatch, deviceDiscoveredCallbackOSX, this, &portIterator); if (kernResult != KERN_SUCCESS) { qDebug() << "IOServiceAddMatchingNotification return:" << kernResult; return false; } // arm the callback, and grab any devices that are already connected deviceDiscoveredCallbackOSX( this, portIterator ); kernResult = IOServiceAddMatchingNotification(notificationPortRef, kIOTerminatedNotification, classesToMatch, deviceTerminatedCallbackOSX, this, &portIterator); if (kernResult != KERN_SUCCESS) { qDebug() << "IOServiceAddMatchingNotification return:" << kernResult; return false; } // arm the callback, and clear any devices that are terminated deviceTerminatedCallbackOSX( this, portIterator ); kernResult = IOServiceAddMatchingNotification(notificationPortRef, kIOTerminatedNotification, cdcClassesToMatch, deviceTerminatedCallbackOSX, this, &portIterator); if (kernResult != KERN_SUCCESS) { qDebug() << "IOServiceAddMatchingNotification return:" << kernResult; return false; } // arm the callback, and clear any devices that are terminated deviceTerminatedCallbackOSX( this, portIterator ); return true; } rtimulib-7.2.1/RTHost/RTSerialPort/src/qextserialenumerator_p.h000066400000000000000000000075161254201074400246370ustar00rootroot00000000000000/**************************************************************************** ** Copyright (c) 2000-2003 Wayne Roth ** Copyright (c) 2004-2007 Stefan Sander ** Copyright (c) 2007 Michal Policht ** Copyright (c) 2008 Brandon Fosdick ** Copyright (c) 2009-2010 Liam Staskawicz ** Copyright (c) 2011 Debao Zhang ** All right reserved. ** Web: http://code.google.com/p/qextserialport/ ** ** Permission 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. ** ****************************************************************************/ #ifndef _QEXTSERIALENUMERATOR_P_H_ #define _QEXTSERIALENUMERATOR_P_H_ // // W A R N I N G // ------------- // // This file is not part of the QESP API. It exists for the convenience // of other QESP classes. This header file may change from version to // version without notice, or even be removed. // // We mean it. // #include "qextserialenumerator.h" #ifdef Q_OS_WIN // needed for mingw to pull in appropriate dbt business... // probably a better way to do this // http://mingw-users.1079350.n2.nabble.com/DEV-BROADCAST-DEVICEINTERFACE-was-not-declared-in-this-scope-td3552762.html # ifdef __MINGW32__ # define _WIN32_WINNT 0x0500 # define _WIN32_WINDOWS 0x0500 # define WINVER 0x0500 # endif # include #endif /*Q_OS_WIN*/ #ifdef Q_OS_MAC # include #endif /*Q_OS_MAC*/ #if (defined(QT_GUI_LIB) && QT_VERSION < QT_VERSION_CHECK(5, 0, 0)) || defined(QT_WIDGETS_LIB) # define HAS_QWIDGET #endif class QextSerialRegistrationWidget; class QextSerialEnumeratorPrivate { Q_DECLARE_PUBLIC(QextSerialEnumerator) public: QextSerialEnumeratorPrivate(QextSerialEnumerator * enumrator); ~QextSerialEnumeratorPrivate(); void platformSpecificInit(); void platformSpecificDestruct(); static QList getPorts_sys(); bool setUpNotifications_sys(bool setup); #ifdef Q_OS_WIN LRESULT onDeviceChanged( WPARAM wParam, LPARAM lParam ); bool matchAndDispatchChangedDevice(const QString & deviceID, const GUID & guid, WPARAM wParam); # ifdef HAS_QWIDGET QextSerialRegistrationWidget* notificationWidget; # endif #endif /*Q_OS_WIN*/ #ifdef Q_OS_MAC /*! * Search for serial ports using IOKit. * \param infoList list with result. */ static void iterateServicesOSX(io_object_t service, QList & infoList); static bool getServiceDetailsOSX( io_object_t service, QextPortInfo* portInfo ); void onDeviceDiscoveredOSX( io_object_t service ); void onDeviceTerminatedOSX( io_object_t service ); friend void deviceDiscoveredCallbackOSX( void *ctxt, io_iterator_t serialPortIterator ); friend void deviceTerminatedCallbackOSX( void *ctxt, io_iterator_t serialPortIterator ); IONotificationPortRef notificationPortRef; #endif // Q_OS_MAC private: QextSerialEnumerator * q_ptr; }; #endif //_QEXTSERIALENUMERATOR_P_H_ rtimulib-7.2.1/RTHost/RTSerialPort/src/qextserialenumerator_unix.cpp000066400000000000000000000074411254201074400257130ustar00rootroot00000000000000/**************************************************************************** ** Copyright (c) 2000-2003 Wayne Roth ** Copyright (c) 2004-2007 Stefan Sander ** Copyright (c) 2007 Michal Policht ** Copyright (c) 2008 Brandon Fosdick ** Copyright (c) 2009-2010 Liam Staskawicz ** Copyright (c) 2011 Debao Zhang ** All right reserved. ** Web: http://code.google.com/p/qextserialport/ ** ** Permission 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. ** ****************************************************************************/ #include "qextserialenumerator.h" #include "qextserialenumerator_p.h" #include #include #include void QextSerialEnumeratorPrivate::platformSpecificInit() { } void QextSerialEnumeratorPrivate::platformSpecificDestruct() { } QList QextSerialEnumeratorPrivate::getPorts_sys() { QList infoList; #ifdef Q_OS_LINUX QStringList portNamePrefixes, portNameList; portNamePrefixes << QLatin1String("ttyS*"); // list normal serial ports first QDir dir(QLatin1String("/dev")); portNameList = dir.entryList(portNamePrefixes, (QDir::System | QDir::Files), QDir::Name); // remove the values which are not serial ports for e.g. /dev/ttysa for (int i = 0; i < portNameList.size(); i++) { bool ok; QString current = portNameList.at(i); // remove the ttyS part, and check, if the other part is a number current.remove(0,4).toInt(&ok, 10); if (!ok) { portNameList.removeAt(i); i--; } } // get the non standard serial ports names // (USB-serial, bluetooth-serial, 18F PICs, and so on) // if you know an other name prefix for serial ports please let us know portNamePrefixes.clear(); portNamePrefixes << QLatin1String("ttyACM*") << QLatin1String("ttyUSB*") << QLatin1String("rfcomm*"); portNameList += dir.entryList(portNamePrefixes, (QDir::System | QDir::Files), QDir::Name); foreach (QString str , portNameList) { QextPortInfo inf; inf.physName = QLatin1String("/dev/")+str; inf.portName = str; if (str.contains(QLatin1String("ttyS"))) { inf.friendName = QLatin1String("Serial port ")+str.remove(0, 4); } else if (str.contains(QLatin1String("ttyUSB"))) { inf.friendName = QLatin1String("USB-serial adapter ")+str.remove(0, 6); } else if (str.contains(QLatin1String("rfcomm"))) { inf.friendName = QLatin1String("Bluetooth-serial adapter ")+str.remove(0, 6); } inf.enumName = QLatin1String("/dev"); // is there a more helpful name for this? infoList.append(inf); } #endif return infoList; } bool QextSerialEnumeratorPrivate::setUpNotifications_sys(bool setup) { Q_UNUSED(setup) return false; } rtimulib-7.2.1/RTHost/RTSerialPort/src/qextserialenumerator_win.cpp000066400000000000000000000246571254201074400255350ustar00rootroot00000000000000/**************************************************************************** ** Copyright (c) 2000-2003 Wayne Roth ** Copyright (c) 2004-2007 Stefan Sander ** Copyright (c) 2007 Michal Policht ** Copyright (c) 2008 Brandon Fosdick ** Copyright (c) 2009-2010 Liam Staskawicz ** Copyright (c) 2011 Debao Zhang ** All right reserved. ** Web: http://code.google.com/p/qextserialport/ ** ** Permission 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. ** ****************************************************************************/ #include "qextserialenumerator.h" #include "qextserialenumerator_p.h" #include #include #include #include #include #include #include #include "qextserialport.h" #ifdef HAS_QWIDGET #include class QextSerialRegistrationWidget : public QWidget { public: QextSerialRegistrationWidget(QextSerialEnumeratorPrivate* qese) { this->qese = qese; } ~QextSerialRegistrationWidget() {} protected: bool winEvent( MSG* message, long* result ) { if ( message->message == WM_DEVICECHANGE ) { qese->onDeviceChanged(message->wParam, message->lParam ); *result = 1; return true; } return false; } private: QextSerialEnumeratorPrivate* qese; }; #endif // HAS_QWIDGET void QextSerialEnumeratorPrivate::platformSpecificInit() { #ifdef HAS_QWIDGET notificationWidget = 0; #endif // HAS_QWIDGET } /*! default */ void QextSerialEnumeratorPrivate::platformSpecificDestruct() { #ifdef HAS_QWIDGET if( notificationWidget ) delete notificationWidget; #endif } // ### This url has broken, anyone can fix it? // see http://msdn.microsoft.com/en-us/library/ms791134.aspx for list of GUID classes #ifndef GUID_DEVCLASS_PORTS DEFINE_GUID(GUID_DEVCLASS_PORTS, 0x4D36E978, 0xE325, 0x11CE, 0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18 ); #endif /* Gordon Schumacher's macros for TCHAR -> QString conversions and vice versa */ #ifdef UNICODE #define QStringToTCHAR(x) (wchar_t*) x.utf16() #define PQStringToTCHAR(x) (wchar_t*) x->utf16() #define TCHARToQString(x) QString::fromUtf16((ushort*)(x)) #define TCHARToQStringN(x,y) QString::fromUtf16((ushort*)(x),(y)) #else #define QStringToTCHAR(x) x.local8Bit().constData() #define PQStringToTCHAR(x) x->local8Bit().constData() #define TCHARToQString(x) QString::fromLocal8Bit((char*)(x)) #define TCHARToQStringN(x,y) QString::fromLocal8Bit((char*)(x),(y)) #endif /*UNICODE*/ /*! \internal Get value of specified property from the registry. \a key handle to an open key. \a property property name. return property value. */ static QString getRegKeyValue(HKEY key, LPCTSTR property) { DWORD size = 0; DWORD type; ::RegQueryValueEx(key, property, NULL, NULL, NULL, & size); BYTE* buff = new BYTE[size]; QString result; if(::RegQueryValueEx(key, property, NULL, &type, buff, & size) == ERROR_SUCCESS ) result = TCHARToQString(buff); ::RegCloseKey(key); delete [] buff; return result; } /*! \internal Get specific property from registry. \a devInfo pointer to the device information set that contains the interface and its underlying device. Returned by SetupDiGetClassDevs() function. \a devData pointer to an SP_DEVINFO_DATA structure that defines the device instance. this is returned by SetupDiGetDeviceInterfaceDetail() function. \a property registry property. One of defined SPDRP_* constants. return property string. */ static QString getDeviceProperty(HDEVINFO devInfo, PSP_DEVINFO_DATA devData, DWORD property) { DWORD buffSize = 0; ::SetupDiGetDeviceRegistryProperty(devInfo, devData, property, NULL, NULL, 0, & buffSize); BYTE* buff = new BYTE[buffSize]; ::SetupDiGetDeviceRegistryProperty(devInfo, devData, property, NULL, buff, buffSize, NULL); QString result = TCHARToQString(buff); delete [] buff; return result; } /*! \internal */ static bool getDeviceDetailsWin( QextPortInfo* portInfo, HDEVINFO devInfo, PSP_DEVINFO_DATA devData , WPARAM wParam = DBT_DEVICEARRIVAL) { portInfo->friendName = getDeviceProperty(devInfo, devData, SPDRP_FRIENDLYNAME); if( wParam == DBT_DEVICEARRIVAL) portInfo->physName = getDeviceProperty(devInfo, devData, SPDRP_PHYSICAL_DEVICE_OBJECT_NAME); portInfo->enumName = getDeviceProperty(devInfo, devData, SPDRP_ENUMERATOR_NAME); QString hardwareIDs = getDeviceProperty(devInfo, devData, SPDRP_HARDWAREID); HKEY devKey = ::SetupDiOpenDevRegKey(devInfo, devData, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_QUERY_VALUE); portInfo->portName = getRegKeyValue(devKey, TEXT("PortName")); QRegExp idRx(QLatin1String("VID_(\\w+)&PID_(\\w+)")); if(hardwareIDs.toUpper().contains(idRx)) { bool dummy; portInfo->vendorID = idRx.cap(1).toInt(&dummy, 16); portInfo->productID = idRx.cap(2).toInt(&dummy, 16); //qDebug() << "got vid:" << vid << "pid:" << pid; } return true; } /*! \internal */ static void enumerateDevicesWin( const GUID & guid, QList* infoList ) { HDEVINFO devInfo; if( (devInfo = ::SetupDiGetClassDevs(&guid, NULL, NULL, DIGCF_PRESENT)) != INVALID_HANDLE_VALUE) { SP_DEVINFO_DATA devInfoData; devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); for(int i = 0; ::SetupDiEnumDeviceInfo(devInfo, i, &devInfoData); i++) { QextPortInfo info; info.productID = info.vendorID = 0; getDeviceDetailsWin( &info, devInfo, &devInfoData ); infoList->append(info); } ::SetupDiDestroyDeviceInfoList(devInfo); } } static bool lessThan(const QextPortInfo &s1, const QextPortInfo &s2) { if (s1.portName.startsWith(QLatin1String("COM")) && s2.portName.startsWith(QLatin1String("COM"))) { return s1.portName.mid(3).toInt() QextSerialEnumeratorPrivate::getPorts_sys() { QList ports; enumerateDevicesWin(GUID_DEVCLASS_PORTS, &ports); qSort(ports.begin(), ports.end(), lessThan); return ports; } /* Enable event-driven notifications of board discovery/removal. */ bool QextSerialEnumeratorPrivate::setUpNotifications_sys(bool setup) { #ifndef HAS_QWIDGET Q_UNUSED(setup) QESP_WARNING("QextSerialEnumerator: GUI not enabled - can't register for device notifications."); return false; #else Q_Q(QextSerialEnumerator); if(setup && notificationWidget) //already setup return true; notificationWidget = new QextSerialRegistrationWidget(this); DEV_BROADCAST_DEVICEINTERFACE dbh; ::ZeroMemory(&dbh, sizeof(dbh)); dbh.dbcc_size = sizeof(dbh); dbh.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; ::CopyMemory(&dbh.dbcc_classguid, &GUID_DEVCLASS_PORTS, sizeof(GUID)); if(::RegisterDeviceNotification((HWND)notificationWidget->winId(), &dbh, DEVICE_NOTIFY_WINDOW_HANDLE ) == NULL) { QESP_WARNING() << "RegisterDeviceNotification failed:" << GetLastError(); return false; } // setting up notifications doesn't tell us about devices already connected // so get those manually foreach(QextPortInfo port, getPorts_sys()) Q_EMIT q->deviceDiscovered(port); return true; #endif } LRESULT QextSerialEnumeratorPrivate::onDeviceChanged( WPARAM wParam, LPARAM lParam ) { if (DBT_DEVICEARRIVAL == wParam || DBT_DEVICEREMOVECOMPLETE == wParam ) { PDEV_BROADCAST_HDR pHdr = (PDEV_BROADCAST_HDR)lParam; if(pHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE ) { PDEV_BROADCAST_DEVICEINTERFACE pDevInf = (PDEV_BROADCAST_DEVICEINTERFACE)pHdr; // delimiters are different across APIs...change to backslash. ugh. QString deviceID = TCHARToQString(pDevInf->dbcc_name).toUpper().replace(QLatin1String("#"), QLatin1String("\\")); matchAndDispatchChangedDevice(deviceID, GUID_DEVCLASS_PORTS, wParam); } } return 0; } bool QextSerialEnumeratorPrivate::matchAndDispatchChangedDevice(const QString & deviceID, const GUID & guid, WPARAM wParam) { Q_Q(QextSerialEnumerator); bool rv = false; DWORD dwFlag = (DBT_DEVICEARRIVAL == wParam) ? DIGCF_PRESENT : DIGCF_ALLCLASSES; HDEVINFO devInfo; if( (devInfo = SetupDiGetClassDevs(&guid,NULL,NULL,dwFlag)) != INVALID_HANDLE_VALUE ) { SP_DEVINFO_DATA spDevInfoData; spDevInfoData.cbSize = sizeof(SP_DEVINFO_DATA); for(int i=0; SetupDiEnumDeviceInfo(devInfo, i, &spDevInfoData); i++) { DWORD nSize=0 ; TCHAR buf[MAX_PATH]; if ( SetupDiGetDeviceInstanceId(devInfo, &spDevInfoData, buf, MAX_PATH, &nSize) && deviceID.contains(TCHARToQString(buf))) { // we found a match rv = true; QextPortInfo info; info.productID = info.vendorID = 0; getDeviceDetailsWin( &info, devInfo, &spDevInfoData, wParam ); if( wParam == DBT_DEVICEARRIVAL ) Q_EMIT q->deviceDiscovered(info); else if( wParam == DBT_DEVICEREMOVECOMPLETE ) Q_EMIT q->deviceRemoved(info); break; } } SetupDiDestroyDeviceInfoList(devInfo); } return rv; } rtimulib-7.2.1/RTHost/RTSerialPort/src/qextserialport.cpp000066400000000000000000000757651254201074400234710ustar00rootroot00000000000000/**************************************************************************** ** Copyright (c) 2000-2003 Wayne Roth ** Copyright (c) 2004-2007 Stefan Sander ** Copyright (c) 2007 Michal Policht ** Copyright (c) 2008 Brandon Fosdick ** Copyright (c) 2009-2010 Liam Staskawicz ** Copyright (c) 2011 Debao Zhang ** All right reserved. ** Web: http://code.google.com/p/qextserialport/ ** ** Permission 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. ** ****************************************************************************/ #include "qextserialport.h" #include "qextserialport_p.h" #include #include #include #include /*! \enum BaudRateType baud rate values support. */ /*! \enum DataBitsType */ /*! \enum ParityType */ /*! \enum StopBitsType \value STOP_1 \value STOP_1_5 \value STOP_2 */ /*! \enum FlowType */ /*! \class PortSettings \brief The PortSettings class contain port settings Structure to contain port settings. \code BaudRateType BaudRate; DataBitsType DataBits; ParityType Parity; StopBitsType StopBits; FlowType FlowControl; long Timeout_Millisec; \endcode */ QextSerialPortPrivate::QextSerialPortPrivate(QextSerialPort *q) :lock(QReadWriteLock::Recursive), q_ptr(q) { lastErr = E_NO_ERROR; Settings.BaudRate = BAUD9600; Settings.Parity = PAR_NONE; Settings.FlowControl = FLOW_OFF; Settings.DataBits = DATA_8; Settings.StopBits = STOP_1; Settings.Timeout_Millisec = 10; settingsDirtyFlags = DFE_ALL; platformSpecificInit(); } QextSerialPortPrivate::~QextSerialPortPrivate() { platformSpecificDestruct(); } void QextSerialPortPrivate::setBaudRate(BaudRateType baudRate, bool update) { switch (baudRate) { #ifdef Q_OS_WIN //Windows Special case BAUD14400: case BAUD56000: case BAUD128000: case BAUD256000: QESP_PORTABILITY_WARNING()<<"QextSerialPort Portability Warning: POSIX does not support baudRate:"<isOpen()) updatePortSettings(); break; default: QESP_WARNING()<<"QextSerialPort does not support baudRate:"<isOpen()) updatePortSettings(); } void QextSerialPortPrivate::setDataBits(DataBitsType dataBits, bool update) { switch(dataBits) { case DATA_5: if (Settings.StopBits==STOP_2) { QESP_WARNING("QextSerialPort: 5 Data bits cannot be used with 2 stop bits."); } else { Settings.DataBits=dataBits; settingsDirtyFlags |= DFE_DataBits; } break; case DATA_6: #ifdef Q_OS_WIN if (Settings.StopBits==STOP_1_5) { QESP_WARNING("QextSerialPort: 6 Data bits cannot be used with 1.5 stop bits."); } else #endif { Settings.DataBits=dataBits; settingsDirtyFlags |= DFE_DataBits; } break; case DATA_7: #ifdef Q_OS_WIN if (Settings.StopBits==STOP_1_5) { QESP_WARNING("QextSerialPort: 7 Data bits cannot be used with 1.5 stop bits."); } else #endif { Settings.DataBits=dataBits; settingsDirtyFlags |= DFE_DataBits; } break; case DATA_8: #ifdef Q_OS_WIN if (Settings.StopBits==STOP_1_5) { QESP_WARNING("QextSerialPort: 8 Data bits cannot be used with 1.5 stop bits."); } else #endif { Settings.DataBits=dataBits; settingsDirtyFlags |= DFE_DataBits; } break; default: QESP_WARNING()<<"QextSerialPort does not support Data bits:"<isOpen()) updatePortSettings(); } void QextSerialPortPrivate::setStopBits(StopBitsType stopBits, bool update) { switch (stopBits) { /*one stop bit*/ case STOP_1: Settings.StopBits = stopBits; settingsDirtyFlags |= DFE_StopBits; break; #ifdef Q_OS_WIN /*1.5 stop bits*/ case STOP_1_5: QESP_PORTABILITY_WARNING("QextSerialPort Portability Warning: 1.5 stop bit operation is not supported by POSIX."); if (Settings.DataBits!=DATA_5) { QESP_WARNING("QextSerialPort: 1.5 stop bits can only be used with 5 data bits"); } else { Settings.StopBits = stopBits; settingsDirtyFlags |= DFE_StopBits; } break; #endif /*two stop bits*/ case STOP_2: if (Settings.DataBits==DATA_5) { QESP_WARNING("QextSerialPort: 2 stop bits cannot be used with 5 data bits"); } else { Settings.StopBits = stopBits; settingsDirtyFlags |= DFE_StopBits; } break; default: QESP_WARNING()<<"QextSerialPort does not support stop bits: "<isOpen()) updatePortSettings(); } void QextSerialPortPrivate::setFlowControl(FlowType flow, bool update) { Settings.FlowControl=flow; settingsDirtyFlags |= DFE_Flow; if (update && q_func()->isOpen()) updatePortSettings(); } void QextSerialPortPrivate::setTimeout(long millisec, bool update) { Settings.Timeout_Millisec = millisec; settingsDirtyFlags |= DFE_TimeOut; if (update && q_func()->isOpen()) updatePortSettings(); } void QextSerialPortPrivate::setPortSettings(const PortSettings &settings, bool update) { setBaudRate(settings.BaudRate, false); setDataBits(settings.DataBits, false); setStopBits(settings.StopBits, false); setParity(settings.Parity, false); setFlowControl(settings.FlowControl, false); setTimeout(settings.Timeout_Millisec, false); settingsDirtyFlags = DFE_ALL; if (update && q_func()->isOpen()) updatePortSettings(); } void QextSerialPortPrivate::_q_canRead() { qint64 maxSize = bytesAvailable_sys(); if (maxSize > 0) { char * writePtr = readBuffer.reserve(size_t(maxSize)); qint64 bytesRead = readData_sys(writePtr, maxSize); if (bytesRead < maxSize) readBuffer.chop(maxSize - bytesRead); Q_Q(QextSerialPort); Q_EMIT q->readyRead(); } } /*! \class QextSerialPort \brief The QextSerialPort class encapsulates a serial port on both POSIX and Windows systems. \section1 Usage QextSerialPort offers both a polling and event driven API. Event driven is typically easier to use, since you never have to worry about checking for new data. \bold Example \code QextSerialPort* port = new QextSerialPort("COM1"); connect(port, SIGNAL(readyRead()), myClass, SLOT(onDataAvailable())); port->open(); void MyClass::onDataAvailable() { int avail = port->bytesAvailable(); if( avail > 0 ) { QByteArray usbdata; usbdata.resize(avail); int read = port->read(usbdata.data(), usbdata.size()); if( read > 0 ) { processNewData(usbdata); } } } \endcode \section1 Compatibility The user will be notified of errors and possible portability conflicts at run-time by default. For example, if a application has used BAUD1800, when it is runing under unix, you will get following message. \code QextSerialPort Portability Warning: Windows does not support baudRate:1800 \endcode This behavior can be turned off by defining macro QESP_NO_WARN (to turn off all warnings) or QESP_NO_PORTABILITY_WARN (to turn off portability warnings) in the project. \bold Author: Stefan Sander, Michal Policht, Brandon Fosdick, Liam Staskawicz, Debao Zhang */ /*! \enum QextSerialPort::QueryMode This enum type specifies query mode used in a serial port: \value Polling asynchronously read and write \value EventDriven synchronously read and write */ /*! \fn void QextSerialPort::dsrChanged(bool status) This signal is emitted whenever dsr line has changed its state. You may use this signal to check if device is connected. \a status true when DSR signal is on, false otherwise. */ /*! \fn QueryMode QextSerialPort::queryMode() const Get query mode. */ /*! Default constructor. Note that the name of the device used by a QextSerialPort is dependent on your OS. Possible naming conventions and their associated OS are: \code OS Constant Used By Naming Convention ------------- ------------- ------------------------ Q_OS_WIN Windows COM1, COM2 Q_OS_IRIX SGI/IRIX /dev/ttyf1, /dev/ttyf2 Q_OS_HPUX HP-UX /dev/tty1p0, /dev/tty2p0 Q_OS_SOLARIS SunOS/Slaris /dev/ttya, /dev/ttyb Q_OS_OSF Digital UNIX /dev/tty01, /dev/tty02 Q_OS_FREEBSD FreeBSD /dev/ttyd0, /dev/ttyd1 Q_OS_OPENBSD OpenBSD /dev/tty00, /dev/tty01 Q_OS_LINUX Linux /dev/ttyS0, /dev/ttyS1 /dev/ttyS0, /dev/ttyS1 \endcode This constructor assigns the device name to the name of the first port on the specified system. See the other constructors if you need to open a different port. Default \a mode is EventDriven. As a subclass of QObject, \a parent can be specified. */ QextSerialPort::QextSerialPort(QextSerialPort::QueryMode mode, QObject *parent) : QIODevice(parent), d_ptr(new QextSerialPortPrivate(this)) { #ifdef Q_OS_WIN setPortName(QLatin1String("COM1")); #elif defined(Q_OS_IRIX) setPortName(QLatin1String("/dev/ttyf1")); #elif defined(Q_OS_HPUX) setPortName(QLatin1String("/dev/tty1p0")); #elif defined(Q_OS_SOLARIS) setPortName(QLatin1String("/dev/ttya")); #elif defined(Q_OS_OSF) //formally DIGITAL UNIX setPortName(QLatin1String("/dev/tty01")); #elif defined(Q_OS_FREEBSD) setPortName(QLatin1String("/dev/ttyd1")); #elif defined(Q_OS_OPENBSD) setPortName(QLatin1String("/dev/tty00")); #else setPortName(QLatin1String("/dev/ttyS0")); #endif setQueryMode(mode); } /*! Constructs a serial port attached to the port specified by name. \a name is the name of the device, which is windowsystem-specific, e.g."COM1" or "/dev/ttyS0". \a mode */ QextSerialPort::QextSerialPort(const QString & name, QextSerialPort::QueryMode mode, QObject *parent) : QIODevice(parent), d_ptr(new QextSerialPortPrivate(this)) { setQueryMode(mode); setPortName(name); } /*! Constructs a port with default name and specified \a settings. */ QextSerialPort::QextSerialPort(const PortSettings& settings, QextSerialPort::QueryMode mode, QObject *parent) : QIODevice(parent), d_ptr(new QextSerialPortPrivate(this)) { Q_D(QextSerialPort); setQueryMode(mode); d->setPortSettings(settings); } /*! Constructs a port with specified \a name , \a mode and \a settings. */ QextSerialPort::QextSerialPort(const QString & name, const PortSettings& settings, QextSerialPort::QueryMode mode, QObject *parent) : QIODevice(parent), d_ptr(new QextSerialPortPrivate(this)) { Q_D(QextSerialPort); setPortName(name); setQueryMode(mode); d->setPortSettings(settings); } /*! Opens a serial port and sets its OpenMode to \a mode. Note that this function does not specify which device to open. Returns true if successful; otherwise returns false.This function has no effect if the port associated with the class is already open. The port is also configured to the current settings, as stored in the Settings structure. */ bool QextSerialPort::open(OpenMode mode) { Q_D(QextSerialPort); QWriteLocker locker(&d->lock); if (mode != QIODevice::NotOpen && !isOpen()) d->open_sys(mode); return isOpen(); } /*! \reimp Closes a serial port. This function has no effect if the serial port associated with the class is not currently open. */ void QextSerialPort::close() { Q_D(QextSerialPort); QWriteLocker locker(&d->lock); if (isOpen()) { // Be a good QIODevice and call QIODevice::close() before really close() // so the aboutToClose() signal is emitted at the proper time QIODevice::close(); // mark ourselves as closed d->close_sys(); d->readBuffer.clear(); } } /*! Flushes all pending I/O to the serial port. This function has no effect if the serial port associated with the class is not currently open. */ void QextSerialPort::flush() { Q_D(QextSerialPort); QWriteLocker locker(&d->lock); if (isOpen()) d->flush_sys(); } /*! \reimp Returns the number of bytes waiting in the port's receive queue. This function will return 0 if the port is not currently open, or -1 on error. */ qint64 QextSerialPort::bytesAvailable() const { QWriteLocker locker(&d_func()->lock); if (isOpen()) { qint64 bytes = d_func()->bytesAvailable_sys(); if (bytes != -1) { return bytes + d_func()->readBuffer.size() + QIODevice::bytesAvailable(); } else { return -1; } } return 0; } /*! * Set desired serial communication handling style. You may choose from polling * or event driven approach. This function does nothing when port is open; to * apply changes port must be reopened. * * In event driven approach read() and write() functions are acting * asynchronously. They return immediately and the operation is performed in * the background, so they doesn't freeze the calling thread. * To determine when operation is finished, QextSerialPort runs separate thread * and monitors serial port events. Whenever the event occurs, adequate signal * is emitted. * * When polling is set, read() and write() are acting synchronously. Signals are * not working in this mode and some functions may not be available. The advantage * of polling is that it generates less overhead due to lack of signals emissions * and it doesn't start separate thread to monitor events. * * Generally event driven approach is more capable and friendly, although some * applications may need as low overhead as possible and then polling comes. * * \a mode query mode. */ void QextSerialPort::setQueryMode(QueryMode mode) { Q_D(QextSerialPort); QWriteLocker locker(&d->lock); if (mode != d->_queryMode) { d->_queryMode = mode; } } /*! Sets the \a name of the device associated with the object, e.g. "COM1", or "/dev/ttyS0". */ void QextSerialPort::setPortName(const QString & name) { Q_D(QextSerialPort); QWriteLocker locker(&d->lock); d->port = name; } /*! Returns the name set by setPortName(). */ QString QextSerialPort::portName() const { QReadLocker locker(&d_func()->lock); return d_func()->port; } QextSerialPort::QueryMode QextSerialPort::queryMode() const { QReadLocker locker(&d_func()->lock); return d_func()->_queryMode; } /*! Reads all available data from the device, and returns it as a QByteArray. This function has no way of reporting errors; returning an empty QByteArray() can mean either that no data was currently available for reading, or that an error occurred. */ QByteArray QextSerialPort::readAll() { int avail = this->bytesAvailable(); return (avail > 0) ? this->read(avail) : QByteArray(); } /*! Returns the baud rate of the serial port. For a list of possible return values see the definition of the enum BaudRateType. */ BaudRateType QextSerialPort::baudRate() const { QReadLocker locker(&d_func()->lock); return d_func()->Settings.BaudRate; } /*! Returns the number of data bits used by the port. For a list of possible values returned by this function, see the definition of the enum DataBitsType. */ DataBitsType QextSerialPort::dataBits() const { QReadLocker locker(&d_func()->lock); return d_func()->Settings.DataBits; } /*! Returns the type of parity used by the port. For a list of possible values returned by this function, see the definition of the enum ParityType. */ ParityType QextSerialPort::parity() const { QReadLocker locker(&d_func()->lock); return d_func()->Settings.Parity; } /*! Returns the number of stop bits used by the port. For a list of possible return values, see the definition of the enum StopBitsType. */ StopBitsType QextSerialPort::stopBits() const { QReadLocker locker(&d_func()->lock); return d_func()->Settings.StopBits; } /*! Returns the type of flow control used by the port. For a list of possible values returned by this function, see the definition of the enum FlowType. */ FlowType QextSerialPort::flowControl() const { QReadLocker locker(&d_func()->lock); return d_func()->Settings.FlowControl; } /*! Returns true if device is sequential, otherwise returns false. Serial port is sequential device so this function always returns true. Check QIODevice::isSequential() documentation for more information. */ bool QextSerialPort::isSequential() const { return true; } /*! Return the error number, or 0 if no error occurred. */ ulong QextSerialPort::lastError() const { QReadLocker locker(&d_func()->lock); return d_func()->lastErr; } /*! Returns the line status as stored by the port function. This function will retrieve the states of the following lines: DCD, CTS, DSR, and RI. On POSIX systems, the following additional lines can be monitored: DTR, RTS, Secondary TXD, and Secondary RXD. The value returned is an unsigned long with specific bits indicating which lines are high. The following constants should be used to examine the states of individual lines: \code Mask Line ------ ---- LS_CTS CTS LS_DSR DSR LS_DCD DCD LS_RI RI LS_RTS RTS (POSIX only) LS_DTR DTR (POSIX only) LS_ST Secondary TXD (POSIX only) LS_SR Secondary RXD (POSIX only) \endcode This function will return 0 if the port associated with the class is not currently open. */ unsigned long QextSerialPort::lineStatus() { Q_D(QextSerialPort); QWriteLocker locker(&d->lock); if (isOpen()) return d->lineStatus_sys(); return 0; } /*! Returns a human-readable description of the last device error that occurred. */ QString QextSerialPort::errorString() { Q_D(QextSerialPort); QReadLocker locker(&d->lock); switch(d->lastErr) { case E_NO_ERROR: return tr("No Error has occurred"); case E_INVALID_FD: return tr("Invalid file descriptor (port was not opened correctly)"); case E_NO_MEMORY: return tr("Unable to allocate memory tables (POSIX)"); case E_CAUGHT_NON_BLOCKED_SIGNAL: return tr("Caught a non-blocked signal (POSIX)"); case E_PORT_TIMEOUT: return tr("Operation timed out (POSIX)"); case E_INVALID_DEVICE: return tr("The file opened by the port is not a valid device"); case E_BREAK_CONDITION: return tr("The port detected a break condition"); case E_FRAMING_ERROR: return tr("The port detected a framing error (usually caused by incorrect baud rate settings)"); case E_IO_ERROR: return tr("There was an I/O error while communicating with the port"); case E_BUFFER_OVERRUN: return tr("Character buffer overrun"); case E_RECEIVE_OVERFLOW: return tr("Receive buffer overflow"); case E_RECEIVE_PARITY_ERROR: return tr("The port detected a parity error in the received data"); case E_TRANSMIT_OVERFLOW: return tr("Transmit buffer overflow"); case E_READ_FAILED: return tr("General read operation failure"); case E_WRITE_FAILED: return tr("General write operation failure"); case E_FILE_NOT_FOUND: return tr("The %1 file doesn't exists").arg(this->portName()); case E_PERMISSION_DENIED: return tr("Permission denied"); case E_AGAIN: return tr("Device is already locked"); default: return tr("Unknown error: %1").arg(d->lastErr); } } /*! Destructs the QextSerialPort object. */ QextSerialPort::~QextSerialPort() { if (isOpen()) { close(); } delete d_ptr; } /*! Sets the flow control used by the port to \a flow. Possible values of flow are: \code FLOW_OFF No flow control FLOW_HARDWARE Hardware (RTS/CTS) flow control FLOW_XONXOFF Software (XON/XOFF) flow control \endcode */ void QextSerialPort::setFlowControl(FlowType flow) { Q_D(QextSerialPort); QWriteLocker locker(&d->lock); if (d->Settings.FlowControl != flow) d->setFlowControl(flow, true); } /*! Sets the parity associated with the serial port to \a parity. The possible values of parity are: \code PAR_SPACE Space Parity PAR_MARK Mark Parity PAR_NONE No Parity PAR_EVEN Even Parity PAR_ODD Odd Parity \endcode */ void QextSerialPort::setParity(ParityType parity) { Q_D(QextSerialPort); QWriteLocker locker(&d->lock); if (d->Settings.Parity != parity) d->setParity(parity, true); } /*! Sets the number of data bits used by the serial port to \a dataBits. Possible values of dataBits are: \code DATA_5 5 data bits DATA_6 6 data bits DATA_7 7 data bits DATA_8 8 data bits \endcode \bold note: This function is subject to the following restrictions: \list \o 5 data bits cannot be used with 2 stop bits. \o 1.5 stop bits can only be used with 5 data bits. \o 8 data bits cannot be used with space parity on POSIX systems. \endlist */ void QextSerialPort::setDataBits(DataBitsType dataBits) { Q_D(QextSerialPort); QWriteLocker locker(&d->lock); if (d->Settings.DataBits != dataBits) d->setDataBits(dataBits, true); } /*! Sets the number of stop bits used by the serial port to \a stopBits. Possible values of stopBits are: \code STOP_1 1 stop bit STOP_1_5 1.5 stop bits STOP_2 2 stop bits \endcode \bold note: This function is subject to the following restrictions: \list \o 2 stop bits cannot be used with 5 data bits. \o 1.5 stop bits cannot be used with 6 or more data bits. \o POSIX does not support 1.5 stop bits. \endlist */ void QextSerialPort::setStopBits(StopBitsType stopBits) { Q_D(QextSerialPort); QWriteLocker locker(&d->lock); if (d->Settings.StopBits != stopBits) d->setStopBits(stopBits, true); } /*! Sets the baud rate of the serial port to \a baudRate. Note that not all rates are applicable on all platforms. The following table shows translations of the various baud rate constants on Windows(including NT/2000) and POSIX platforms. Speeds marked with an * are speeds that are usable on both Windows and POSIX. \code RATE Windows Speed POSIX Speed ----------- ------------- ----------- BAUD50 X 50 BAUD75 X 75 *BAUD110 110 110 BAUD134 X 134.5 BAUD150 X 150 BAUD200 X 200 *BAUD300 300 300 *BAUD600 600 600 *BAUD1200 1200 1200 BAUD1800 X 1800 *BAUD2400 2400 2400 *BAUD4800 4800 4800 *BAUD9600 9600 9600 BAUD14400 14400 X *BAUD19200 19200 19200 *BAUD38400 38400 38400 BAUD56000 56000 X *BAUD57600 57600 57600 BAUD76800 X 76800 *BAUD115200 115200 115200 BAUD128000 128000 X BAUD230400 X 230400 BAUD256000 256000 X BAUD460800 X 460800 BAUD500000 X 500000 BAUD576000 X 576000 BAUD921600 X 921600 BAUD1000000 X 1000000 BAUD1152000 X 1152000 BAUD1500000 X 1500000 BAUD2000000 X 2000000 BAUD2500000 X 2500000 BAUD3000000 X 3000000 BAUD3500000 X 3500000 BAUD4000000 X 4000000 \endcode */ void QextSerialPort::setBaudRate(BaudRateType baudRate) { Q_D(QextSerialPort); QWriteLocker locker(&d->lock); if (d->Settings.BaudRate != baudRate) d->setBaudRate(baudRate, true); } /*! For Unix: Sets the read and write timeouts for the port to \a millisec milliseconds. Note that this is a per-character timeout, i.e. the port will wait this long for each individual character, not for the whole read operation. This timeout also applies to the bytesWaiting() function. \bold note: POSIX does not support millisecond-level control for I/O timeout values. Any timeout set using this function will be set to the next lowest tenth of a second for the purposes of detecting read or write timeouts. For example a timeout of 550 milliseconds will be seen by the class as a timeout of 500 milliseconds for the purposes of reading and writing the port. However millisecond-level control is allowed by the select() system call, so for example a 550-millisecond timeout will be seen as 550 milliseconds on POSIX systems for the purpose of detecting available bytes in the read buffer. For Windows: Sets the read and write timeouts for the port to \a millisec milliseconds. Setting 0 indicates that timeouts are not used for read nor write operations; however read() and write() functions will still block. Set -1 to provide non-blocking behaviour (read() and write() will return immediately). \bold note: this function does nothing in event driven mode. */ void QextSerialPort::setTimeout(long millisec) { Q_D(QextSerialPort); QWriteLocker locker(&d->lock); if (d->Settings.Timeout_Millisec != millisec) d->setTimeout(millisec, true); } /*! Sets DTR line to the requested state (\a set default to high). This function will have no effect if the port associated with the class is not currently open. */ void QextSerialPort::setDtr(bool set) { Q_D(QextSerialPort); QWriteLocker locker(&d->lock); if (isOpen()) d->setDtr_sys(set); } /*! Sets RTS line to the requested state \a set (high by default). This function will have no effect if the port associated with the class is not currently open. */ void QextSerialPort::setRts(bool set) { Q_D(QextSerialPort); QWriteLocker locker(&d->lock); if (isOpen()) d->setRts_sys(set); } /*! \reimp Reads a block of data from the serial port. This function will read at most maxlen bytes from the serial port and place them in the buffer pointed to by data. Return value is the number of bytes actually read, or -1 on error. \warning before calling this function ensure that serial port associated with this class is currently open (use isOpen() function to check if port is open). */ qint64 QextSerialPort::readData(char *data, qint64 maxSize) { Q_D(QextSerialPort); QWriteLocker locker(&d->lock); qint64 bytesFromBuffer = 0; if (!d->readBuffer.isEmpty()) { bytesFromBuffer = d->readBuffer.read(data, maxSize); if (bytesFromBuffer == maxSize) return bytesFromBuffer; } qint64 bytesFromDevice = d->readData_sys(data+bytesFromBuffer, maxSize-bytesFromBuffer); if (bytesFromDevice < 0) { return -1; } return bytesFromBuffer + bytesFromDevice; } /*! \reimp Writes a block of data to the serial port. This function will write len bytes from the buffer pointed to by data to the serial port. Return value is the number of bytes actually written, or -1 on error. \warning before calling this function ensure that serial port associated with this class is currently open (use isOpen() function to check if port is open). */ qint64 QextSerialPort::writeData(const char *data, qint64 maxSize) { Q_D(QextSerialPort); QWriteLocker locker(&d->lock); return d->writeData_sys(data, maxSize); } #include "moc_qextserialport.cpp" rtimulib-7.2.1/RTHost/RTSerialPort/src/qextserialport.h000066400000000000000000000155121254201074400231160ustar00rootroot00000000000000/**************************************************************************** ** Copyright (c) 2000-2003 Wayne Roth ** Copyright (c) 2004-2007 Stefan Sander ** Copyright (c) 2007 Michal Policht ** Copyright (c) 2008 Brandon Fosdick ** Copyright (c) 2009-2010 Liam Staskawicz ** Copyright (c) 2011 Debao Zhang ** All right reserved. ** Web: http://code.google.com/p/qextserialport/ ** ** Permission 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. ** ****************************************************************************/ #ifndef _QEXTSERIALPORT_H_ #define _QEXTSERIALPORT_H_ #include #include "qextserialport_global.h" #ifdef Q_OS_UNIX #include #endif /*line status constants*/ // ### QESP2.0 move to enum #define LS_CTS 0x01 #define LS_DSR 0x02 #define LS_DCD 0x04 #define LS_RI 0x08 #define LS_RTS 0x10 #define LS_DTR 0x20 #define LS_ST 0x40 #define LS_SR 0x80 /*error constants*/ // ### QESP2.0 move to enum #define E_NO_ERROR 0 #define E_INVALID_FD 1 #define E_NO_MEMORY 2 #define E_CAUGHT_NON_BLOCKED_SIGNAL 3 #define E_PORT_TIMEOUT 4 #define E_INVALID_DEVICE 5 #define E_BREAK_CONDITION 6 #define E_FRAMING_ERROR 7 #define E_IO_ERROR 8 #define E_BUFFER_OVERRUN 9 #define E_RECEIVE_OVERFLOW 10 #define E_RECEIVE_PARITY_ERROR 11 #define E_TRANSMIT_OVERFLOW 12 #define E_READ_FAILED 13 #define E_WRITE_FAILED 14 #define E_FILE_NOT_FOUND 15 #define E_PERMISSION_DENIED 16 #define E_AGAIN 17 enum BaudRateType { #if defined(Q_OS_UNIX) || defined(qdoc) BAUD50 = 50, //POSIX ONLY BAUD75 = 75, //POSIX ONLY BAUD134 = 134, //POSIX ONLY BAUD150 = 150, //POSIX ONLY BAUD200 = 200, //POSIX ONLY BAUD1800 = 1800, //POSIX ONLY # if defined(B76800) || defined(qdoc) BAUD76800 = 76800, //POSIX ONLY # endif # if (defined(B230400) && defined(B4000000)) || defined(qdoc) BAUD230400 = 230400, //POSIX ONLY BAUD460800 = 460800, //POSIX ONLY BAUD500000 = 500000, //POSIX ONLY BAUD576000 = 576000, //POSIX ONLY BAUD921600 = 921600, //POSIX ONLY BAUD1000000 = 1000000, //POSIX ONLY BAUD1152000 = 1152000, //POSIX ONLY BAUD1500000 = 1500000, //POSIX ONLY BAUD2000000 = 2000000, //POSIX ONLY BAUD2500000 = 2500000, //POSIX ONLY BAUD3000000 = 3000000, //POSIX ONLY BAUD3500000 = 3500000, //POSIX ONLY BAUD4000000 = 4000000, //POSIX ONLY # endif #endif //Q_OS_UNIX #if defined(Q_OS_WIN) || defined(qdoc) BAUD14400 = 14400, //WINDOWS ONLY BAUD56000 = 56000, //WINDOWS ONLY BAUD128000 = 128000, //WINDOWS ONLY BAUD256000 = 256000, //WINDOWS ONLY #endif //Q_OS_WIN BAUD110 = 110, BAUD300 = 300, BAUD600 = 600, BAUD1200 = 1200, BAUD2400 = 2400, BAUD4800 = 4800, BAUD9600 = 9600, BAUD19200 = 19200, BAUD38400 = 38400, BAUD57600 = 57600, BAUD115200 = 115200 }; enum DataBitsType { DATA_5 = 5, DATA_6 = 6, DATA_7 = 7, DATA_8 = 8 }; enum ParityType { PAR_NONE, PAR_ODD, PAR_EVEN, #if defined(Q_OS_WIN) || defined(qdoc) PAR_MARK, //WINDOWS ONLY #endif PAR_SPACE }; enum StopBitsType { STOP_1, #if defined(Q_OS_WIN) || defined(qdoc) STOP_1_5, //WINDOWS ONLY #endif STOP_2 }; enum FlowType { FLOW_OFF, FLOW_HARDWARE, FLOW_XONXOFF }; /** * structure to contain port settings */ struct PortSettings { BaudRateType BaudRate; DataBitsType DataBits; ParityType Parity; StopBitsType StopBits; FlowType FlowControl; long Timeout_Millisec; }; class QextSerialPortPrivate; class QEXTSERIALPORT_EXPORT QextSerialPort: public QIODevice { Q_OBJECT Q_DECLARE_PRIVATE(QextSerialPort) Q_ENUMS(QueryMode) Q_PROPERTY(QString portName READ portName WRITE setPortName) Q_PROPERTY(QueryMode queryMode READ queryMode WRITE setQueryMode) public: enum QueryMode { Polling, EventDriven }; explicit QextSerialPort(QueryMode mode = EventDriven, QObject* parent = 0); explicit QextSerialPort(const QString & name, QueryMode mode = EventDriven, QObject * parent = 0); explicit QextSerialPort(const PortSettings & s, QueryMode mode = EventDriven, QObject * parent = 0); QextSerialPort(const QString & name, const PortSettings& s, QueryMode mode = EventDriven, QObject *parent=0); ~QextSerialPort(); QString portName() const; QueryMode queryMode() const; BaudRateType baudRate() const; DataBitsType dataBits() const; ParityType parity() const; StopBitsType stopBits() const; FlowType flowControl() const; bool open(OpenMode mode); bool isSequential() const; void close(); void flush(); qint64 bytesAvailable() const; QByteArray readAll(); ulong lastError() const; ulong lineStatus(); QString errorString(); public Q_SLOTS: void setPortName(const QString & name); void setQueryMode(QueryMode mode); void setBaudRate(BaudRateType); void setDataBits(DataBitsType); void setParity(ParityType); void setStopBits(StopBitsType); void setFlowControl(FlowType); void setTimeout(long); void setDtr(bool set=true); void setRts(bool set=true); Q_SIGNALS: void dsrChanged(bool status); protected: qint64 readData(char * data, qint64 maxSize); qint64 writeData(const char * data, qint64 maxSize); private: Q_DISABLE_COPY(QextSerialPort) #ifdef Q_OS_WIN Q_PRIVATE_SLOT(d_func(), void _q_onWinEvent(HANDLE)) #endif Q_PRIVATE_SLOT(d_func(), void _q_canRead()) QextSerialPortPrivate * const d_ptr; }; #endif rtimulib-7.2.1/RTHost/RTSerialPort/src/qextserialport.pri000066400000000000000000000037351254201074400234650ustar00rootroot00000000000000exists(../common.pri) { #For case: # someone want to copy all file in the src/ directory # to their project src/ directory and they does not like # the common.pri file. #In this case: # they can just include this file (qextserialport.pri) too. include(../common.pri) } INCLUDEPATH += $$PWD DEPENDPATH += $$PWD qextserialport-library:!qextserialport-buildlib { # Using QextSerialPort as shared or static library. LIBS += -L$$QEXTSERIALPORT_LIBDIR -l$$QEXTSERIALPORT_LIBNAME !qextserialport-static: DEFINES += QEXTSERIALPORT_USING_SHARED } else { # Building library(shared or static) # or including source files HEADERS += $$PWD/qextserialport.h \ $$PWD/qextserialport_p.h \ $$PWD/qextserialenumerator.h \ $$PWD/qextserialenumerator_p.h \ $$PWD/qextserialport_global.h SOURCES += $$PWD/qextserialport.cpp \ $$PWD/qextserialenumerator.cpp unix:SOURCES += $$PWD/qextserialport_unix.cpp unix:!macx:SOURCES += $$PWD/qextserialenumerator_unix.cpp macx:SOURCES += $$PWD/qextserialenumerator_osx.cpp win32:SOURCES += $$PWD/qextserialport_win.cpp \ $$PWD/qextserialenumerator_win.cpp # For Windows user who doesn't have Qt4's Private files win32:contains(QT_VERSION, ^4\\..*\\..*):!exists($$[QT_INSTALL_HEADERS]/QtCore/private/qwineventnotifier_p.h){ DEFINES += QESP_NO_QT4_PRIVATE HEADERS += $$PWD/qextwineventnotifier_p.h SOURCES += $$PWD/qextwineventnotifier_p.cpp } # For building shared library only contains(TEMPLATE, .*lib):contains(CONFIG, shared): DEFINES += QEXTSERIALPORT_BUILD_SHARED } macx:LIBS += -framework IOKit -framework CoreFoundation win32:LIBS += -lsetupapi -ladvapi32 -luser32 rtimulib-7.2.1/RTHost/RTSerialPort/src/qextserialport_global.h000066400000000000000000000050071254201074400244340ustar00rootroot00000000000000/**************************************************************************** ** Copyright (c) 2000-2003 Wayne Roth ** Copyright (c) 2004-2007 Stefan Sander ** Copyright (c) 2007 Michal Policht ** Copyright (c) 2008 Brandon Fosdick ** Copyright (c) 2009-2010 Liam Staskawicz ** Copyright (c) 2011 Debao Zhang ** All right reserved. ** Web: http://code.google.com/p/qextserialport/ ** ** Permission 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. ** ****************************************************************************/ #ifndef QEXTSERIALPORT_GLOBAL_H #define QEXTSERIALPORT_GLOBAL_H #include #ifdef QEXTSERIALPORT_BUILD_SHARED # define QEXTSERIALPORT_EXPORT Q_DECL_EXPORT #elif defined(QEXTSERIALPORT_USING_SHARED) # define QEXTSERIALPORT_EXPORT Q_DECL_IMPORT #else # define QEXTSERIALPORT_EXPORT #endif // ### for compatible with old version. should be removed in QESP 2.0 #ifdef _TTY_NOWARN_ # define QESP_NO_WARN #endif #ifdef _TTY_NOWARN_PORT_ # define QESP_NO_PORTABILITY_WARN #endif /*if all warning messages are turned off, flag portability warnings to be turned off as well*/ #ifdef QESP_NO_WARN # define QESP_NO_PORTABILITY_WARN #endif /*macros for warning and debug messages*/ #ifdef QESP_NO_PORTABILITY_WARN # define QESP_PORTABILITY_WARNING while(false)qWarning #else # define QESP_PORTABILITY_WARNING qWarning #endif /*QESP_NOWARN_PORT*/ #ifdef QESP_NO_WARN # define QESP_WARNING while(false)qWarning #else # define QESP_WARNING qWarning #endif /*QESP_NOWARN*/ #endif // QEXTSERIALPORT_GLOBAL_H rtimulib-7.2.1/RTHost/RTSerialPort/src/qextserialport_p.h000066400000000000000000000153711254201074400234400ustar00rootroot00000000000000/**************************************************************************** ** Copyright (c) 2000-2003 Wayne Roth ** Copyright (c) 2004-2007 Stefan Sander ** Copyright (c) 2007 Michal Policht ** Copyright (c) 2008 Brandon Fosdick ** Copyright (c) 2009-2010 Liam Staskawicz ** Copyright (c) 2011 Debao Zhang ** All right reserved. ** Web: http://code.google.com/p/qextserialport/ ** ** Permission 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. ** ****************************************************************************/ #ifndef _QEXTSERIALPORT_P_H_ #define _QEXTSERIALPORT_P_H_ // // W A R N I N G // ------------- // // This file is not part of the QESP API. It exists for the convenience // of other QESP classes. This header file may change from version to // version without notice, or even be removed. // // We mean it. // #include "qextserialport.h" #include #ifdef Q_OS_UNIX # include #elif (defined Q_OS_WIN) # include #endif #include // This is QextSerialPort's read buffer, needed by posix system. // ref: QRingBuffer & QIODevicePrivateLinearBuffer class QextReadBuffer { public: inline QextReadBuffer(size_t growth=4096) : len(0), first(0), buf(0), capacity(0), basicBlockSize(growth) { } ~QextReadBuffer() { delete [] buf; } inline void clear() { first = buf; len = 0; } inline int size() const { return len; } inline bool isEmpty() const { return len == 0; } inline int read(char* target, int size) { int r = qMin(size, len); if (r == 1) { *target = *first; --len; ++first; } else { memcpy(target, first, r); len -= r; first += r; } return r; } inline char* reserve(size_t size) { if ((first - buf) + len + size > capacity) { size_t newCapacity = qMax(capacity, basicBlockSize); while (newCapacity < size) newCapacity *= 2; if (newCapacity > capacity) { // allocate more space char* newBuf = new char[newCapacity]; memmove(newBuf, first, len); delete [] buf; buf = newBuf; capacity = newCapacity; } else { // shift any existing data to make space memmove(buf, first, len); } first = buf; } char* writePtr = first + len; len += (int)size; return writePtr; } inline void chop(int size) { if (size >= len) { clear(); } else { len -= size; } } inline void squeeze() { if (first != buf) { memmove(buf, first, len); first = buf; } size_t newCapacity = basicBlockSize; while (newCapacity < size_t(len)) newCapacity *= 2; if (newCapacity < capacity) { char * tmp = static_cast(realloc(buf, newCapacity)); if (tmp) { buf = tmp; capacity = newCapacity; } } } inline QByteArray readAll() { char* f = first; int l = len; clear(); return QByteArray(f, l); } private: int len; char* first; char* buf; size_t capacity; size_t basicBlockSize; }; class QextWinEventNotifier; class QWinEventNotifier; class QReadWriteLock; class QSocketNotifier; class QextSerialPortPrivate { Q_DECLARE_PUBLIC(QextSerialPort) public: QextSerialPortPrivate(QextSerialPort * q); ~QextSerialPortPrivate(); enum DirtyFlagEnum { DFE_BaudRate = 0x0001, DFE_Parity = 0x0002, DFE_StopBits = 0x0004, DFE_DataBits = 0x0008, DFE_Flow = 0x0010, DFE_TimeOut = 0x0100, DFE_ALL = 0x0fff, DFE_Settings_Mask = 0x00ff //without TimeOut }; mutable QReadWriteLock lock; QString port; PortSettings Settings; QextReadBuffer readBuffer; int settingsDirtyFlags; ulong lastErr; QextSerialPort::QueryMode _queryMode; // platform specific members #ifdef Q_OS_UNIX int fd; QSocketNotifier *readNotifier; struct termios Posix_CommConfig; struct termios old_termios; #elif (defined Q_OS_WIN) HANDLE Win_Handle; OVERLAPPED overlap; COMMCONFIG Win_CommConfig; COMMTIMEOUTS Win_CommTimeouts; # ifndef QESP_NO_QT4_PRIVATE QWinEventNotifier *winEventNotifier; # else QextWinEventNotifier *winEventNotifier; # endif DWORD eventMask; QList pendingWrites; QReadWriteLock* bytesToWriteLock; qint64 _bytesToWrite; #endif /*fill PortSettings*/ void setBaudRate(BaudRateType baudRate, bool update=true); void setDataBits(DataBitsType dataBits, bool update=true); void setParity(ParityType parity, bool update=true); void setStopBits(StopBitsType stopbits, bool update=true); void setFlowControl(FlowType flow, bool update=true); void setTimeout(long millisec, bool update=true); void setPortSettings(const PortSettings& settings, bool update=true); void platformSpecificDestruct(); void platformSpecificInit(); void translateError(ulong error); void updatePortSettings(); qint64 readData_sys(char * data, qint64 maxSize); qint64 writeData_sys(const char * data, qint64 maxSize); void setDtr_sys(bool set=true); void setRts_sys(bool set=true); bool open_sys(QIODevice::OpenMode mode); bool close_sys(); bool flush_sys(); ulong lineStatus_sys(); qint64 bytesAvailable_sys() const; #ifdef Q_OS_WIN void _q_onWinEvent(HANDLE h); #endif void _q_canRead(); QextSerialPort * q_ptr; }; #endif //_QEXTSERIALPORT_P_H_ rtimulib-7.2.1/RTHost/RTSerialPort/src/qextserialport_unix.cpp000066400000000000000000000342301254201074400245120ustar00rootroot00000000000000/**************************************************************************** ** Copyright (c) 2000-2003 Wayne Roth ** Copyright (c) 2004-2007 Stefan Sander ** Copyright (c) 2007 Michal Policht ** Copyright (c) 2008 Brandon Fosdick ** Copyright (c) 2009-2010 Liam Staskawicz ** Copyright (c) 2011 Debao Zhang ** All right reserved. ** Web: http://code.google.com/p/qextserialport/ ** ** Permission 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. ** ****************************************************************************/ #include "qextserialport.h" #include "qextserialport_p.h" #include #include #include #include #include #include #include #include #include #include void QextSerialPortPrivate::platformSpecificInit() { fd = 0; readNotifier = 0; } /*! Standard destructor. */ void QextSerialPortPrivate::platformSpecificDestruct() { } bool QextSerialPortPrivate::open_sys(QIODevice::OpenMode mode) { Q_Q(QextSerialPort); //note: linux 2.6.21 seems to ignore O_NDELAY flag if ((fd = ::open(qPrintable(port) ,O_RDWR | O_NOCTTY | O_NDELAY)) != -1) { /*In the Private class, We can not call QIODevice::open()*/ q->setOpenMode(mode); // Flag the port as opened ::tcgetattr(fd, &old_termios); // Save the old termios Posix_CommConfig = old_termios; // Make a working copy ::cfmakeraw(&Posix_CommConfig); // Enable raw access /*set up other port settings*/ Posix_CommConfig.c_cflag |= CREAD|CLOCAL; Posix_CommConfig.c_lflag &= (~(ICANON|ECHO|ECHOE|ECHOK|ECHONL|ISIG)); Posix_CommConfig.c_iflag &= (~(INPCK|IGNPAR|PARMRK|ISTRIP|ICRNL|IXANY)); Posix_CommConfig.c_oflag &= (~OPOST); Posix_CommConfig.c_cc[VMIN] = 0; #ifdef _POSIX_VDISABLE // Is a disable character available on this system? // Some systems allow for per-device disable-characters, so get the // proper value for the configured device const long vdisable = ::fpathconf(fd, _PC_VDISABLE); Posix_CommConfig.c_cc[VINTR] = vdisable; Posix_CommConfig.c_cc[VQUIT] = vdisable; Posix_CommConfig.c_cc[VSTART] = vdisable; Posix_CommConfig.c_cc[VSTOP] = vdisable; Posix_CommConfig.c_cc[VSUSP] = vdisable; #endif //_POSIX_VDISABLE settingsDirtyFlags = DFE_ALL; updatePortSettings(); if (_queryMode == QextSerialPort::EventDriven) { readNotifier = new QSocketNotifier(fd, QSocketNotifier::Read, q); q->connect(readNotifier, SIGNAL(activated(int)), q, SLOT(_q_canRead())); } return true; } else { translateError(errno); return false; } } bool QextSerialPortPrivate::close_sys() { // Force a flush and then restore the original termios flush_sys(); // Using both TCSAFLUSH and TCSANOW here discards any pending input ::tcsetattr(fd, TCSAFLUSH | TCSANOW, &old_termios); // Restore termios ::close(fd); if(readNotifier) { delete readNotifier; readNotifier = 0; } return true; } bool QextSerialPortPrivate::flush_sys() { ::tcdrain(fd); return true; } qint64 QextSerialPortPrivate::bytesAvailable_sys() const { int bytesQueued; if (::ioctl(fd, FIONREAD, &bytesQueued) == -1) { return (qint64)-1; } return bytesQueued; } /*! Translates a system-specific error code to a QextSerialPort error code. Used internally. */ void QextSerialPortPrivate::translateError(ulong error) { switch (error) { case EBADF: case ENOTTY: lastErr = E_INVALID_FD; break; case EINTR: lastErr = E_CAUGHT_NON_BLOCKED_SIGNAL; break; case ENOMEM: lastErr = E_NO_MEMORY; break; case EACCES: lastErr = E_PERMISSION_DENIED; break; case EAGAIN: lastErr = E_AGAIN; break; } } void QextSerialPortPrivate::setDtr_sys(bool set) { int status; ::ioctl(fd, TIOCMGET, &status); if (set) status |= TIOCM_DTR; else status &= ~TIOCM_DTR; ::ioctl(fd, TIOCMSET, &status); } void QextSerialPortPrivate::setRts_sys(bool set) { int status; ::ioctl(fd, TIOCMGET, &status); if (set) status |= TIOCM_RTS; else status &= ~TIOCM_RTS; ::ioctl(fd, TIOCMSET, &status); } unsigned long QextSerialPortPrivate::lineStatus_sys() { unsigned long Status=0, Temp=0; ::ioctl(fd, TIOCMGET, &Temp); if (Temp & TIOCM_CTS) Status |= LS_CTS; if (Temp & TIOCM_DSR) Status |= LS_DSR; if (Temp & TIOCM_RI ) Status |= LS_RI; if (Temp & TIOCM_CD ) Status |= LS_DCD; if (Temp & TIOCM_DTR) Status |= LS_DTR; if (Temp & TIOCM_RTS) Status |= LS_RTS; if (Temp & TIOCM_ST ) Status |= LS_ST; if (Temp & TIOCM_SR ) Status |= LS_SR; return Status; } /*! Reads a block of data from the serial port. This function will read at most maxSize bytes from the serial port and place them in the buffer pointed to by data. Return value is the number of bytes actually read, or -1 on error. \warning before calling this function ensure that serial port associated with this class is currently open (use isOpen() function to check if port is open). */ qint64 QextSerialPortPrivate::readData_sys(char * data, qint64 maxSize) { int retVal = ::read(fd, data, maxSize); if (retVal == -1) lastErr = E_READ_FAILED; return retVal; } /*! Writes a block of data to the serial port. This function will write maxSize bytes from the buffer pointed to by data to the serial port. Return value is the number of bytes actually written, or -1 on error. \warning before calling this function ensure that serial port associated with this class is currently open (use isOpen() function to check if port is open). */ qint64 QextSerialPortPrivate::writeData_sys(const char * data, qint64 maxSize) { int retVal = ::write(fd, data, maxSize); if (retVal == -1) lastErr = E_WRITE_FAILED; return (qint64)retVal; } static void setBaudRate2Termios(termios *config, int baudRate) { #ifdef CBAUD config->c_cflag &= (~CBAUD); config->c_cflag |= baudRate; #else ::cfsetispeed(config, baudRate); ::cfsetospeed(config, baudRate); #endif } /* All the platform settings was performed in this function. */ void QextSerialPortPrivate::updatePortSettings() { if (!q_func()->isOpen() || !settingsDirtyFlags) return; if (settingsDirtyFlags & DFE_BaudRate) { switch (Settings.BaudRate) { case BAUD50: setBaudRate2Termios(&Posix_CommConfig, B50); break; case BAUD75: setBaudRate2Termios(&Posix_CommConfig, B75); break; case BAUD110: setBaudRate2Termios(&Posix_CommConfig, B110); break; case BAUD134: setBaudRate2Termios(&Posix_CommConfig, B134); break; case BAUD150: setBaudRate2Termios(&Posix_CommConfig, B150); break; case BAUD200: setBaudRate2Termios(&Posix_CommConfig, B200); break; case BAUD300: setBaudRate2Termios(&Posix_CommConfig, B300); break; case BAUD600: setBaudRate2Termios(&Posix_CommConfig, B600); break; case BAUD1200: setBaudRate2Termios(&Posix_CommConfig, B1200); break; case BAUD1800: setBaudRate2Termios(&Posix_CommConfig, B1800); break; case BAUD2400: setBaudRate2Termios(&Posix_CommConfig, B2400); break; case BAUD4800: setBaudRate2Termios(&Posix_CommConfig, B4800); break; case BAUD9600: setBaudRate2Termios(&Posix_CommConfig, B9600); break; case BAUD19200: setBaudRate2Termios(&Posix_CommConfig, B19200); break; case BAUD38400: setBaudRate2Termios(&Posix_CommConfig, B38400); break; case BAUD57600: setBaudRate2Termios(&Posix_CommConfig, B57600); break; #ifdef B76800 case BAUD76800: setBaudRate2Termios(&Posix_CommConfig, B76800); break; #endif case BAUD115200: setBaudRate2Termios(&Posix_CommConfig, B115200); break; #if defined(B230400) && defined(B4000000) case BAUD230400: setBaudRate2Termios(&Posix_CommConfig, B230400); break; case BAUD460800: setBaudRate2Termios(&Posix_CommConfig, B460800); break; case BAUD500000: setBaudRate2Termios(&Posix_CommConfig, B500000); break; case BAUD576000: setBaudRate2Termios(&Posix_CommConfig, B576000); break; case BAUD921600: setBaudRate2Termios(&Posix_CommConfig, B921600); break; case BAUD1000000: setBaudRate2Termios(&Posix_CommConfig, B1000000); break; case BAUD1152000: setBaudRate2Termios(&Posix_CommConfig, B1152000); break; case BAUD1500000: setBaudRate2Termios(&Posix_CommConfig, B1500000); break; case BAUD2000000: setBaudRate2Termios(&Posix_CommConfig, B2000000); break; case BAUD2500000: setBaudRate2Termios(&Posix_CommConfig, B2500000); break; case BAUD3000000: setBaudRate2Termios(&Posix_CommConfig, B3000000); break; case BAUD3500000: setBaudRate2Termios(&Posix_CommConfig, B3500000); break; case BAUD4000000: setBaudRate2Termios(&Posix_CommConfig, B4000000); break; #endif } } if (settingsDirtyFlags & DFE_Parity) { switch (Settings.Parity) { case PAR_SPACE: /*space parity not directly supported - add an extra data bit to simulate it*/ settingsDirtyFlags |= DFE_DataBits; break; case PAR_NONE: Posix_CommConfig.c_cflag &= (~PARENB); break; case PAR_EVEN: Posix_CommConfig.c_cflag &= (~PARODD); Posix_CommConfig.c_cflag |= PARENB; break; case PAR_ODD: Posix_CommConfig.c_cflag |= (PARENB|PARODD); break; } } /*must after Parity settings*/ if (settingsDirtyFlags & DFE_DataBits) { if (Settings.Parity != PAR_SPACE) { Posix_CommConfig.c_cflag &= (~CSIZE); switch(Settings.DataBits) { case DATA_5: Posix_CommConfig.c_cflag |= CS5; break; case DATA_6: Posix_CommConfig.c_cflag |= CS6; break; case DATA_7: Posix_CommConfig.c_cflag |= CS7; break; case DATA_8: Posix_CommConfig.c_cflag |= CS8; break; } } else { /*space parity not directly supported - add an extra data bit to simulate it*/ Posix_CommConfig.c_cflag &= ~(PARENB|CSIZE); switch(Settings.DataBits) { case DATA_5: Posix_CommConfig.c_cflag |= CS6; break; case DATA_6: Posix_CommConfig.c_cflag |= CS7; break; case DATA_7: Posix_CommConfig.c_cflag |= CS8; break; case DATA_8: /*this will never happen, put here to Suppress an warning*/ break; } } } if (settingsDirtyFlags & DFE_StopBits) { switch (Settings.StopBits) { case STOP_1: Posix_CommConfig.c_cflag &= (~CSTOPB); break; case STOP_2: Posix_CommConfig.c_cflag |= CSTOPB; break; } } if (settingsDirtyFlags & DFE_Flow) { switch(Settings.FlowControl) { case FLOW_OFF: Posix_CommConfig.c_cflag &= (~CRTSCTS); Posix_CommConfig.c_iflag &= (~(IXON|IXOFF|IXANY)); break; case FLOW_XONXOFF: /*software (XON/XOFF) flow control*/ Posix_CommConfig.c_cflag &= (~CRTSCTS); Posix_CommConfig.c_iflag |= (IXON|IXOFF|IXANY); break; case FLOW_HARDWARE: Posix_CommConfig.c_cflag |= CRTSCTS; Posix_CommConfig.c_iflag &= (~(IXON|IXOFF|IXANY)); break; } } /*if any thing in Posix_CommConfig changed, flush*/ if (settingsDirtyFlags & DFE_Settings_Mask) ::tcsetattr(fd, TCSAFLUSH, &Posix_CommConfig); if (settingsDirtyFlags & DFE_TimeOut) { int millisec = Settings.Timeout_Millisec; if (millisec == -1) { ::fcntl(fd, F_SETFL, O_NDELAY); } else { //O_SYNC should enable blocking ::write() //however this seems not working on Linux 2.6.21 (works on OpenBSD 4.2) ::fcntl(fd, F_SETFL, O_SYNC); } ::tcgetattr(fd, & Posix_CommConfig); Posix_CommConfig.c_cc[VTIME] = millisec/100; ::tcsetattr(fd, TCSAFLUSH, & Posix_CommConfig); } settingsDirtyFlags = 0; } rtimulib-7.2.1/RTHost/RTSerialPort/src/qextserialport_win.cpp000066400000000000000000000350231254201074400243250ustar00rootroot00000000000000/**************************************************************************** ** Copyright (c) 2000-2003 Wayne Roth ** Copyright (c) 2004-2007 Stefan Sander ** Copyright (c) 2007 Michal Policht ** Copyright (c) 2008 Brandon Fosdick ** Copyright (c) 2009-2010 Liam Staskawicz ** Copyright (c) 2011 Debao Zhang ** All right reserved. ** Web: http://code.google.com/p/qextserialport/ ** ** Permission 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. ** ****************************************************************************/ #include "qextserialport.h" #include "qextserialport_p.h" #include #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) # include # define WinEventNotifier QWinEventNotifier #elif !defined(QESP_NO_QT4_PRIVATE) # include # define WinEventNotifier QWinEventNotifier #else # include "qextwineventnotifier_p.h" # define WinEventNotifier QextWinEventNotifier #endif void QextSerialPortPrivate::platformSpecificInit() { Win_Handle=INVALID_HANDLE_VALUE; ZeroMemory(&overlap, sizeof(OVERLAPPED)); overlap.hEvent = CreateEvent(NULL, true, false, NULL); winEventNotifier = 0; bytesToWriteLock = new QReadWriteLock; _bytesToWrite = 0; } void QextSerialPortPrivate::platformSpecificDestruct() { CloseHandle(overlap.hEvent); delete bytesToWriteLock; } /*! \internal COM ports greater than 9 need \\.\ prepended This is only need when open the port. */ static QString fullPortNameWin(const QString & name) { QRegExp rx(QLatin1String("^COM(\\d+)")); QString fullName(name); if(fullName.contains(rx)) fullName.prepend(QLatin1String("\\\\.\\")); return fullName; } bool QextSerialPortPrivate::open_sys(QIODevice::OpenMode mode) { Q_Q(QextSerialPort); DWORD confSize = sizeof(COMMCONFIG); Win_CommConfig.dwSize = confSize; DWORD dwFlagsAndAttributes = 0; if (_queryMode == QextSerialPort::EventDriven) dwFlagsAndAttributes += FILE_FLAG_OVERLAPPED; /*open the port*/ Win_Handle=CreateFileW((wchar_t*)fullPortNameWin(port).utf16(), GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, dwFlagsAndAttributes, NULL); if (Win_Handle!=INVALID_HANDLE_VALUE) { q->setOpenMode(mode); /*configure port settings*/ GetCommConfig(Win_Handle, &Win_CommConfig, &confSize); GetCommState(Win_Handle, &(Win_CommConfig.dcb)); /*set up parameters*/ Win_CommConfig.dcb.fBinary=TRUE; Win_CommConfig.dcb.fInX=FALSE; Win_CommConfig.dcb.fOutX=FALSE; Win_CommConfig.dcb.fAbortOnError=FALSE; Win_CommConfig.dcb.fNull=FALSE; /*flush all settings*/ settingsDirtyFlags = DFE_ALL; updatePortSettings(); //init event driven approach if (_queryMode == QextSerialPort::EventDriven) { if (!SetCommMask( Win_Handle, EV_TXEMPTY | EV_RXCHAR | EV_DSR)) { QESP_WARNING()<<"failed to set Comm Mask. Error code:"<("HANDLE"); q->connect(winEventNotifier, SIGNAL(activated(HANDLE)), q, SLOT(_q_onWinEvent(HANDLE)), Qt::DirectConnection); WaitCommEvent(Win_Handle, &eventMask, &overlap); } return true; } return false; } bool QextSerialPortPrivate::close_sys() { flush_sys(); CancelIo(Win_Handle); if (CloseHandle(Win_Handle)) Win_Handle = INVALID_HANDLE_VALUE; if (winEventNotifier){ winEventNotifier->setEnabled(false); winEventNotifier->deleteLater(); winEventNotifier = 0; } _bytesToWrite = 0; foreach(OVERLAPPED* o, pendingWrites) { CloseHandle(o->hEvent); delete o; } pendingWrites.clear(); return true; } bool QextSerialPortPrivate::flush_sys() { FlushFileBuffers(Win_Handle); return true; } qint64 QextSerialPortPrivate::bytesAvailable_sys() const { DWORD Errors; COMSTAT Status; if (ClearCommError(Win_Handle, &Errors, &Status)) { return Status.cbInQue; } return (qint64)-1; } /* Translates a system-specific error code to a QextSerialPort error code. Used internally. */ void QextSerialPortPrivate::translateError(ulong error) { if (error&CE_BREAK) { lastErr=E_BREAK_CONDITION; } else if (error&CE_FRAME) { lastErr=E_FRAMING_ERROR; } else if (error&CE_IOE) { lastErr=E_IO_ERROR; } else if (error&CE_MODE) { lastErr=E_INVALID_FD; } else if (error&CE_OVERRUN) { lastErr=E_BUFFER_OVERRUN; } else if (error&CE_RXPARITY) { lastErr=E_RECEIVE_PARITY_ERROR; } else if (error&CE_RXOVER) { lastErr=E_RECEIVE_OVERFLOW; } else if (error&CE_TXFULL) { lastErr=E_TRANSMIT_OVERFLOW; } } /* Reads a block of data from the serial port. This function will read at most maxlen bytes from the serial port and place them in the buffer pointed to by data. Return value is the number of bytes actually read, or -1 on error. \warning before calling this function ensure that serial port associated with this class is currently open (use isOpen() function to check if port is open). */ qint64 QextSerialPortPrivate::readData_sys(char *data, qint64 maxSize) { DWORD bytesRead = 0; bool failed = false; if (_queryMode == QextSerialPort::EventDriven) { OVERLAPPED overlapRead; ZeroMemory(&overlapRead, sizeof(OVERLAPPED)); if (!ReadFile(Win_Handle, (void*)data, (DWORD)maxSize, & bytesRead, & overlapRead)) { if (GetLastError() == ERROR_IO_PENDING) GetOverlappedResult(Win_Handle, & overlapRead, & bytesRead, true); else failed = true; } } else if (!ReadFile(Win_Handle, (void*)data, (DWORD)maxSize, & bytesRead, NULL)) { failed = true; } if (!failed) return (qint64)bytesRead; lastErr = E_READ_FAILED; return -1; } /* Writes a block of data to the serial port. This function will write len bytes from the buffer pointed to by data to the serial port. Return value is the number of bytes actually written, or -1 on error. \warning before calling this function ensure that serial port associated with this class is currently open (use isOpen() function to check if port is open). */ qint64 QextSerialPortPrivate::writeData_sys(const char *data, qint64 maxSize) { DWORD bytesWritten = 0; bool failed = false; if (_queryMode == QextSerialPort::EventDriven) { OVERLAPPED* newOverlapWrite = new OVERLAPPED; ZeroMemory(newOverlapWrite, sizeof(OVERLAPPED)); newOverlapWrite->hEvent = CreateEvent(NULL, true, false, NULL); if (WriteFile(Win_Handle, (void*)data, (DWORD)maxSize, & bytesWritten, newOverlapWrite)) { CloseHandle(newOverlapWrite->hEvent); delete newOverlapWrite; } else if (GetLastError() == ERROR_IO_PENDING) { // writing asynchronously...not an error QWriteLocker writelocker(bytesToWriteLock); _bytesToWrite += maxSize; pendingWrites.append(newOverlapWrite); } else { QESP_WARNING()<<"QextSerialPort write error:"<hEvent)) QESP_WARNING("QextSerialPort: couldn't cancel IO"); if(!CloseHandle(newOverlapWrite->hEvent)) QESP_WARNING("QextSerialPort: couldn't close OVERLAPPED handle"); delete newOverlapWrite; } } else if (!WriteFile(Win_Handle, (void*)data, (DWORD)maxSize, & bytesWritten, NULL)) { failed = true; } if (!failed) return (qint64)bytesWritten; lastErr = E_WRITE_FAILED; return -1; } void QextSerialPortPrivate::setDtr_sys(bool set) { EscapeCommFunction(Win_Handle, set ? SETDTR : CLRDTR); } void QextSerialPortPrivate::setRts_sys(bool set) { EscapeCommFunction(Win_Handle, set ? SETRTS : CLRRTS); } ulong QextSerialPortPrivate::lineStatus_sys(void) { unsigned long Status=0, Temp=0; GetCommModemStatus(Win_Handle, &Temp); if (Temp & MS_CTS_ON) Status|=LS_CTS; if (Temp & MS_DSR_ON) Status|=LS_DSR; if (Temp & MS_RING_ON) Status|=LS_RI; if (Temp & MS_RLSD_ON) Status|=LS_DCD; return Status; } /* Triggered when there's activity on our HANDLE. */ void QextSerialPortPrivate::_q_onWinEvent(HANDLE h) { Q_Q(QextSerialPort); if(h == overlap.hEvent) { if (eventMask & EV_RXCHAR) { if (q->sender() != q && bytesAvailable_sys() > 0) _q_canRead(); } if (eventMask & EV_TXEMPTY) { /* A write completed. Run through the list of OVERLAPPED writes, and if they completed successfully, take them off the list and delete them. Otherwise, leave them on there so they can finish. */ qint64 totalBytesWritten = 0; QList overlapsToDelete; foreach(OVERLAPPED* o, pendingWrites) { DWORD numBytes = 0; if (GetOverlappedResult(Win_Handle, o, & numBytes, false)) { overlapsToDelete.append(o); totalBytesWritten += numBytes; } else if( GetLastError() != ERROR_IO_INCOMPLETE ) { overlapsToDelete.append(o); QESP_WARNING()<<"CommEvent overlapped write error:" << GetLastError(); } } if (q->sender() != q && totalBytesWritten > 0) { QWriteLocker writelocker(bytesToWriteLock); Q_EMIT q->bytesWritten(totalBytesWritten); _bytesToWrite = 0; } foreach(OVERLAPPED* o, overlapsToDelete) { OVERLAPPED *toDelete = pendingWrites.takeAt(pendingWrites.indexOf(o)); CloseHandle(toDelete->hEvent); delete toDelete; } } if (eventMask & EV_DSR) { if (lineStatus_sys() & LS_DSR) Q_EMIT q->dsrChanged(true); else Q_EMIT q->dsrChanged(false); } } WaitCommEvent(Win_Handle, &eventMask, &overlap); } void QextSerialPortPrivate::updatePortSettings() { if (!q_ptr->isOpen() || !settingsDirtyFlags) return; //fill struct : COMMCONFIG if (settingsDirtyFlags & DFE_BaudRate) { Win_CommConfig.dcb.BaudRate = Settings.BaudRate; } if (settingsDirtyFlags & DFE_Parity) { Win_CommConfig.dcb.Parity = (BYTE)Settings.Parity; Win_CommConfig.dcb.fParity = (Settings.Parity == PAR_NONE) ? FALSE : TRUE; } if (settingsDirtyFlags & DFE_DataBits) { Win_CommConfig.dcb.ByteSize = (BYTE)Settings.DataBits; } if (settingsDirtyFlags & DFE_StopBits) { switch (Settings.StopBits) { case STOP_1: Win_CommConfig.dcb.StopBits = ONESTOPBIT; break; case STOP_1_5: Win_CommConfig.dcb.StopBits = ONE5STOPBITS; break; case STOP_2: Win_CommConfig.dcb.StopBits = TWOSTOPBITS; break; } } if (settingsDirtyFlags & DFE_Flow) { switch(Settings.FlowControl) { /*no flow control*/ case FLOW_OFF: Win_CommConfig.dcb.fOutxCtsFlow=FALSE; Win_CommConfig.dcb.fRtsControl=RTS_CONTROL_DISABLE; Win_CommConfig.dcb.fInX=FALSE; Win_CommConfig.dcb.fOutX=FALSE; break; /*software (XON/XOFF) flow control*/ case FLOW_XONXOFF: Win_CommConfig.dcb.fOutxCtsFlow=FALSE; Win_CommConfig.dcb.fRtsControl=RTS_CONTROL_DISABLE; Win_CommConfig.dcb.fInX=TRUE; Win_CommConfig.dcb.fOutX=TRUE; break; /*hardware flow control*/ case FLOW_HARDWARE: Win_CommConfig.dcb.fOutxCtsFlow=TRUE; Win_CommConfig.dcb.fRtsControl=RTS_CONTROL_HANDSHAKE; Win_CommConfig.dcb.fInX=FALSE; Win_CommConfig.dcb.fOutX=FALSE; break; } } //fill struct : COMMTIMEOUTS if (settingsDirtyFlags & DFE_TimeOut) { if (_queryMode != QextSerialPort::EventDriven) { int millisec = Settings.Timeout_Millisec; if (millisec == -1) { Win_CommTimeouts.ReadIntervalTimeout = MAXDWORD; Win_CommTimeouts.ReadTotalTimeoutConstant = 0; } else { Win_CommTimeouts.ReadIntervalTimeout = millisec; Win_CommTimeouts.ReadTotalTimeoutConstant = millisec; } Win_CommTimeouts.ReadTotalTimeoutMultiplier = 0; Win_CommTimeouts.WriteTotalTimeoutMultiplier = millisec; Win_CommTimeouts.WriteTotalTimeoutConstant = 0; } else { Win_CommTimeouts.ReadIntervalTimeout = MAXDWORD; Win_CommTimeouts.ReadTotalTimeoutMultiplier = 0; Win_CommTimeouts.ReadTotalTimeoutConstant = 0; Win_CommTimeouts.WriteTotalTimeoutMultiplier = 0; Win_CommTimeouts.WriteTotalTimeoutConstant = 0; } } if (settingsDirtyFlags & DFE_Settings_Mask) SetCommConfig(Win_Handle, &Win_CommConfig, sizeof(COMMCONFIG)); if ((settingsDirtyFlags & DFE_TimeOut)) SetCommTimeouts(Win_Handle, &Win_CommTimeouts); settingsDirtyFlags = 0; } rtimulib-7.2.1/RTHost/RTSerialPort/src/qextwineventnotifier_p.cpp000066400000000000000000000165001254201074400252010ustar00rootroot00000000000000/**************************************************************************** ** Copyright (c) 2000-2003 Wayne Roth ** Copyright (c) 2004-2007 Stefan Sander ** Copyright (c) 2007 Michal Policht ** Copyright (c) 2008 Brandon Fosdick ** Copyright (c) 2009-2010 Liam Staskawicz ** Copyright (c) 2011 Debao Zhang ** All right reserved. ** Web: http://code.google.com/p/qextserialport/ ** ** Permission 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. ** ****************************************************************************/ #include "qextwineventnotifier_p.h" #include #include #include #include #include #include #include class QextWinEventNotifierPrivate { Q_DECLARE_PUBLIC(QextWinEventNotifier) public: QextWinEventNotifierPrivate(HANDLE hEvent, QextWinEventNotifier * q) :handleToEvent(hEvent), enabled(false), q_ptr(q) {} HANDLE handleToEvent; bool enabled; private: QextWinEventNotifier * q_ptr; }; /* \internal \class QextWinEventNotifierThread This class works more or less like an EventDispatcher. The api function WaitForMultipleObjects() is used in the new thread to wait for the registered handle. */ class QextWinEventNotifierThread:public QThread { public: explicit QextWinEventNotifierThread(QObject * parent=0); ~QextWinEventNotifierThread(); void stop(); bool registerEventNotifier(QextWinEventNotifier * notifier); void unregisterEventNotifier(QextWinEventNotifier * notifier); protected: void run(); private: HANDLE hStopEvent; //stop thread when this event signaled. HANDLE hUpdateEvent; //make sure eventlist updated. QMutex mutex; QList winEventNotifierList; }; Q_GLOBAL_STATIC(QextWinEventNotifierThread, notifierThread) QextWinEventNotifierThread::QextWinEventNotifierThread(QObject * parent) :QThread(parent) { hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL); hUpdateEvent = CreateEvent(NULL, FALSE, FALSE, NULL); start(); } QextWinEventNotifierThread::~QextWinEventNotifierThread() { if (isRunning()) stop(); CloseHandle(hStopEvent); CloseHandle(hUpdateEvent); } void QextWinEventNotifierThread::stop() { { QMutexLocker locker(&mutex); SetEvent(hStopEvent); } wait(); /// Is this an good idea? } bool QextWinEventNotifierThread::registerEventNotifier(QextWinEventNotifier *notifier) { QMutexLocker locker(&mutex); if (!notifier) { QESP_WARNING("QextWinEventNotifier: Internal error"); return false; } if (winEventNotifierList.contains(notifier)) return true; if (winEventNotifierList.count() >= MAXIMUM_WAIT_OBJECTS - 3) { QESP_WARNING("QextWinEventNotifier: Cannot have more than %d enabled at one time", MAXIMUM_WAIT_OBJECTS - 3); return false; } winEventNotifierList.append(notifier); SetEvent(hUpdateEvent); return true; } void QextWinEventNotifierThread::unregisterEventNotifier(QextWinEventNotifier *notifier) { QMutexLocker locker(&mutex); if (!notifier) { QESP_WARNING("QextWinEventNotifier: Internal error"); return; } int idx = winEventNotifierList.indexOf(notifier); if (idx != -1) { winEventNotifierList.takeAt(idx); SetEvent(hUpdateEvent); } } void QextWinEventNotifierThread::run() { forever{ HANDLE pHandles[MAXIMUM_WAIT_OBJECTS - 1]; DWORD nCount = 0; { QMutexLocker locker(&mutex); nCount = winEventNotifierList.count(); for (int i=0; i<(int)nCount; ++i) pHandles[i] = winEventNotifierList.at(i)->handle(); pHandles[nCount] = hUpdateEvent; pHandles[nCount+1] = hStopEvent; } DWORD ret = WaitForMultipleObjects(nCount+2, pHandles, FALSE, INFINITE); if (ret >= WAIT_OBJECT_0 && ret < WAIT_OBJECT_0 + nCount) { QEvent *evt = new QEvent(QEvent::User); QMutexLocker locker(&mutex); ResetEvent(pHandles[ret-WAIT_OBJECT_0]); QObject * notifier = winEventNotifierList[ret - WAIT_OBJECT_0]; QCoreApplication::postEvent(notifier, evt); } else if (ret == WAIT_OBJECT_0 + nCount) { //ResetEvent(hUpdateEvent); } else if (ret == WAIT_OBJECT_0 + nCount + 1) { //qDebug()<<"quit..."; return; } } } /*! \internal \class QextWinEventNotifier \brief The QextWinEventNotifier class provides support for the Windows Wait functions. The QextWinEventNotifier class makes it possible to use the wait functions on windows in a asynchronous manner. With this class you can register a HANDLE to an event and get notification when that event becomes signalled. \bold Note: If it is a manual reset event ,it will be reset before the notification. This is different from QWinEventNotifier. \bold Note: All the registered handles will be waited under a new thread. This is different from QWinEventNotifier whose event handle will be waited in its affinal thread. */ QextWinEventNotifier::QextWinEventNotifier(QObject *parent) : QObject(parent), d_ptr(new QextWinEventNotifierPrivate(0, this)) {} QextWinEventNotifier::QextWinEventNotifier(HANDLE hEvent, QObject *parent) : QObject(parent), d_ptr(new QextWinEventNotifierPrivate(hEvent, this)) { setEnabled(true); } QextWinEventNotifier::~QextWinEventNotifier() { setEnabled(false); } void QextWinEventNotifier::setHandle(HANDLE hEvent) { setEnabled(false); Q_D(QextWinEventNotifier); d->handleToEvent = hEvent; } HANDLE QextWinEventNotifier::handle() const { return d_func()->handleToEvent; } bool QextWinEventNotifier::isEnabled() const { return d_func()->enabled; } void QextWinEventNotifier::setEnabled(bool enable) { Q_D(QextWinEventNotifier); if (d->enabled == enable) return; d->enabled = enable; if (d->enabled) notifierThread()->registerEventNotifier(this); else notifierThread()->unregisterEventNotifier(this); } bool QextWinEventNotifier::event(QEvent * e) { Q_D(QextWinEventNotifier); QObject::event(e); if (e->type() == QEvent::User) { emit activated(d->handleToEvent); return true; } return false; } rtimulib-7.2.1/RTHost/RTSerialPort/src/qextwineventnotifier_p.h000066400000000000000000000051251254201074400246470ustar00rootroot00000000000000/**************************************************************************** ** Copyright (c) 2000-2003 Wayne Roth ** Copyright (c) 2004-2007 Stefan Sander ** Copyright (c) 2007 Michal Policht ** Copyright (c) 2008 Brandon Fosdick ** Copyright (c) 2009-2010 Liam Staskawicz ** Copyright (c) 2011 Debao Zhang ** All right reserved. ** Web: http://code.google.com/p/qextserialport/ ** ** Permission 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. ** ****************************************************************************/ #ifndef QEXTWINEVENTNOTIFIER_P_H_ #define QEXTWINEVENTNOTIFIER_P_H_ // // W A R N I N G // ------------- // // This file is not part of the QESP API. It exists for the convenience // of other QESP classes. This header file may change from version to // version without notice, or even be removed. // // We mean it. // #include #include #include "qextserialport_global.h" class QextWinEventNotifierPrivate; class QEXTSERIALPORT_EXPORT QextWinEventNotifier : public QObject { Q_OBJECT Q_DECLARE_PRIVATE(QextWinEventNotifier) public: explicit QextWinEventNotifier(QObject *parent = 0); explicit QextWinEventNotifier(HANDLE hEvent, QObject *parent = 0); ~QextWinEventNotifier(); void setHandle(HANDLE hEvent); HANDLE handle() const; bool isEnabled() const; public Q_SLOTS: void setEnabled(bool enable); Q_SIGNALS: void activated(HANDLE hEvent); protected: bool event(QEvent * e); private: Q_DISABLE_COPY(QextWinEventNotifier) QextWinEventNotifierPrivate * d_ptr; }; #endif // QEXTWINEVENTNOTIFIER_P_H_ rtimulib-7.2.1/RTIMULib/000077500000000000000000000000001254201074400146765ustar00rootroot00000000000000rtimulib-7.2.1/RTIMULib/CMakeLists.txt000066400000000000000000000071061254201074400174420ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014-2015, richards-tech #// #// Permission 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. #// The cmake support was based on work by Moritz Fischer at ettus.com. #// Original copyright notice: # # Copyright 2014 Ettus Research LLC # ######################################################################## IF(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) MESSAGE(FATAL_ERROR "Prevented in-tree built. This is bad practice.") ENDIF(${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_BINARY_DIR}) ######################################################################## # Project setup ######################################################################## CMAKE_MINIMUM_REQUIRED(VERSION 2.8.9) PROJECT(RTIMULib CXX) ENABLE_TESTING() INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) INCLUDE(${CMAKE_CURRENT_SOURCE_DIR}/../RTIMULibVersion.txt) SET(LIBRTIMU_SRCS RTFusion.cpp RTFusionRTQF.cpp RTMath.cpp RTFusionKalman4.cpp RTIMUAccelCal.cpp RTIMUHal.cpp RTIMUMagCal.cpp RTIMUSettings.cpp IMUDrivers/RTIMU.cpp IMUDrivers/RTIMUGD20M303DLHC.cpp IMUDrivers/RTIMUGD20HM303DLHC.cpp IMUDrivers/RTIMUGD20HM303D.cpp IMUDrivers/RTIMULSM9DS0.cpp IMUDrivers/RTIMULSM9DS1.cpp IMUDrivers/RTIMUMPU9150.cpp IMUDrivers/RTIMUMPU9250.cpp IMUDrivers/RTIMUBMX055.cpp IMUDrivers/RTIMUBNO055.cpp IMUDrivers/RTIMUNull.cpp IMUDrivers/RTPressure.cpp IMUDrivers/RTPressureBMP180.cpp IMUDrivers/RTPressureLPS25H.cpp IMUDrivers/RTPressureMS5611.cpp IMUDrivers/RTPressureMS5637.cpp IMUDrivers/RTHumidity.cpp IMUDrivers/RTHumidityHTS221.cpp IMUDrivers/RTHumidityHTU21D.cpp ) IF(WIN32 AND QT5) FIND_PACKAGE(Qt5Widgets) FIND_PACKAGE(Qt5Gui) qt5_wrap_ui(UI_HEADERS RTIMULibDemo.ui) ADD_LIBRARY(RTIMULib STATIC ${LIBRTIMU_SRCS}) qt5_use_modules(RTIMULib Widgets Gui) ENDIF(WIN32 AND QT5) IF(WIN32 AND (NOT QT5)) FIND_PACKAGE(Qt4 REQUIRED) INCLUDE(${QT_USE_FILE}) ADD_DEFINITIONS(${QT_DEFINITIONS}) ADD_LIBRARY(RTIMULib STATIC ${LIBRTIMU_SRCS}) TARGET_LINK_LIBRARIES(RTIMULib ${QT_LIBRARIES}) ENDIF(WIN32 AND (NOT QT5)) IF(UNIX) ADD_LIBRARY(RTIMULib SHARED ${LIBRTIMU_SRCS}) SET_PROPERTY(TARGET RTIMULib PROPERTY VERSION ${RTIMULIB_VERSION}) SET_PROPERTY(TARGET RTIMULib PROPERTY SOVERSION ${RTIMULIB_VERSION_MAJOR}) INSTALL(TARGETS RTIMULib DESTINATION lib) INSTALL(DIRECTORY . DESTINATION include FILES_MATCHING PATTERN "*.h") ENDIF(UNIX) rtimulib-7.2.1/RTIMULib/IMUDrivers/000077500000000000000000000000001254201074400166675ustar00rootroot00000000000000rtimulib-7.2.1/RTIMULib/IMUDrivers/RTHumidity.cpp000066400000000000000000000040401254201074400214330ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTHumidity.h" #include "RTHumidityHTS221.h" #include "RTHumidityHTU21D.h" RTHumidity *RTHumidity::createHumidity(RTIMUSettings *settings) { switch (settings->m_humidityType) { case RTHUMIDITY_TYPE_HTS221: return new RTHumidityHTS221(settings); case RTHUMIDITY_TYPE_HTU21D: return new RTHumidityHTU21D(settings); case RTHUMIDITY_TYPE_AUTODISCOVER: if (settings->discoverHumidity(settings->m_humidityType, settings->m_I2CHumidityAddress)) { settings->saveSettings(); return RTHumidity::createHumidity(settings); } return NULL; case RTHUMIDITY_TYPE_NULL: return NULL; default: return NULL; } } RTHumidity::RTHumidity(RTIMUSettings *settings) { m_settings = settings; } RTHumidity::~RTHumidity() { } rtimulib-7.2.1/RTIMULib/IMUDrivers/RTHumidity.h000066400000000000000000000042401254201074400211020ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTHUMIDITY_H #define _RTHUMIDITY_H #include "RTIMUSettings.h" #include "RTIMULibDefs.h" #include "RTHumidityDefs.h" class RTHumidity { public: // Humidity sensor objects should always be created with the following call static RTHumidity *createHumidity(RTIMUSettings *settings); // Constructor/destructor RTHumidity(RTIMUSettings *settings); virtual ~RTHumidity(); // These functions must be provided by sub classes virtual const char *humidityName() = 0; // the name of the humidity sensor virtual int humidityType() = 0; // the type code of the humidity sensor virtual bool humidityInit() = 0; // set up the humidity sensor virtual bool humidityRead(RTIMU_DATA& data) = 0; // get latest value protected: RTIMUSettings *m_settings; // the settings object pointer }; #endif // _RTHUMIDITY_H rtimulib-7.2.1/RTIMULib/IMUDrivers/RTHumidityDefs.h000066400000000000000000000056361254201074400217160ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTHUMIDITYDEFS_H #define _RTHUMIDITYDEFS_H // Pressure sensor type codes #define RTHUMIDITY_TYPE_AUTODISCOVER 0 // audodiscover the humidity sensor #define RTHUMIDITY_TYPE_NULL 1 // if no physical hardware #define RTHUMIDITY_TYPE_HTS221 2 // HTS221 #define RTHUMIDITY_TYPE_HTU21D 3 // HTU21D //---------------------------------------------------------- // // HTS221 // HTS221 I2C Slave Address #define HTS221_ADDRESS 0x5f #define HTS221_REG_ID 0x0f #define HTS221_ID 0xbc // Register map #define HTS221_WHO_AM_I 0x0f #define HTS221_AV_CONF 0x10 #define HTS221_CTRL1 0x20 #define HTS221_CTRL2 0x21 #define HTS221_CTRL3 0x22 #define HTS221_STATUS 0x27 #define HTS221_HUMIDITY_OUT_L 0x28 #define HTS221_HUMIDITY_OUT_H 0x29 #define HTS221_TEMP_OUT_L 0x2a #define HTS221_TEMP_OUT_H 0x2b #define HTS221_H0_H_2 0x30 #define HTS221_H1_H_2 0x31 #define HTS221_T0_C_8 0x32 #define HTS221_T1_C_8 0x33 #define HTS221_T1_T0 0x35 #define HTS221_H0_T0_OUT 0x36 #define HTS221_H1_T0_OUT 0x3a #define HTS221_T0_OUT 0x3c #define HTS221_T1_OUT 0x3e //---------------------------------------------------------- // // HTU21D // HTU21D I2C Slave Address #define HTU21D_ADDRESS 0x40 // Register map #define HTU21D_CMD_TRIG_TEMP 0xf3 #define HTU21D_CMD_TRIG_HUM 0xf5 #define HTU21D_WRITE_USER_REG 0xe6 #define HTU21D_READ_USER_REG 0xe7 #define HTU21D_CMD_SOFT_RESET 0xfe #endif // _RTHUMIDITYDEFS_H rtimulib-7.2.1/RTIMULib/IMUDrivers/RTHumidityHTS221.cpp000066400000000000000000000126071254201074400222470ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTHumidityHTS221.h" #include "RTHumidityDefs.h" RTHumidityHTS221::RTHumidityHTS221(RTIMUSettings *settings) : RTHumidity(settings) { m_humidityValid = false; m_temperatureValid = false; } RTHumidityHTS221::~RTHumidityHTS221() { } bool RTHumidityHTS221::humidityInit() { unsigned char rawData[2]; uint8_t H0_H_2 = 0; uint8_t H1_H_2 = 0; uint16_t T0_C_8 = 0; uint16_t T1_C_8 = 0; int16_t H0_T0_OUT = 0; int16_t H1_T0_OUT = 0; int16_t T0_OUT = 0; int16_t T1_OUT = 0; float H0, H1, T0, T1; m_humidityAddr = m_settings->m_I2CHumidityAddress; if (!m_settings->HALWrite(m_humidityAddr, HTS221_CTRL1, 0x87, "Failed to set HTS221 CTRL_REG_1")) return false; if (!m_settings->HALWrite(m_humidityAddr, HTS221_AV_CONF, 0x1b, "Failed to set HTS221 AV_CONF")) return false; // Get calibration data if (!m_settings->HALRead(m_humidityAddr, HTS221_T1_T0 + 0x80, 1, &rawData[1], "Failed to read HTS221 T1_T0")) return false; if (!m_settings->HALRead(m_humidityAddr, HTS221_T0_C_8 + 0x80, 1, rawData, "Failed to read HTS221 T0_C_8")) return false; T0_C_8 = (((unsigned int)rawData[1] & 0x3 ) << 8) | (unsigned int)rawData[0]; T0 = (RTFLOAT)T0_C_8 / 8; if (!m_settings->HALRead(m_humidityAddr, HTS221_T1_C_8 + 0x80, 1, rawData, "Failed to read HTS221 T1_C_8")) return false; T1_C_8 = (unsigned int)(((uint16_t)(rawData[1] & 0xC) << 6) | (uint16_t)rawData[0]); T1 = (RTFLOAT)T1_C_8 / 8; if (!m_settings->HALRead(m_humidityAddr, HTS221_T0_OUT + 0x80, 2, rawData, "Failed to read HTS221 T0_OUT")) return false; T0_OUT = (int16_t)(((unsigned int)rawData[1]) << 8) | (unsigned int)rawData[0]; if (!m_settings->HALRead(m_humidityAddr, HTS221_T1_OUT + 0x80, 2, rawData, "Failed to read HTS221 T1_OUT")) return false; T1_OUT = (int16_t)(((unsigned int)rawData[1]) << 8) | (unsigned int)rawData[0]; if (!m_settings->HALRead(m_humidityAddr, HTS221_H0_H_2 + 0x80, 1, &H0_H_2, "Failed to read HTS221 H0_H_2")) return false; H0 = (RTFLOAT)H0_H_2 / 2; if (!m_settings->HALRead(m_humidityAddr, HTS221_H1_H_2 + 0x80, 1, &H1_H_2, "Failed to read HTS221 H1_H_2")) return false; H1 = (RTFLOAT)H1_H_2 / 2; if (!m_settings->HALRead(m_humidityAddr, HTS221_H0_T0_OUT + 0x80, 2, rawData, "Failed to read HTS221 H0_T_OUT")) return false; H0_T0_OUT = (int16_t)(((unsigned int)rawData[1]) << 8) | (unsigned int)rawData[0]; if (!m_settings->HALRead(m_humidityAddr, HTS221_H1_T0_OUT + 0x80, 2, rawData, "Failed to read HTS221 H1_T_OUT")) return false; H1_T0_OUT = (int16_t)(((unsigned int)rawData[1]) << 8) | (unsigned int)rawData[0]; m_temperature_m = (T1-T0)/(T1_OUT-T0_OUT); m_temperature_c = T0-(m_temperature_m*T0_OUT); m_humidity_m = (H1-H0)/(H1_T0_OUT-H0_T0_OUT); m_humidity_c = (H0)-(m_humidity_m*H0_T0_OUT); return true; } bool RTHumidityHTS221::humidityRead(RTIMU_DATA& data) { unsigned char rawData[2]; unsigned char status; data.humidityValid = false; data.temperatureValid = false; data.temperature = 0; data.humidity = 0; if (!m_settings->HALRead(m_humidityAddr, HTS221_STATUS, 1, &status, "Failed to read HTS221 status")) return false; if (status & 2) { if (!m_settings->HALRead(m_humidityAddr, HTS221_HUMIDITY_OUT_L + 0x80, 2, rawData, "Failed to read HTS221 humidity")) return false; m_humidity = (int16_t)((((unsigned int)rawData[1]) << 8) | (unsigned int)rawData[0]); m_humidity = m_humidity * m_humidity_m + m_humidity_c; m_humidityValid = true; } if (status & 1) { if (!m_settings->HALRead(m_humidityAddr, HTS221_TEMP_OUT_L + 0x80, 2, rawData, "Failed to read HTS221 temperature")) return false; m_temperature = (int16_t)((((unsigned int)rawData[1]) << 8) | (unsigned int)rawData[0]); m_temperature = m_temperature * m_temperature_m + m_temperature_c; m_temperatureValid = true; } data.humidityValid = m_humidityValid; data.humidity = m_humidity; data.temperatureValid = m_temperatureValid; data.temperature = m_temperature; return true; } rtimulib-7.2.1/RTIMULib/IMUDrivers/RTHumidityHTS221.h000066400000000000000000000045631254201074400217160ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTHUMIDITYHTS221_H_ #define _RTHUMIDITYHTS221_H_ #include "RTHumidity.h" class RTIMUSettings; class RTHumidityHTS221 : public RTHumidity { public: RTHumidityHTS221(RTIMUSettings *settings); ~RTHumidityHTS221(); virtual const char *humidityName() { return "HTS221"; } virtual int humidityType() { return RTHUMIDITY_TYPE_HTS221; } virtual bool humidityInit(); virtual bool humidityRead(RTIMU_DATA& data); private: unsigned char m_humidityAddr; // I2C address RTFLOAT m_humidity; // the current humidity RTFLOAT m_temperature; // the current temperature RTFLOAT m_temperature_m; // temperature calibration slope RTFLOAT m_temperature_c; // temperature calibration y intercept RTFLOAT m_humidity_m; // humidity calibration slope RTFLOAT m_humidity_c; // humidity calibration y intercept bool m_humidityValid; bool m_temperatureValid; }; #endif // _RTHUMIDITYHTS221_H_ rtimulib-7.2.1/RTIMULib/IMUDrivers/RTHumidityHTU21D.cpp000066400000000000000000000106271254201074400222730ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTHumidityHTU21D.h" #include "RTHumidityDefs.h" #define HTU21D_STATE_IN_RESET 0 // reset in progress #define HTU21D_STATE_IDLE 1 // nothing happening #define HTU21D_STATE_TEMP_REQ 2 // requested temperature #define HTU21D_STATE_HUM_REQ 3 // requested humidity #define HTU21D_STATE_INTERVAL 100000 // the interval between state changes RTHumidityHTU21D::RTHumidityHTU21D(RTIMUSettings *settings) : RTHumidity(settings) { m_humidityValid = false; m_temperatureValid = false; m_humidity = 0; m_temperature = 0; } RTHumidityHTU21D::~RTHumidityHTU21D() { } bool RTHumidityHTU21D::humidityInit() { m_humidityAddr = m_settings->m_I2CHumidityAddress; if (!m_settings->HALWrite(m_humidityAddr, HTU21D_CMD_SOFT_RESET, 0, NULL, "Failed to reset HTU21D")) return false; m_state = HTU21D_STATE_IN_RESET; m_startTime = RTMath::currentUSecsSinceEpoch(); return true; } bool RTHumidityHTU21D::humidityRead(RTIMU_DATA& data) { if (!processBackground()) return false; data.humidityValid = m_humidityValid; data.humidity = m_humidity; data.temperatureValid = m_temperatureValid; data.temperature = m_temperature; return true; } bool RTHumidityHTU21D:: processBackground() { unsigned char rawData[3]; uint64_t now = RTMath::currentUSecsSinceEpoch(); bool expired = (now - m_startTime) >= HTU21D_STATE_INTERVAL; if (!expired) return true; switch (m_state) { case HTU21D_STATE_IN_RESET: m_state = HTU21D_STATE_IDLE; m_startTime = now; break; case HTU21D_STATE_IDLE: // start a temperature conversion if (!m_settings->HALWrite(m_humidityAddr, HTU21D_CMD_TRIG_TEMP, 0, NULL, "Failed to start HTU21D temp conv")) return false; m_state = HTU21D_STATE_TEMP_REQ; m_startTime = now; break; case HTU21D_STATE_TEMP_REQ: // read temp data if (!m_settings->HALRead(m_humidityAddr, 3, rawData, "Failed to read HTU21D temperature")) return false; // remove status bits rawData[1] &= 0xfc; m_temperature = -46.85 + 175.72 * (RTFLOAT)((((uint16_t)rawData[0]) << 8) | (uint16_t)rawData[1]) / 65536.0; m_temperatureValid = true; // start humidity conversion if (!m_settings->HALWrite(m_humidityAddr, HTU21D_CMD_TRIG_HUM, 0, NULL, "Failed to start HTU21D humidity conv")) return false; m_state = HTU21D_STATE_HUM_REQ; m_startTime = now; break; case HTU21D_STATE_HUM_REQ: // read humidity data if (!m_settings->HALRead(m_humidityAddr, 3, rawData, "Failed to read HTU21D humidity")) return false; // remove status bits rawData[1] &= 0xfc; m_humidity = -6.0 + 125.0 * (RTFLOAT)((((uint16_t)rawData[0]) << 8) | (uint16_t)rawData[1]) / 65536.0; // do temp compensation m_humidity += (25.0 - m_temperature) * -0.15; m_humidityValid = true; m_state = HTU21D_STATE_IDLE; m_startTime = now; break; } return true; } rtimulib-7.2.1/RTIMULib/IMUDrivers/RTHumidityHTU21D.h000066400000000000000000000041031254201074400217300ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTHUMIDITYHTU21D_H_ #define _RTHUMIDITYHTU21D_H_ #include "RTHumidity.h" class RTIMUSettings; class RTHumidityHTU21D : public RTHumidity { public: RTHumidityHTU21D(RTIMUSettings *settings); ~RTHumidityHTU21D(); virtual const char *humidityName() { return "HTU21D"; } virtual int humidityType() { return RTHUMIDITY_TYPE_HTU21D; } virtual bool humidityInit(); virtual bool humidityRead(RTIMU_DATA& data); private: bool processBackground(); unsigned char m_humidityAddr; // I2C address int m_state; uint64_t m_startTime; RTFLOAT m_humidity; // the current humidity RTFLOAT m_temperature; // the current temperature bool m_humidityValid; bool m_temperatureValid; }; #endif // _RTHUMIDITYHTU21D_H_ rtimulib-7.2.1/RTIMULib/IMUDrivers/RTIMU.cpp000066400000000000000000000345261254201074400203050ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech // // Permission 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. #include "RTIMU.h" #include "RTFusionKalman4.h" #include "RTFusionRTQF.h" #include "RTIMUNull.h" #include "RTIMUMPU9150.h" #include "RTIMUMPU9250.h" #include "RTIMUGD20HM303D.h" #include "RTIMUGD20M303DLHC.h" #include "RTIMUGD20HM303DLHC.h" #include "RTIMULSM9DS0.h" #include "RTIMULSM9DS1.h" #include "RTIMUBMX055.h" #include "RTIMUBNO055.h" // this sets the learning rate for compass running average calculation #define COMPASS_ALPHA 0.2f // this defines the accelerometer noise level #define RTIMU_FUZZY_GYRO_ZERO 0.20 // this defines the accelerometer noise level #define RTIMU_FUZZY_ACCEL_ZERO 0.05 // Axis rotation arrays float RTIMU::m_axisRotation[RTIMU_AXIS_ROTATION_COUNT][9] = { {1, 0, 0, 0, 1, 0, 0, 0, 1}, // RTIMU_XNORTH_YEAST {0, -1, 0, 1, 0, 0, 0, 0, 1}, // RTIMU_XEAST_YSOUTH {-1, 0, 0, 0, -1, 0, 0, 0, 1}, // RTIMU_XSOUTH_YWEST {0, 1, 0, -1, 0, 0, 0, 0, 1}, // RTIMU_XWEST_YNORTH {1, 0, 0, 0, -1, 0, 0, 0, -1}, // RTIMU_XNORTH_YWEST {0, 1, 0, 1, 0, 0, 0, 0, -1}, // RTIMU_XEAST_YNORTH {-1, 0, 0, 0, 1, 0, 0, 0, -1}, // RTIMU_XSOUTH_YEAST {0, -1, 0, -1, 0, 0, 0, 0, -1}, // RTIMU_XWEST_YSOUTH {0, 1, 0, 0, 0, -1, -1, 0, 0}, // RTIMU_XUP_YNORTH {0, 0, 1, 0, 1, 0, -1, 0, 0}, // RTIMU_XUP_YEAST {0, -1, 0, 0, 0, 1, -1, 0, 0}, // RTIMU_XUP_YSOUTH {0, 0, -1, 0, -1, 0, -1, 0, 0}, // RTIMU_XUP_YWEST {0, 1, 0, 0, 0, 1, 1, 0, 0}, // RTIMU_XDOWN_YNORTH {0, 0, -1, 0, 1, 0, 1, 0, 0}, // RTIMU_XDOWN_YEAST {0, -1, 0, 0, 0, -1, 1, 0, 0}, // RTIMU_XDOWN_YSOUTH {0, 0, 1, 0, -1, 0, 1, 0, 0}, // RTIMU_XDOWN_YWEST {1, 0, 0, 0, 0, 1, 0, -1, 0}, // RTIMU_XNORTH_YUP {0, 0, -1, 1, 0, 0, 0, -1, 0}, // RTIMU_XEAST_YUP {-1, 0, 0, 0, 0, -1, 0, -1, 0}, // RTIMU_XSOUTH_YUP {0, 0, 1, -1, 0, 0, 0, -1, 0}, // RTIMU_XWEST_YUP {1, 0, 0, 0, 0, -1, 0, 1, 0}, // RTIMU_XNORTH_YDOWN {0, 0, 1, 1, 0, 0, 0, 1, 0}, // RTIMU_XEAST_YDOWN {-1, 0, 0, 0, 0, 1, 0, 1, 0}, // RTIMU_XSOUTH_YDOWN {0, 0, -1, -1, 0, 0, 0, 1, 0} // RTIMU_XWEST_YDOWN }; RTIMU *RTIMU::createIMU(RTIMUSettings *settings) { switch (settings->m_imuType) { case RTIMU_TYPE_MPU9150: return new RTIMUMPU9150(settings); case RTIMU_TYPE_GD20HM303D: return new RTIMUGD20HM303D(settings); case RTIMU_TYPE_GD20M303DLHC: return new RTIMUGD20M303DLHC(settings); case RTIMU_TYPE_LSM9DS0: return new RTIMULSM9DS0(settings); case RTIMU_TYPE_LSM9DS1: return new RTIMULSM9DS1(settings); case RTIMU_TYPE_MPU9250: return new RTIMUMPU9250(settings); case RTIMU_TYPE_GD20HM303DLHC: return new RTIMUGD20HM303DLHC(settings); case RTIMU_TYPE_BMX055: return new RTIMUBMX055(settings); case RTIMU_TYPE_BNO055: return new RTIMUBNO055(settings); case RTIMU_TYPE_AUTODISCOVER: if (settings->discoverIMU(settings->m_imuType, settings->m_busIsI2C, settings->m_I2CSlaveAddress)) { settings->saveSettings(); return RTIMU::createIMU(settings); } return new RTIMUNull(settings); case RTIMU_TYPE_NULL: return new RTIMUNull(settings); default: return NULL; } } RTIMU::RTIMU(RTIMUSettings *settings) { m_settings = settings; m_compassCalibrationMode = false; m_accelCalibrationMode = false; switch (m_settings->m_fusionType) { case RTFUSION_TYPE_KALMANSTATE4: m_fusion = new RTFusionKalman4(); break; case RTFUSION_TYPE_RTQF: m_fusion = new RTFusionRTQF(); break; default: m_fusion = new RTFusion(); break; } HAL_INFO1("Using fusion algorithm %s\n", RTFusion::fusionName(m_settings->m_fusionType)); } RTIMU::~RTIMU() { delete m_fusion; m_fusion = NULL; } void RTIMU::setCalibrationData() { float maxDelta = -1; float delta; if (m_settings->m_compassCalValid) { // find biggest range for (int i = 0; i < 3; i++) { if ((m_settings->m_compassCalMax.data(i) - m_settings->m_compassCalMin.data(i)) > maxDelta) maxDelta = m_settings->m_compassCalMax.data(i) - m_settings->m_compassCalMin.data(i); } if (maxDelta < 0) { HAL_ERROR("Error in compass calibration data\n"); return; } maxDelta /= 2.0f; // this is the max +/- range for (int i = 0; i < 3; i++) { delta = (m_settings->m_compassCalMax.data(i) - m_settings->m_compassCalMin.data(i)) / 2.0f; m_compassCalScale[i] = maxDelta / delta; // makes everything the same range m_compassCalOffset[i] = (m_settings->m_compassCalMax.data(i) + m_settings->m_compassCalMin.data(i)) / 2.0f; } } if (m_settings->m_compassCalValid) { HAL_INFO("Using min/max compass calibration\n"); } else { HAL_INFO("min/max compass calibration not in use\n"); } if (m_settings->m_compassCalEllipsoidValid) { HAL_INFO("Using ellipsoid compass calibration\n"); } else { HAL_INFO("Ellipsoid compass calibration not in use\n"); } if (m_settings->m_accelCalValid) { HAL_INFO("Using accel calibration\n"); } else { HAL_INFO("Accel calibration not in use\n"); } } bool RTIMU::setGyroContinuousLearningAlpha(RTFLOAT alpha) { if ((alpha < 0.0) || (alpha >= 1.0)) return false; m_gyroContinuousAlpha = alpha; return true; } void RTIMU::gyroBiasInit() { m_gyroLearningAlpha = 2.0f / m_sampleRate; m_gyroContinuousAlpha = 0.01f / m_sampleRate; m_gyroSampleCount = 0; } // Note - code assumes that this is the first thing called after axis swapping // for each specific IMU chip has occurred. void RTIMU::handleGyroBias() { // do axis rotation if ((m_settings->m_axisRotation > 0) && (m_settings->m_axisRotation < RTIMU_AXIS_ROTATION_COUNT)) { // need to do an axis rotation float *matrix = m_axisRotation[m_settings->m_axisRotation]; RTIMU_DATA tempIMU = m_imuData; // do new x value if (matrix[0] != 0) { m_imuData.gyro.setX(tempIMU.gyro.x() * matrix[0]); m_imuData.accel.setX(tempIMU.accel.x() * matrix[0]); m_imuData.compass.setX(tempIMU.compass.x() * matrix[0]); } else if (matrix[1] != 0) { m_imuData.gyro.setX(tempIMU.gyro.y() * matrix[1]); m_imuData.accel.setX(tempIMU.accel.y() * matrix[1]); m_imuData.compass.setX(tempIMU.compass.y() * matrix[1]); } else if (matrix[2] != 0) { m_imuData.gyro.setX(tempIMU.gyro.z() * matrix[2]); m_imuData.accel.setX(tempIMU.accel.z() * matrix[2]); m_imuData.compass.setX(tempIMU.compass.z() * matrix[2]); } // do new y value if (matrix[3] != 0) { m_imuData.gyro.setY(tempIMU.gyro.x() * matrix[3]); m_imuData.accel.setY(tempIMU.accel.x() * matrix[3]); m_imuData.compass.setY(tempIMU.compass.x() * matrix[3]); } else if (matrix[4] != 0) { m_imuData.gyro.setY(tempIMU.gyro.y() * matrix[4]); m_imuData.accel.setY(tempIMU.accel.y() * matrix[4]); m_imuData.compass.setY(tempIMU.compass.y() * matrix[4]); } else if (matrix[5] != 0) { m_imuData.gyro.setY(tempIMU.gyro.z() * matrix[5]); m_imuData.accel.setY(tempIMU.accel.z() * matrix[5]); m_imuData.compass.setY(tempIMU.compass.z() * matrix[5]); } // do new z value if (matrix[6] != 0) { m_imuData.gyro.setZ(tempIMU.gyro.x() * matrix[6]); m_imuData.accel.setZ(tempIMU.accel.x() * matrix[6]); m_imuData.compass.setZ(tempIMU.compass.x() * matrix[6]); } else if (matrix[7] != 0) { m_imuData.gyro.setZ(tempIMU.gyro.y() * matrix[7]); m_imuData.accel.setZ(tempIMU.accel.y() * matrix[7]); m_imuData.compass.setZ(tempIMU.compass.y() * matrix[7]); } else if (matrix[8] != 0) { m_imuData.gyro.setZ(tempIMU.gyro.z() * matrix[8]); m_imuData.accel.setZ(tempIMU.accel.z() * matrix[8]); m_imuData.compass.setZ(tempIMU.compass.z() * matrix[8]); } } RTVector3 deltaAccel = m_previousAccel; deltaAccel -= m_imuData.accel; // compute difference m_previousAccel = m_imuData.accel; if ((deltaAccel.length() < RTIMU_FUZZY_ACCEL_ZERO) && (m_imuData.gyro.length() < RTIMU_FUZZY_GYRO_ZERO)) { // what we are seeing on the gyros should be bias only so learn from this if (m_gyroSampleCount < (5 * m_sampleRate)) { m_settings->m_gyroBias.setX((1.0 - m_gyroLearningAlpha) * m_settings->m_gyroBias.x() + m_gyroLearningAlpha * m_imuData.gyro.x()); m_settings->m_gyroBias.setY((1.0 - m_gyroLearningAlpha) * m_settings->m_gyroBias.y() + m_gyroLearningAlpha * m_imuData.gyro.y()); m_settings->m_gyroBias.setZ((1.0 - m_gyroLearningAlpha) * m_settings->m_gyroBias.z() + m_gyroLearningAlpha * m_imuData.gyro.z()); m_gyroSampleCount++; if (m_gyroSampleCount == (5 * m_sampleRate)) { // this could have been true already of course m_settings->m_gyroBiasValid = true; m_settings->saveSettings(); } } else { m_settings->m_gyroBias.setX((1.0 - m_gyroContinuousAlpha) * m_settings->m_gyroBias.x() + m_gyroContinuousAlpha * m_imuData.gyro.x()); m_settings->m_gyroBias.setY((1.0 - m_gyroContinuousAlpha) * m_settings->m_gyroBias.y() + m_gyroContinuousAlpha * m_imuData.gyro.y()); m_settings->m_gyroBias.setZ((1.0 - m_gyroContinuousAlpha) * m_settings->m_gyroBias.z() + m_gyroContinuousAlpha * m_imuData.gyro.z()); } } m_imuData.gyro -= m_settings->m_gyroBias; } void RTIMU::calibrateAverageCompass() { // calibrate if required if (getCompassCalibrationValid()) { m_imuData.compass.setX((m_imuData.compass.x() - m_compassCalOffset[0]) * m_compassCalScale[0]); m_imuData.compass.setY((m_imuData.compass.y() - m_compassCalOffset[1]) * m_compassCalScale[1]); m_imuData.compass.setZ((m_imuData.compass.z() - m_compassCalOffset[2]) * m_compassCalScale[2]); if (m_settings->m_compassCalEllipsoidValid) { RTVector3 ev = m_imuData.compass; ev -= m_settings->m_compassCalEllipsoidOffset; m_imuData.compass.setX(ev.x() * m_settings->m_compassCalEllipsoidCorr[0][0] + ev.y() * m_settings->m_compassCalEllipsoidCorr[0][1] + ev.z() * m_settings->m_compassCalEllipsoidCorr[0][2]); m_imuData.compass.setY(ev.x() * m_settings->m_compassCalEllipsoidCorr[1][0] + ev.y() * m_settings->m_compassCalEllipsoidCorr[1][1] + ev.z() * m_settings->m_compassCalEllipsoidCorr[1][2]); m_imuData.compass.setZ(ev.x() * m_settings->m_compassCalEllipsoidCorr[2][0] + ev.y() * m_settings->m_compassCalEllipsoidCorr[2][1] + ev.z() * m_settings->m_compassCalEllipsoidCorr[2][2]); } } // update running average m_compassAverage.setX(m_imuData.compass.x() * COMPASS_ALPHA + m_compassAverage.x() * (1.0 - COMPASS_ALPHA)); m_compassAverage.setY(m_imuData.compass.y() * COMPASS_ALPHA + m_compassAverage.y() * (1.0 - COMPASS_ALPHA)); m_compassAverage.setZ(m_imuData.compass.z() * COMPASS_ALPHA + m_compassAverage.z() * (1.0 - COMPASS_ALPHA)); m_imuData.compass = m_compassAverage; } void RTIMU::calibrateAccel() { if (!getAccelCalibrationValid()) return; if (m_imuData.accel.x() >= 0) m_imuData.accel.setX(m_imuData.accel.x() / m_settings->m_accelCalMax.x()); else m_imuData.accel.setX(m_imuData.accel.x() / -m_settings->m_accelCalMin.x()); if (m_imuData.accel.y() >= 0) m_imuData.accel.setY(m_imuData.accel.y() / m_settings->m_accelCalMax.y()); else m_imuData.accel.setY(m_imuData.accel.y() / -m_settings->m_accelCalMin.y()); if (m_imuData.accel.z() >= 0) m_imuData.accel.setZ(m_imuData.accel.z() / m_settings->m_accelCalMax.z()); else m_imuData.accel.setZ(m_imuData.accel.z() / -m_settings->m_accelCalMin.z()); } void RTIMU::updateFusion() { m_fusion->newIMUData(m_imuData, m_settings); } bool RTIMU::IMUGyroBiasValid() { return m_settings->m_gyroBiasValid; } void RTIMU::setExtIMUData(RTFLOAT gx, RTFLOAT gy, RTFLOAT gz, RTFLOAT ax, RTFLOAT ay, RTFLOAT az, RTFLOAT mx, RTFLOAT my, RTFLOAT mz, uint64_t timestamp) { m_imuData.gyro.setX(gx); m_imuData.gyro.setY(gy); m_imuData.gyro.setZ(gz); m_imuData.accel.setX(ax); m_imuData.accel.setY(ay); m_imuData.accel.setZ(az); m_imuData.compass.setX(mx); m_imuData.compass.setY(my); m_imuData.compass.setZ(mz); m_imuData.timestamp = timestamp; updateFusion(); } rtimulib-7.2.1/RTIMULib/IMUDrivers/RTIMU.h000066400000000000000000000207351254201074400177470ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTIMU_H #define _RTIMU_H #include "RTMath.h" #include "RTFusion.h" #include "RTIMULibDefs.h" #include "RTIMUSettings.h" // Axis rotation defs // // These allow the IMU to be virtually repositioned if it is in a non-standard configuration // Standard configuration is X pointing at north, Y pointing east and Z pointing down // with the IMU horizontal. There are 24 different possible orientations as defined // below. Setting the axis rotation code to non-zero values performs the repositioning. #define RTIMU_XNORTH_YEAST 0 // this is the default identity matrix #define RTIMU_XEAST_YSOUTH 1 #define RTIMU_XSOUTH_YWEST 2 #define RTIMU_XWEST_YNORTH 3 #define RTIMU_XNORTH_YWEST 4 #define RTIMU_XEAST_YNORTH 5 #define RTIMU_XSOUTH_YEAST 6 #define RTIMU_XWEST_YSOUTH 7 #define RTIMU_XUP_YNORTH 8 #define RTIMU_XUP_YEAST 9 #define RTIMU_XUP_YSOUTH 10 #define RTIMU_XUP_YWEST 11 #define RTIMU_XDOWN_YNORTH 12 #define RTIMU_XDOWN_YEAST 13 #define RTIMU_XDOWN_YSOUTH 14 #define RTIMU_XDOWN_YWEST 15 #define RTIMU_XNORTH_YUP 16 #define RTIMU_XEAST_YUP 17 #define RTIMU_XSOUTH_YUP 18 #define RTIMU_XWEST_YUP 19 #define RTIMU_XNORTH_YDOWN 20 #define RTIMU_XEAST_YDOWN 21 #define RTIMU_XSOUTH_YDOWN 22 #define RTIMU_XWEST_YDOWN 23 #define RTIMU_AXIS_ROTATION_COUNT 24 class RTIMU { public: // IMUs should always be created with the following call static RTIMU *createIMU(RTIMUSettings *settings); // Constructor/destructor RTIMU(RTIMUSettings *settings); virtual ~RTIMU(); // These functions must be provided by sub classes virtual const char *IMUName() = 0; // the name of the IMU virtual int IMUType() = 0; // the type code of the IMU virtual bool IMUInit() = 0; // set up the IMU virtual int IMUGetPollInterval() = 0; // returns the recommended poll interval in mS virtual bool IMURead() = 0; // get a sample // setGyroContinuousALearninglpha allows the continuous learning rate to be over-ridden // The value must be between 0.0 and 1.0 and will generally be close to 0 bool setGyroContinuousLearningAlpha(RTFLOAT alpha); // returns true if enough samples for valid data virtual bool IMUGyroBiasValid(); // the following function can be called to set the SLERP power void setSlerpPower(RTFLOAT power) { m_fusion->setSlerpPower(power); } // call the following to reset the fusion algorithm void resetFusion() { m_fusion->reset(); } // the following three functions control the influence of the gyro, accel and compass sensors void setGyroEnable(bool enable) { m_fusion->setGyroEnable(enable);} void setAccelEnable(bool enable) { m_fusion->setAccelEnable(enable);} void setCompassEnable(bool enable) { m_fusion->setCompassEnable(enable);} // call the following to enable debug messages void setDebugEnable(bool enable) { m_fusion->setDebugEnable(enable); } // getIMUData returns the standard outputs of the IMU and fusion filter const RTIMU_DATA& getIMUData() { return m_imuData; } // setExtIMUData allows data from some external IMU to be injected to the fusion algorithm void setExtIMUData(RTFLOAT gx, RTFLOAT gy, RTFLOAT gz, RTFLOAT ax, RTFLOAT ay, RTFLOAT az, RTFLOAT mx, RTFLOAT my, RTFLOAT mz, uint64_t timestamp); // the following two functions get access to the measured pose (accel and compass) const RTVector3& getMeasuredPose() { return m_fusion->getMeasuredPose(); } const RTQuaternion& getMeasuredQPose() { return m_fusion->getMeasuredQPose(); } // setCompassCalibrationMode() turns off use of cal data so that raw data can be accumulated // to derive calibration data void setCompassCalibrationMode(bool enable) { m_compassCalibrationMode = enable; } // setAccelCalibrationMode() turns off use of cal data so that raw data can be accumulated // to derive calibration data void setAccelCalibrationMode(bool enable) { m_accelCalibrationMode = enable; } // setCalibrationData configures the cal data from settings and also enables use if valid void setCalibrationData(); // getCompassCalibrationValid() returns true if the compass min/max calibration data is being used bool getCompassCalibrationValid() { return !m_compassCalibrationMode && m_settings->m_compassCalValid; } // getCompassCalibrationEllipsoidValid() returns true if the compass ellipsoid calibration data is being used bool getCompassCalibrationEllipsoidValid() { return !m_compassCalibrationMode && m_settings->m_compassCalEllipsoidValid; } // getAccelCalibrationValid() returns true if the accel calibration data is being used bool getAccelCalibrationValid() { return !m_accelCalibrationMode && m_settings->m_accelCalValid; } const RTVector3& getGyro() { return m_imuData.gyro; } // gets gyro rates in radians/sec const RTVector3& getAccel() { return m_imuData.accel; } // get accel data in gs const RTVector3& getCompass() { return m_imuData.compass; } // gets compass data in uT RTVector3 getAccelResiduals() { return m_fusion->getAccelResiduals(); } protected: void gyroBiasInit(); // sets up gyro bias calculation void handleGyroBias(); // adjust gyro for bias void calibrateAverageCompass(); // calibrate and smooth compass void calibrateAccel(); // calibrate the accelerometers void updateFusion(); // call when new data to update fusion state bool m_compassCalibrationMode; // true if cal mode so don't use cal data! bool m_accelCalibrationMode; // true if cal mode so don't use cal data! RTIMU_DATA m_imuData; // the data from the IMU RTIMUSettings *m_settings; // the settings object pointer RTFusion *m_fusion; // the fusion algorithm int m_sampleRate; // samples per second uint64_t m_sampleInterval; // interval between samples in microseonds RTFLOAT m_gyroLearningAlpha; // gyro bias rapid learning rate RTFLOAT m_gyroContinuousAlpha; // gyro bias continuous (slow) learning rate int m_gyroSampleCount; // number of gyro samples used RTVector3 m_previousAccel; // previous step accel for gyro learning float m_compassCalOffset[3]; float m_compassCalScale[3]; RTVector3 m_compassAverage; // a running average to smooth the mag outputs static float m_axisRotation[RTIMU_AXIS_ROTATION_COUNT][9]; // array of rotation matrices }; #endif // _RTIMU_H rtimulib-7.2.1/RTIMULib/IMUDrivers/RTIMUBMX055.cpp000066400000000000000000000757201254201074400211070ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. // Some code from Bosch BMM150 API driver: /* **************************************************************************** * Copyright (C) 2011 - 2014 Bosch Sensortec GmbH * * bmm050.c * Date: 2014/12/12 * Revision: 2.0.3 $ * * Usage: Sensor Driver for BMM050 and BMM150 sensor * **************************************************************************** * License: * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of the copyright holder nor the names of the * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER * OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE * * The information provided is believed to be accurate and reliable. * The copyright holder assumes no responsibility * for the consequences of use * of such information nor for any infringement of patents or * other rights of third parties which may result from its use. * No license is granted by implication or otherwise under any patent or * patent rights of the copyright holder. **************************************************************************/ /****************************************************************************/ #include "RTIMUBMX055.h" #include "RTIMUSettings.h" // this sets the learning rate for compass running average calculation #define COMPASS_ALPHA 0.2f RTIMUBMX055::RTIMUBMX055(RTIMUSettings *settings) : RTIMU(settings) { m_sampleRate = 100; m_sampleInterval = (uint64_t)1000000 / m_sampleRate; } RTIMUBMX055::~RTIMUBMX055() { } bool RTIMUBMX055::IMUInit() { unsigned char result; m_firstTime = true; // set validity flags m_imuData.fusionPoseValid = false; m_imuData.fusionQPoseValid = false; m_imuData.gyroValid = true; m_imuData.accelValid = true; m_imuData.compassValid = true; m_imuData.pressureValid = false; m_imuData.temperatureValid = false; m_imuData.humidityValid = false; // configure IMU m_gyroSlaveAddr = m_settings->m_I2CSlaveAddress; if (!m_settings->HALRead(m_gyroSlaveAddr, BMX055_GYRO_WHO_AM_I, 1, &result, "Failed to read BMX055 gyro id")) return false; if (result != BMX055_GYRO_ID) { HAL_ERROR1("Incorrect BMX055 id %d\n", result); return false; } // work out accel address if (m_settings->HALRead(BMX055_ACCEL_ADDRESS0, BMX055_ACCEL_WHO_AM_I, 1, &result, "")) { if (result == BMX055_ACCEL_ID) { m_accelSlaveAddr = BMX055_ACCEL_ADDRESS0; } else { m_accelSlaveAddr = BMX055_ACCEL_ADDRESS1; } } // work out mag address int magAddr; for (magAddr = BMX055_MAG_ADDRESS0; magAddr <= BMX055_MAG_ADDRESS3; magAddr++) { // have to enable chip to get id... m_settings->HALWrite(magAddr, BMX055_MAG_POWER, 1, ""); m_settings->delayMs(50); if (m_settings->HALRead(magAddr, BMX055_MAG_WHO_AM_I, 1, &result, "")) { if (result == BMX055_MAG_ID) { m_magSlaveAddr = magAddr; break; } } } if (magAddr > BMX055_MAG_ADDRESS3) { HAL_ERROR("Failed to find BMX055 mag\n"); return false; } setCalibrationData(); // enable the I2C bus if (!m_settings->HALOpen()) return false; // Set up the gyro if (!m_settings->HALWrite(m_gyroSlaveAddr, BMX055_GYRO_FIFO_CONFIG_1, 0x40, "Failed to set BMX055 FIFO config")) return false; if (!setGyroSampleRate()) return false; if (!setGyroFSR()) return false; gyroBiasInit(); // set up the accel if (!setAccelSampleRate()) return false; if (!setAccelFSR()) return false; // set up the mag magInitTrimRegisters(); setMagPreset(); HAL_INFO("BMX055 init complete\n"); return true; } bool RTIMUBMX055::setGyroSampleRate() { unsigned char bw; switch (m_settings->m_BMX055GyroSampleRate) { case BMX055_GYRO_SAMPLERATE_2000_523: bw = 0; m_sampleRate = 2000; break; case BMX055_GYRO_SAMPLERATE_2000_230: bw = 1; m_sampleRate = 2000; break; case BMX055_GYRO_SAMPLERATE_1000_116: bw = 2; m_sampleRate = 1000; break; case BMX055_GYRO_SAMPLERATE_400_47: bw = 3; m_sampleRate = 400; break; case BMX055_GYRO_SAMPLERATE_200_23: bw = 4; m_sampleRate = 200; break; case BMX055_GYRO_SAMPLERATE_100_12: bw = 5; m_sampleRate = 100; break; case BMX055_GYRO_SAMPLERATE_200_64: bw = 6; m_sampleRate = 200; break; case BMX055_GYRO_SAMPLERATE_100_32: bw = 7; m_sampleRate = 100; break; default: HAL_ERROR1("Illegal BMX055 gyro sample rate code %d\n", m_settings->m_BMX055GyroSampleRate); return false; } m_sampleInterval = (uint64_t)1000000 / m_sampleRate; return (m_settings->HALWrite(m_gyroSlaveAddr, BMX055_GYRO_BW, bw, "Failed to set BMX055 gyro rate")); } bool RTIMUBMX055::setGyroFSR() { switch(m_settings->m_BMX055GyroFsr) { case BMX055_GYRO_FSR_2000: m_gyroScale = 0.061 * RTMATH_DEGREE_TO_RAD; break; case BMX055_GYRO_FSR_1000: m_gyroScale = 0.0305 * RTMATH_DEGREE_TO_RAD; break; case BMX055_GYRO_FSR_500: m_gyroScale = 0.0153 * RTMATH_DEGREE_TO_RAD; break; case BMX055_GYRO_FSR_250: m_gyroScale = 0.0076 * RTMATH_DEGREE_TO_RAD; break; case BMX055_GYRO_FSR_125: m_gyroScale = 0.0038 * RTMATH_DEGREE_TO_RAD; break; default: HAL_ERROR1("Illegal BMX055 gyro FSR code %d\n", m_settings->m_BMX055GyroFsr); return false; } return (m_settings->HALWrite(m_gyroSlaveAddr, BMX055_GYRO_RANGE, m_settings->m_BMX055GyroFsr, "Failed to set BMX055 gyro rate")); } bool RTIMUBMX055::setAccelSampleRate() { unsigned char reg; switch(m_settings->m_BMX055AccelSampleRate) { case BMX055_ACCEL_SAMPLERATE_15: reg = 0x08; break; case BMX055_ACCEL_SAMPLERATE_31: reg = 0x09; break; case BMX055_ACCEL_SAMPLERATE_62: reg = 0x0a; break; case BMX055_ACCEL_SAMPLERATE_125: reg = 0x0b; break; case BMX055_ACCEL_SAMPLERATE_250: reg = 0x0c; break; case BMX055_ACCEL_SAMPLERATE_500: reg = 0x0d; break; case BMX055_ACCEL_SAMPLERATE_1000: reg = 0x0e; break; case BMX055_ACCEL_SAMPLERATE_2000: reg = 0x0f; break; default: HAL_ERROR1("Illegal BMX055 accel FSR code %d\n", m_settings->m_BMX055AccelSampleRate); return false; } return (m_settings->HALWrite(m_accelSlaveAddr, BMX055_ACCEL_PMU_BW, reg, "Failed to set BMX055 accel rate")); } bool RTIMUBMX055::setAccelFSR() { unsigned char reg; switch(m_settings->m_BMX055AccelFsr) { case BMX055_ACCEL_FSR_2: reg = 0x03; m_accelScale = 0.00098 / 16.0; break; case BMX055_ACCEL_FSR_4: reg = 0x05; m_accelScale = 0.00195 / 16.0; break; case BMX055_ACCEL_FSR_8: reg = 0x08; m_accelScale = 0.00391 / 16.0; break; case BMX055_ACCEL_FSR_16: reg = 0x0c; m_accelScale = 0.00781 / 16.0; break; default: HAL_ERROR1("Illegal BMX055 accel FSR code %d\n", m_settings->m_BMX055AccelFsr); return false; } return (m_settings->HALWrite(m_accelSlaveAddr, BMX055_ACCEL_PMU_RANGE, reg, "Failed to set BMX055 accel rate")); } bool RTIMUBMX055::setMagPreset() { unsigned char mode, repXY, repZ; switch (m_settings->m_BMX055MagPreset) { case BMX055_MAG_LOW_POWER: // ODR=10, RepXY=3, RepZ=3 mode = 0; repXY = 1; repZ = 2; break; case BMX055_MAG_REGULAR: // ODR=10, RepXY=9, RepZ=15 mode = 0; repXY = 4; repZ = 14; break; case BMX055_MAG_ENHANCED: // ODR=10, RepXY=15, RepZ=27 mode = 0; repXY = 7; repZ = 26; break; case BMX055_MAG_HIGH_ACCURACY: // ODR=10, RepXY=47, RepZ=83 mode = 0; repXY = 23; repZ = 82; break; default: HAL_ERROR1("Illegal BMX055 mag preset code %d\n", m_settings->m_BMX055MagPreset); return false; } if (!m_settings->HALWrite(m_magSlaveAddr, BMX055_MAG_MODE, mode, "Failed to set BMX055 mag mode")) return false; if (!m_settings->HALWrite(m_magSlaveAddr, BMX055_MAG_REPXY, repXY, "Failed to set BMX055 mag repXY")) return false; if (!m_settings->HALWrite(m_magSlaveAddr, BMX055_MAG_REPZ, repZ, "Failed to set BMX055 mag repZ")) return false; return true; } int RTIMUBMX055::IMUGetPollInterval() { if (m_sampleRate > 400) return 1; else return (400 / m_sampleRate); } bool RTIMUBMX055::IMURead() { unsigned char status; unsigned char gyroData[6]; unsigned char accelData[6]; unsigned char magData[8]; if (!m_settings->HALRead(m_gyroSlaveAddr, BMX055_GYRO_FIFO_STATUS, 1, &status, "Failed to read BMX055 gyro fifo status")) return false; if (status & 0x80) { // fifo overflowed HAL_ERROR("BMX055 fifo overflow\n"); // this should clear it if (!m_settings->HALWrite(m_gyroSlaveAddr, BMX055_GYRO_FIFO_CONFIG_1, 0x40, "Failed to set BMX055 FIFO config")) return false; m_imuData.timestamp = RTMath::currentUSecsSinceEpoch(); // try to fix timestamp return false; } if (status == 0) return false; if (!m_settings->HALRead(m_gyroSlaveAddr, BMX055_GYRO_FIFO_DATA, 6, gyroData, "Failed to read BMX055 gyro data")) return false; if (!m_settings->HALRead(m_accelSlaveAddr, BMX055_ACCEL_X_LSB, 6, accelData, "Failed to read BMX055 accel data")) return false; if (!m_settings->HALRead(m_magSlaveAddr, BMX055_MAG_X_LSB, 8, magData, "Failed to read BMX055 mag data")) return false; RTMath::convertToVector(gyroData, m_imuData.gyro, m_gyroScale, false); // need to prepare accel data accelData[0] &= 0xf0; accelData[2] &= 0xf0; accelData[4] &= 0xf0; RTMath::convertToVector(accelData, m_imuData.accel, m_accelScale, false); float mx, my, mz; processMagData(magData, mx, my, mz); m_imuData.compass.setX(mx); m_imuData.compass.setY(my); m_imuData.compass.setZ(mz); // sort out gyro axes m_imuData.gyro.setY(-m_imuData.gyro.y()); m_imuData.gyro.setZ(-m_imuData.gyro.z()); // sort out accel axes m_imuData.accel.setX(-m_imuData.accel.x()); // sort out mag axes #ifdef BMX055_REMAP m_imuData.compass.setY(-m_imuData.compass.y()); m_imuData.compass.setZ(-m_imuData.compass.z()); #else RTFLOAT temp = m_imuData.compass.x(); m_imuData.compass.setX(-m_imuData.compass.y()); m_imuData.compass.setY(-temp); m_imuData.compass.setZ(-m_imuData.compass.z()); #endif // now do standard processing handleGyroBias(); calibrateAverageCompass(); calibrateAccel(); if (m_firstTime) m_imuData.timestamp = RTMath::currentUSecsSinceEpoch(); else m_imuData.timestamp += m_sampleInterval; m_firstTime = false; // now update the filter updateFusion(); return true; } /*! * @brief This API reads remapped compensated Magnetometer * data of X,Y,Z values * @note The output value of compensated X, Y, Z as float * * @note In this function X and Y axis is remapped * @note X is read from the address 0x44 & 0x45 * @note Y is read from the address 0x42 & 0x43 * @note this API is only applicable for BMX055 sensor * * * */ /****************************************************/ /**\name ARRAY PARAMETERS */ /***************************************************/ #define LSB_ZERO 0 #define MSB_ONE 1 #define LSB_TWO 2 #define MSB_THREE 3 #define LSB_FOUR 4 #define MSB_FIVE 5 #define LSB_SIX 6 #define MSB_SEVEN 7 /********************************************/ /**\name BIT MASK, LENGTH AND POSITION OF REMAPPED DATA REGISTERS */ /********************************************/ /* Data X LSB Remapped Register only applicable for BMX055 */ #define BMM050_BMX055_REMAPPED_DATAX_LSB_VALUEX__POS 3 #define BMM050_BMX055_REMAPPED_DATAX_LSB_VALUEX__LEN 5 #define BMM050_BMX055_REMAPPED_DATAX_LSB_VALUEX__MSK 0xF8 #define BMM050_BMX055_REMAPPED_DATAX_LSB_VALUEX__REG\ BMM050_BMX055_REMAPPED_DATAX_LSB /* Data Y LSB Remapped Register only applicable for BMX055 */ #define BMM050_BMX055_REMAPPED_DATAY_LSB_VALUEY__POS 3 #define BMM050_BMX055_REMAPPED_DATAY_LSB_VALUEY__LEN 5 #define BMM050_BMX055_REMAPPED_DATAY_LSB_VALUEY__MSK 0xF8 #define BMM050_BMX055_REMAPPED_DATAY_LSB_VALUEY__REG\ BMM050_BMX055_REMAPPED_DATAY_LSB /********************************************/ /**\name BIT MASK, LENGTH AND POSITION OF DATA REGISTERS */ /********************************************/ /* Data X LSB Register */ #define BMM050_DATAX_LSB_VALUEX__POS 3 #define BMM050_DATAX_LSB_VALUEX__LEN 5 #define BMM050_DATAX_LSB_VALUEX__MSK 0xF8 #define BMM050_DATAX_LSB_VALUEX__REG BMM050_DATAX_LSB /* Data X SelfTest Register */ #define BMM050_DATAX_LSB_TESTX__POS 0 #define BMM050_DATAX_LSB_TESTX__LEN 1 #define BMM050_DATAX_LSB_TESTX__MSK 0x01 #define BMM050_DATAX_LSB_TESTX__REG BMM050_DATAX_LSB /* Data Y LSB Register */ #define BMM050_DATAY_LSB_VALUEY__POS 3 #define BMM050_DATAY_LSB_VALUEY__LEN 5 #define BMM050_DATAY_LSB_VALUEY__MSK 0xF8 #define BMM050_DATAY_LSB_VALUEY__REG BMM050_DATAY_LSB /* Data Y SelfTest Register */ #define BMM050_DATAY_LSB_TESTY__POS 0 #define BMM050_DATAY_LSB_TESTY__LEN 1 #define BMM050_DATAY_LSB_TESTY__MSK 0x01 #define BMM050_DATAY_LSB_TESTY__REG BMM050_DATAY_LSB /* Data Z LSB Register */ #define BMM050_DATAZ_LSB_VALUEZ__POS 1 #define BMM050_DATAZ_LSB_VALUEZ__LEN 7 #define BMM050_DATAZ_LSB_VALUEZ__MSK 0xFE #define BMM050_DATAZ_LSB_VALUEZ__REG BMM050_DATAZ_LSB /* Data Z SelfTest Register */ #define BMM050_DATAZ_LSB_TESTZ__POS 0 #define BMM050_DATAZ_LSB_TESTZ__LEN 1 #define BMM050_DATAZ_LSB_TESTZ__MSK 0x01 #define BMM050_DATAZ_LSB_TESTZ__REG BMM050_DATAZ_LSB /* Hall Resistance LSB Register */ #define BMM050_R_LSB_VALUE__POS 2 #define BMM050_R_LSB_VALUE__LEN 6 #define BMM050_R_LSB_VALUE__MSK 0xFC #define BMM050_R_LSB_VALUE__REG BMM050_R_LSB #define BMM050_DATA_RDYSTAT__POS 0 #define BMM050_DATA_RDYSTAT__LEN 1 #define BMM050_DATA_RDYSTAT__MSK 0x01 #define BMM050_DATA_RDYSTAT__REG BMM050_R_LSB /********************************************/ /**\name GET AND SET BITSLICE FUNCTIONS */ /********************************************/ /* get bit slice */ #define BMM050_GET_BITSLICE(regvar, bitname)\ ((regvar & bitname##__MSK) >> bitname##__POS) /* Set bit slice */ #define BMM050_SET_BITSLICE(regvar, bitname, val)\ ((regvar & ~bitname##__MSK) | ((val<resistance = raw_data_xyz_t.raw_data_r; } #else void RTIMUBMX055::processMagData(unsigned char *v_data_u8, float& magX, float& magY, float& magZ) { /* structure used to store the mag raw xyz and r data */ struct { int16_t raw_data_x; int16_t raw_data_y; int16_t raw_data_z; int16_t raw_data_r; } raw_data_xyz_t; /* Reading data for X axis */ v_data_u8[LSB_ZERO] = BMM050_GET_BITSLICE(v_data_u8[LSB_ZERO], BMM050_DATAX_LSB_VALUEX); raw_data_xyz_t.raw_data_x = (int16_t)((((int32_t) ((int8_t)v_data_u8[MSB_ONE])) << SHIFT_LEFT_5_POSITION) | v_data_u8[LSB_ZERO]); /* Reading data for Y axis */ v_data_u8[LSB_TWO] = BMM050_GET_BITSLICE(v_data_u8[LSB_TWO], BMM050_DATAY_LSB_VALUEY); raw_data_xyz_t.raw_data_y = (int16_t)((((int32_t) ((int8_t)v_data_u8[MSB_THREE])) << SHIFT_LEFT_5_POSITION) | v_data_u8[LSB_TWO]); /* Reading data for Z axis */ v_data_u8[LSB_FOUR] = BMM050_GET_BITSLICE(v_data_u8[LSB_FOUR], BMM050_DATAZ_LSB_VALUEZ); raw_data_xyz_t.raw_data_z = (int16_t)((((int32_t) ((int8_t)v_data_u8[MSB_FIVE])) << SHIFT_LEFT_7_POSITION) | v_data_u8[LSB_FOUR]); /* Reading data for Resistance*/ v_data_u8[LSB_SIX] = BMM050_GET_BITSLICE(v_data_u8[LSB_SIX], BMM050_R_LSB_VALUE); raw_data_xyz_t.raw_data_r = (uint16_t)((((uint32_t) v_data_u8[MSB_SEVEN]) << SHIFT_LEFT_6_POSITION) | v_data_u8[LSB_SIX]); /* Compensation for X axis */ magX = bmm050_compensate_X_float( raw_data_xyz_t.raw_data_x, raw_data_xyz_t.raw_data_r); /* Compensation for Y axis */ magY = bmm050_compensate_Y_float( raw_data_xyz_t.raw_data_y, raw_data_xyz_t.raw_data_r); /* Compensation for Z axis */ magZ = bmm050_compensate_Z_float( raw_data_xyz_t.raw_data_z, raw_data_xyz_t.raw_data_r); /* Output raw resistance value */ // mag_data->resistance = raw_data_xyz_t.raw_data_r; } #endif float RTIMUBMX055::bmm050_compensate_X_float(int16_t mag_data_x, uint16_t data_r) { float inter_retval = BMM050_ZERO_U8X; if (mag_data_x != BMM050_FLIP_OVERFLOW_ADCVAL /* no overflow */ ) { if (data_r != C_BMM050_ZERO_U8X) { inter_retval = ((((float)m_dig_xyz1) * BMM050_FLOAT_ONE_SIX_THREE_EIGHT_FOUR /data_r) - BMM050_FLOAT_ONE_SIX_THREE_EIGHT_FOUR); } else { inter_retval = BMM050_OVERFLOW_OUTPUT_FLOAT; return inter_retval; } inter_retval = (((mag_data_x * ((((((float)m_dig_xy2) * (inter_retval*inter_retval / BMM050_FLOAT_2_6_8_4_3_5_4_5_6_DATA) + inter_retval * ((float)m_dig_xy1) / BMM050_FLOAT_1_6_3_8_4_DATA)) + BMM050_FLOAT_2_5_6_DATA) * (((float)m_dig_x2) + BMM050_FLOAT_1_6_0_DATA))) / BMM050_FLOAT_8_1_9_2_DATA) + (((float)m_dig_x1) * BMM050_FLOAT_EIGHT_DATA))/ BMM050_FLOAT_SIXTEEN_DATA; } else { inter_retval = BMM050_OVERFLOW_OUTPUT_FLOAT; } return inter_retval; } float RTIMUBMX055::bmm050_compensate_Y_float(int16_t mag_data_y, uint16_t data_r) { float inter_retval = BMM050_ZERO_U8X; if (mag_data_y != BMM050_FLIP_OVERFLOW_ADCVAL /* no overflow */ ) { if (data_r != C_BMM050_ZERO_U8X) { inter_retval = ((((float)m_dig_xyz1) * BMM050_FLOAT_ONE_SIX_THREE_EIGHT_FOUR /data_r) - BMM050_FLOAT_ONE_SIX_THREE_EIGHT_FOUR); } else { inter_retval = BMM050_OVERFLOW_OUTPUT_FLOAT; return inter_retval; } inter_retval = (((mag_data_y * ((((((float)m_dig_xy2) * (inter_retval*inter_retval / BMM050_FLOAT_2_6_8_4_3_5_4_5_6_DATA) + inter_retval * ((float)m_dig_xy1) / BMM050_FLOAT_1_6_3_8_4_DATA)) + BMM050_FLOAT_2_5_6_DATA) * (((float)m_dig_y2) + BMM050_FLOAT_1_6_0_DATA))) / BMM050_FLOAT_8_1_9_2_DATA) + (((float)m_dig_y1) * BMM050_FLOAT_EIGHT_DATA)) / BMM050_FLOAT_SIXTEEN_DATA; } else { /* overflow, set output to 0.0f */ inter_retval = BMM050_OVERFLOW_OUTPUT_FLOAT; } return inter_retval; } float RTIMUBMX055::bmm050_compensate_Z_float (int16_t mag_data_z, uint16_t data_r) { float inter_retval = BMM050_ZERO_U8X; /* no overflow */ if (mag_data_z != BMM050_HALL_OVERFLOW_ADCVAL) { if ((m_dig_z2 != BMM050_ZERO_U8X) && (m_dig_z1 != BMM050_ZERO_U8X) && (data_r != BMM050_ZERO_U8X)) { inter_retval = ((((((float)mag_data_z)- ((float)m_dig_z4))* BMM050_FLOAT_1_3_1_0_7_2_DATA)- (((float)m_dig_z3)*(((float)data_r) -((float)m_dig_xyz1)))) /((((float)m_dig_z2)+ ((float)m_dig_z1)*((float)data_r) / BMM050_FLOAT_3_2_7_6_8_DATA) * BMM050_FLOAT_4_DATA)) / BMM050_FLOAT_SIXTEEN_DATA; } } else { /* overflow, set output to 0.0f */ inter_retval = BMM050_OVERFLOW_OUTPUT_FLOAT; } return inter_retval; } bool RTIMUBMX055::magInitTrimRegisters() { unsigned char data[2]; data[0] = 0; data[1] = 0; if (!m_settings->HALRead(m_magSlaveAddr, BMX055_MAG_DIG_X1, 1, (uint8_t *)&m_dig_x1, "Failed to read BMX055 mag trim x1")) return false; if (!m_settings->HALRead(m_magSlaveAddr, BMX055_MAG_DIG_Y1, 1, (uint8_t *)&m_dig_y1, "Failed to read BMX055 mag trim y1")) return false; if (!m_settings->HALRead(m_magSlaveAddr, BMX055_MAG_DIG_X2, 1, (uint8_t *)&m_dig_x2, "Failed to read BMX055 mag trim x2")) return false; if (!m_settings->HALRead(m_magSlaveAddr, BMX055_MAG_DIG_Y2, 1, (uint8_t *)&m_dig_y2, "Failed to read BMX055 mag trim y2")) return false; if (!m_settings->HALRead(m_magSlaveAddr, BMX055_MAG_DIG_XY1, 1, &m_dig_xy1, "Failed to read BMX055 mag trim xy1")) return false; if (!m_settings->HALRead(m_magSlaveAddr, BMX055_MAG_DIG_XY2, 1, (uint8_t *)&m_dig_xy2, "Failed to read BMX055 mag trim xy2")) return false; /* shorts can not be recast into (uint8_t*) * due to possible mix up between trim data * arrangement and memory arrangement */ if (!m_settings->HALRead(m_magSlaveAddr, BMX055_MAG_DIG_Z1_LSB, 2, data, "Failed to read BMX055 mag trim z1")) return false; m_dig_z1 = (uint16_t)((((uint32_t)((uint8_t) data[MSB_ONE])) << SHIFT_LEFT_8_POSITION) | data[LSB_ZERO]); if (!m_settings->HALRead(m_magSlaveAddr, BMX055_MAG_DIG_Z2_LSB, 2, data, "Failed to read BMX055 mag trim z2")) return false; m_dig_z2 = (int16_t)((((int32_t)( (int8_t)data[MSB_ONE])) << SHIFT_LEFT_8_POSITION) | data[LSB_ZERO]); if (!m_settings->HALRead(m_magSlaveAddr, BMX055_MAG_DIG_Z3_LSB, 2, data, "Failed to read BMX055 mag trim z3")) return false; m_dig_z3 = (int16_t)((((int32_t)( (int8_t)data[MSB_ONE])) << SHIFT_LEFT_8_POSITION) | data[LSB_ZERO]); if (!m_settings->HALRead(m_magSlaveAddr, BMX055_MAG_DIG_Z4_LSB, 2, data, "Failed to read BMX055 mag trim z4")) return false; m_dig_z4 = (int16_t)((((int32_t)( (int8_t)data[MSB_ONE])) << SHIFT_LEFT_8_POSITION) | data[LSB_ZERO]); if (!m_settings->HALRead(m_magSlaveAddr, BMX055_MAG_DIG_XYZ1_LSB, 2, data, "Failed to read BMX055 mag trim xyz1")) return false; data[MSB_ONE] = BMM050_GET_BITSLICE(data[MSB_ONE], BMM050_DIG_XYZ1_MSB); m_dig_xyz1 = (uint16_t)((((uint32_t) ((uint8_t)data[MSB_ONE])) << SHIFT_LEFT_8_POSITION) | data[LSB_ZERO]); return true; } rtimulib-7.2.1/RTIMULib/IMUDrivers/RTIMUBMX055.h000066400000000000000000000056771254201074400205600ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTIMUBMX055_H #define _RTIMUBMX055_H #include "RTIMU.h" class RTIMUBMX055 : public RTIMU { public: RTIMUBMX055(RTIMUSettings *settings); ~RTIMUBMX055(); virtual const char *IMUName() { return "BMX055"; } virtual int IMUType() { return RTIMU_TYPE_BMX055; } virtual bool IMUInit(); virtual int IMUGetPollInterval(); virtual bool IMURead(); private: bool setGyroSampleRate(); bool setGyroFSR(); bool setAccelSampleRate(); bool setAccelFSR(); bool magInitTrimRegisters(); bool setMagPreset(); void processMagData(unsigned char *v_data_uint8_t, float& magX, float& magY, float& magZ); float bmm050_compensate_X_float(int16_t mag_data_x, uint16_t data_r); float bmm050_compensate_Y_float(int16_t mag_data_y, uint16_t data_r); float bmm050_compensate_Z_float(int16_t mag_data_z, uint16_t data_r); unsigned char m_gyroSlaveAddr; // I2C address of gyro unsigned char m_accelSlaveAddr; // I2C address of accel unsigned char m_magSlaveAddr; // I2C address of mag bool m_firstTime; // if first sample RTFLOAT m_gyroScale; RTFLOAT m_accelScale; int8_t m_dig_x1;/**< trim x1 data */ int8_t m_dig_y1;/**< trim y1 data */ int8_t m_dig_x2;/**< trim x2 data */ int8_t m_dig_y2;/**< trim y2 data */ uint16_t m_dig_z1;/**< trim z1 data */ int16_t m_dig_z2;/**< trim z2 data */ int16_t m_dig_z3;/**< trim z3 data */ int16_t m_dig_z4;/**< trim z4 data */ uint8_t m_dig_xy1;/**< trim xy1 data */ int8_t m_dig_xy2;/**< trim xy2 data */ uint16_t m_dig_xyz1;/**< trim xyz1 data */ }; #endif // _RTIMUBMX055_H rtimulib-7.2.1/RTIMULib/IMUDrivers/RTIMUBNO055.cpp000066400000000000000000000145461254201074400210760ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. // Based on the Adafruit BNO055 driver: /*************************************************************************** This is a library for the BNO055 orientation sensor Designed specifically to work with the Adafruit BNO055 Breakout. Pick one up today in the adafruit shop! ------> http://www.adafruit.com/products These sensors use I2C to communicate, 2 pins are required to interface. Adafruit invests time and resources providing this open source code, please support Adafruit andopen-source hardware by purchasing products from Adafruit! Written by KTOWN for Adafruit Industries. MIT license, all text above must be included in any redistribution ***************************************************************************/ #include "RTIMUBNO055.h" #include "RTIMUSettings.h" RTIMUBNO055::RTIMUBNO055(RTIMUSettings *settings) : RTIMU(settings) { m_sampleRate = 100; m_sampleInterval = (uint64_t)1000000 / m_sampleRate; } RTIMUBNO055::~RTIMUBNO055() { } bool RTIMUBNO055::IMUInit() { unsigned char result; m_slaveAddr = m_settings->m_I2CSlaveAddress; m_lastReadTime = RTMath::currentUSecsSinceEpoch(); // set validity flags m_imuData.fusionPoseValid = true; m_imuData.fusionQPoseValid = true; m_imuData.gyroValid = true; m_imuData.accelValid = true; m_imuData.compassValid = true; m_imuData.pressureValid = false; m_imuData.temperatureValid = false; m_imuData.humidityValid = false; if (!m_settings->HALRead(m_slaveAddr, BNO055_WHO_AM_I, 1, &result, "Failed to read BNO055 id")) return false; if (result != BNO055_ID) { HAL_ERROR1("Incorrect IMU id %d", result); return false; } if (!m_settings->HALWrite(m_slaveAddr, BNO055_OPER_MODE, BNO055_OPER_MODE_CONFIG, "Failed to set BNO055 into config mode")) return false; m_settings->delayMs(50); if (!m_settings->HALWrite(m_slaveAddr, BNO055_SYS_TRIGGER, 0x20, "Failed to reset BNO055")) return false; m_settings->delayMs(50); while (1) { if (!m_settings->HALRead(m_slaveAddr, BNO055_WHO_AM_I, 1, &result, "")) continue; if (result == BNO055_ID) break; m_settings->delayMs(50); } m_settings->delayMs(50); if (!m_settings->HALWrite(m_slaveAddr, BNO055_PWR_MODE, BNO055_PWR_MODE_NORMAL, "Failed to set BNO055 normal power mode")) return false; m_settings->delayMs(50); if (!m_settings->HALWrite(m_slaveAddr, BNO055_PAGE_ID, 0, "Failed to set BNO055 page 0")) return false; m_settings->delayMs(50); if (!m_settings->HALWrite(m_slaveAddr, BNO055_SYS_TRIGGER, 0x00, "Failed to start BNO055")) return false; m_settings->delayMs(50); if (!m_settings->HALWrite(m_slaveAddr, BNO055_UNIT_SEL, 0x87, "Failed to set BNO055 units")) return false; m_settings->delayMs(50); if (!m_settings->HALWrite(m_slaveAddr, BNO055_OPER_MODE, BNO055_OPER_MODE_NDOF, "Failed to set BNO055 into 9-dof mode")) return false; m_settings->delayMs(50); HAL_INFO("BNO055 init complete\n"); return true; } int RTIMUBNO055::IMUGetPollInterval() { return (7); } bool RTIMUBNO055::IMURead() { unsigned char buffer[24]; if ((RTMath::currentUSecsSinceEpoch() - m_lastReadTime) < m_sampleInterval) return false; // too soon m_lastReadTime = RTMath::currentUSecsSinceEpoch(); if (!m_settings->HALRead(m_slaveAddr, BNO055_ACCEL_DATA, 24, buffer, "Failed to read BNO055 data")) return false; int16_t x, y, z; // process accel data x = (((uint16_t)buffer[1]) << 8) | ((uint16_t)buffer[0]); y = (((uint16_t)buffer[3]) << 8) | ((uint16_t)buffer[2]); z = (((uint16_t)buffer[5]) << 8) | ((uint16_t)buffer[4]); m_imuData.accel.setX((RTFLOAT)y / 1000.0); m_imuData.accel.setY((RTFLOAT)x / 1000.0); m_imuData.accel.setZ((RTFLOAT)z / 1000.0); // process mag data x = (((uint16_t)buffer[7]) << 8) | ((uint16_t)buffer[6]); y = (((uint16_t)buffer[9]) << 8) | ((uint16_t)buffer[8]); z = (((uint16_t)buffer[11]) << 8) | ((uint16_t)buffer[10]); m_imuData.compass.setX(-(RTFLOAT)y / 16.0); m_imuData.compass.setY(-(RTFLOAT)x / 16.0); m_imuData.compass.setZ(-(RTFLOAT)z / 16.0); // process gyro data x = (((uint16_t)buffer[13]) << 8) | ((uint16_t)buffer[12]); y = (((uint16_t)buffer[15]) << 8) | ((uint16_t)buffer[14]); z = (((uint16_t)buffer[17]) << 8) | ((uint16_t)buffer[16]); m_imuData.gyro.setX(-(RTFLOAT)y / 900.0); m_imuData.gyro.setY(-(RTFLOAT)x / 900.0); m_imuData.gyro.setZ(-(RTFLOAT)z / 900.0); // process euler angles x = (((uint16_t)buffer[19]) << 8) | ((uint16_t)buffer[18]); y = (((uint16_t)buffer[21]) << 8) | ((uint16_t)buffer[20]); z = (((uint16_t)buffer[23]) << 8) | ((uint16_t)buffer[22]); // put in structure and do axis remap m_imuData.fusionPose.setX((RTFLOAT)y / 900.0); m_imuData.fusionPose.setY((RTFLOAT)z / 900.0); m_imuData.fusionPose.setZ((RTFLOAT)x / 900.0); m_imuData.fusionQPose.fromEuler(m_imuData.fusionPose); m_imuData.timestamp = RTMath::currentUSecsSinceEpoch(); return true; } rtimulib-7.2.1/RTIMULib/IMUDrivers/RTIMUBNO055.h000066400000000000000000000033711254201074400205350ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTIMUBNO055_H #define _RTIMUBNO055_H #include "RTIMU.h" class RTIMUBNO055 : public RTIMU { public: RTIMUBNO055(RTIMUSettings *settings); ~RTIMUBNO055(); virtual const char *IMUName() { return "BNO055"; } virtual int IMUType() { return RTIMU_TYPE_BNO055; } virtual bool IMUInit(); virtual int IMUGetPollInterval(); virtual bool IMURead(); private: unsigned char m_slaveAddr; // I2C address of BNO055 uint64_t m_lastReadTime; }; #endif // _RTIMUBNO055_H rtimulib-7.2.1/RTIMULib/IMUDrivers/RTIMUDefs.h000066400000000000000000001143501254201074400205460ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. // The MPU-9250 and SPI driver code is based on code generously supplied by // staslock@gmail.com (www.clickdrive.io) #ifndef _RTIMUDEFS_H #define _RTIMUDEFS_H // IMU type codes // // For compatibility, only add new codes at the end to avoid renumbering #define RTIMU_TYPE_AUTODISCOVER 0 // audodiscover the IMU #define RTIMU_TYPE_NULL 1 // if no physical hardware #define RTIMU_TYPE_MPU9150 2 // InvenSense MPU9150 #define RTIMU_TYPE_GD20HM303D 3 // STM L3GD20H/LSM303D (Pololu Altimu) #define RTIMU_TYPE_GD20M303DLHC 4 // STM L3GD20/LSM303DHLC (old Adafruit IMU) #define RTIMU_TYPE_LSM9DS0 5 // STM LSM9DS0 (eg Sparkfun IMU) #define RTIMU_TYPE_LSM9DS1 6 // STM LSM9DS1 #define RTIMU_TYPE_MPU9250 7 // InvenSense MPU9250 #define RTIMU_TYPE_GD20HM303DLHC 8 // STM L3GD20H/LSM303DHLC (new Adafruit IMU) #define RTIMU_TYPE_BMX055 9 // Bosch BMX055 #define RTIMU_TYPE_BNO055 10 // Bosch BNO055 //---------------------------------------------------------- // // MPU-9150 // MPU9150 I2C Slave Addresses #define MPU9150_ADDRESS0 0x68 #define MPU9150_ADDRESS1 0x69 #define MPU9150_ID 0x68 // thes magnetometers are on aux bus #define AK8975_ADDRESS 0x0c #define HMC5883_ADDRESS 0x1e // Register map #define MPU9150_YG_OFFS_TC 0x01 #define MPU9150_SMPRT_DIV 0x19 #define MPU9150_LPF_CONFIG 0x1a #define MPU9150_GYRO_CONFIG 0x1b #define MPU9150_ACCEL_CONFIG 0x1c #define MPU9150_FIFO_EN 0x23 #define MPU9150_I2C_MST_CTRL 0x24 #define MPU9150_I2C_SLV0_ADDR 0x25 #define MPU9150_I2C_SLV0_REG 0x26 #define MPU9150_I2C_SLV0_CTRL 0x27 #define MPU9150_I2C_SLV1_ADDR 0x28 #define MPU9150_I2C_SLV1_REG 0x29 #define MPU9150_I2C_SLV1_CTRL 0x2a #define MPU9150_I2C_SLV4_CTRL 0x34 #define MPU9150_INT_PIN_CFG 0x37 #define MPU9150_INT_ENABLE 0x38 #define MPU9150_INT_STATUS 0x3a #define MPU9150_ACCEL_XOUT_H 0x3b #define MPU9150_GYRO_XOUT_H 0x43 #define MPU9150_EXT_SENS_DATA_00 0x49 #define MPU9150_I2C_SLV1_DO 0x64 #define MPU9150_I2C_MST_DELAY_CTRL 0x67 #define MPU9150_USER_CTRL 0x6a #define MPU9150_PWR_MGMT_1 0x6b #define MPU9150_PWR_MGMT_2 0x6c #define MPU9150_FIFO_COUNT_H 0x72 #define MPU9150_FIFO_R_W 0x74 #define MPU9150_WHO_AM_I 0x75 // sample rate defines (applies to gyros and accels, not mags) #define MPU9150_SAMPLERATE_MIN 5 // 5 samples per second is the lowest #define MPU9150_SAMPLERATE_MAX 1000 // 1000 samples per second is the absolute maximum // compass rate defines #define MPU9150_COMPASSRATE_MIN 1 // 1 samples per second is the lowest #define MPU9150_COMPASSRATE_MAX 100 // 100 samples per second is maximum // LPF options (gyros and accels) #define MPU9150_LPF_256 0 // gyro: 256Hz, accel: 260Hz #define MPU9150_LPF_188 1 // gyro: 188Hz, accel: 184Hz #define MPU9150_LPF_98 2 // gyro: 98Hz, accel: 98Hz #define MPU9150_LPF_42 3 // gyro: 42Hz, accel: 44Hz #define MPU9150_LPF_20 4 // gyro: 20Hz, accel: 21Hz #define MPU9150_LPF_10 5 // gyro: 10Hz, accel: 10Hz #define MPU9150_LPF_5 6 // gyro: 5Hz, accel: 5Hz // Gyro FSR options #define MPU9150_GYROFSR_250 0 // +/- 250 degrees per second #define MPU9150_GYROFSR_500 8 // +/- 500 degrees per second #define MPU9150_GYROFSR_1000 0x10 // +/- 1000 degrees per second #define MPU9150_GYROFSR_2000 0x18 // +/- 2000 degrees per second // Accel FSR options #define MPU9150_ACCELFSR_2 0 // +/- 2g #define MPU9150_ACCELFSR_4 8 // +/- 4g #define MPU9150_ACCELFSR_8 0x10 // +/- 8g #define MPU9150_ACCELFSR_16 0x18 // +/- 16g // AK8975 compass registers #define AK8975_DEVICEID 0x0 // the device ID #define AK8975_ST1 0x02 // status 1 #define AK8975_CNTL 0x0a // control reg #define AK8975_ASAX 0x10 // start of the fuse ROM data // HMC5883 compass registers #define HMC5883_CONFIG_A 0x00 // configuration A #define HMC5883_CONFIG_B 0x01 // configuration B #define HMC5883_MODE 0x02 // mode #define HMC5883_DATA_X_HI 0x03 // data x msb #define HMC5883_STATUS 0x09 // status #define HMC5883_ID 0x0a // id //---------------------------------------------------------- // // MPU-9250 // MPU9250 I2C Slave Addresses #define MPU9250_ADDRESS0 0x68 #define MPU9250_ADDRESS1 0x69 #define MPU9250_ID 0x71 #define AK8963_ADDRESS 0x0c // Register map #define MPU9250_SMPRT_DIV 0x19 #define MPU9250_GYRO_LPF 0x1a #define MPU9250_GYRO_CONFIG 0x1b #define MPU9250_ACCEL_CONFIG 0x1c #define MPU9250_ACCEL_LPF 0x1d #define MPU9250_FIFO_EN 0x23 #define MPU9250_I2C_MST_CTRL 0x24 #define MPU9250_I2C_SLV0_ADDR 0x25 #define MPU9250_I2C_SLV0_REG 0x26 #define MPU9250_I2C_SLV0_CTRL 0x27 #define MPU9250_I2C_SLV1_ADDR 0x28 #define MPU9250_I2C_SLV1_REG 0x29 #define MPU9250_I2C_SLV1_CTRL 0x2a #define MPU9250_I2C_SLV2_ADDR 0x2b #define MPU9250_I2C_SLV2_REG 0x2c #define MPU9250_I2C_SLV2_CTRL 0x2d #define MPU9250_I2C_SLV4_CTRL 0x34 #define MPU9250_INT_PIN_CFG 0x37 #define MPU9250_INT_ENABLE 0x38 #define MPU9250_INT_STATUS 0x3a #define MPU9250_ACCEL_XOUT_H 0x3b #define MPU9250_GYRO_XOUT_H 0x43 #define MPU9250_EXT_SENS_DATA_00 0x49 #define MPU9250_I2C_SLV1_DO 0x64 #define MPU9250_I2C_MST_DELAY_CTRL 0x67 #define MPU9250_USER_CTRL 0x6a #define MPU9250_PWR_MGMT_1 0x6b #define MPU9250_PWR_MGMT_2 0x6c #define MPU9250_FIFO_COUNT_H 0x72 #define MPU9250_FIFO_R_W 0x74 #define MPU9250_WHO_AM_I 0x75 // sample rate defines (applies to gyros and accels, not mags) #define MPU9250_SAMPLERATE_MIN 5 // 5 samples per second is the lowest #define MPU9250_SAMPLERATE_MAX 32000 // 32000 samples per second is the absolute maximum // compass rate defines #define MPU9250_COMPASSRATE_MIN 1 // 1 samples per second is the lowest #define MPU9250_COMPASSRATE_MAX 100 // 100 samples per second is maximum // Gyro LPF options #define MPU9250_GYRO_LPF_8800 0x11 // 8800Hz, 0.64mS delay #define MPU9250_GYRO_LPF_3600 0x10 // 3600Hz, 0.11mS delay #define MPU9250_GYRO_LPF_250 0x00 // 250Hz, 0.97mS delay #define MPU9250_GYRO_LPF_184 0x01 // 184Hz, 2.9mS delay #define MPU9250_GYRO_LPF_92 0x02 // 92Hz, 3.9mS delay #define MPU9250_GYRO_LPF_41 0x03 // 41Hz, 5.9mS delay #define MPU9250_GYRO_LPF_20 0x04 // 20Hz, 9.9mS delay #define MPU9250_GYRO_LPF_10 0x05 // 10Hz, 17.85mS delay #define MPU9250_GYRO_LPF_5 0x06 // 5Hz, 33.48mS delay // Gyro FSR options #define MPU9250_GYROFSR_250 0 // +/- 250 degrees per second #define MPU9250_GYROFSR_500 8 // +/- 500 degrees per second #define MPU9250_GYROFSR_1000 0x10 // +/- 1000 degrees per second #define MPU9250_GYROFSR_2000 0x18 // +/- 2000 degrees per second // Accel FSR options #define MPU9250_ACCELFSR_2 0 // +/- 2g #define MPU9250_ACCELFSR_4 8 // +/- 4g #define MPU9250_ACCELFSR_8 0x10 // +/- 8g #define MPU9250_ACCELFSR_16 0x18 // +/- 16g // Accel LPF options #define MPU9250_ACCEL_LPF_1130 0x08 // 1130Hz, 0.75mS delay #define MPU9250_ACCEL_LPF_460 0x00 // 460Hz, 1.94mS delay #define MPU9250_ACCEL_LPF_184 0x01 // 184Hz, 5.80mS delay #define MPU9250_ACCEL_LPF_92 0x02 // 92Hz, 7.80mS delay #define MPU9250_ACCEL_LPF_41 0x03 // 41Hz, 11.80mS delay #define MPU9250_ACCEL_LPF_20 0x04 // 20Hz, 19.80mS delay #define MPU9250_ACCEL_LPF_10 0x05 // 10Hz, 35.70mS delay #define MPU9250_ACCEL_LPF_5 0x06 // 5Hz, 66.96mS delay // AK8963 compass registers #define AK8963_DEVICEID 0x48 // the device ID #define AK8963_ST1 0x02 // status 1 #define AK8963_CNTL 0x0a // control reg #define AK8963_ASAX 0x10 // start of the fuse ROM data //---------------------------------------------------------- // // L3GD20 // I2C Slave Addresses #define L3GD20_ADDRESS0 0x6a #define L3GD20_ADDRESS1 0x6b #define L3GD20_ID 0xd4 // L3GD20 Register map #define L3GD20_WHO_AM_I 0x0f #define L3GD20_CTRL1 0x20 #define L3GD20_CTRL2 0x21 #define L3GD20_CTRL3 0x22 #define L3GD20_CTRL4 0x23 #define L3GD20_CTRL5 0x24 #define L3GD20_OUT_TEMP 0x26 #define L3GD20_STATUS 0x27 #define L3GD20_OUT_X_L 0x28 #define L3GD20_OUT_X_H 0x29 #define L3GD20_OUT_Y_L 0x2a #define L3GD20_OUT_Y_H 0x2b #define L3GD20_OUT_Z_L 0x2c #define L3GD20_OUT_Z_H 0x2d #define L3GD20_FIFO_CTRL 0x2e #define L3GD20_FIFO_SRC 0x2f #define L3GD20_IG_CFG 0x30 #define L3GD20_IG_SRC 0x31 #define L3GD20_IG_THS_XH 0x32 #define L3GD20_IG_THS_XL 0x33 #define L3GD20_IG_THS_YH 0x34 #define L3GD20_IG_THS_YL 0x35 #define L3GD20_IG_THS_ZH 0x36 #define L3GD20_IG_THS_ZL 0x37 #define L3GD20_IG_DURATION 0x38 // Gyro sample rate defines #define L3GD20_SAMPLERATE_95 0 #define L3GD20_SAMPLERATE_190 1 #define L3GD20_SAMPLERATE_380 2 #define L3GD20_SAMPLERATE_760 3 // Gyro banwidth defines #define L3GD20_BANDWIDTH_0 0 #define L3GD20_BANDWIDTH_1 1 #define L3GD20_BANDWIDTH_2 2 #define L3GD20_BANDWIDTH_3 3 // Gyro FSR defines #define L3GD20_FSR_250 0 #define L3GD20_FSR_500 1 #define L3GD20_FSR_2000 2 // Gyro high pass filter defines #define L3GD20_HPF_0 0 #define L3GD20_HPF_1 1 #define L3GD20_HPF_2 2 #define L3GD20_HPF_3 3 #define L3GD20_HPF_4 4 #define L3GD20_HPF_5 5 #define L3GD20_HPF_6 6 #define L3GD20_HPF_7 7 #define L3GD20_HPF_8 8 #define L3GD20_HPF_9 9 //---------------------------------------------------------- // // L3GD20H // I2C Slave Addresses #define L3GD20H_ADDRESS0 0x6a #define L3GD20H_ADDRESS1 0x6b #define L3GD20H_ID 0xd7 // L3GD20H Register map #define L3GD20H_WHO_AM_I 0x0f #define L3GD20H_CTRL1 0x20 #define L3GD20H_CTRL2 0x21 #define L3GD20H_CTRL3 0x22 #define L3GD20H_CTRL4 0x23 #define L3GD20H_CTRL5 0x24 #define L3GD20H_OUT_TEMP 0x26 #define L3GD20H_STATUS 0x27 #define L3GD20H_OUT_X_L 0x28 #define L3GD20H_OUT_X_H 0x29 #define L3GD20H_OUT_Y_L 0x2a #define L3GD20H_OUT_Y_H 0x2b #define L3GD20H_OUT_Z_L 0x2c #define L3GD20H_OUT_Z_H 0x2d #define L3GD20H_FIFO_CTRL 0x2e #define L3GD20H_FIFO_SRC 0x2f #define L3GD20H_IG_CFG 0x30 #define L3GD20H_IG_SRC 0x31 #define L3GD20H_IG_THS_XH 0x32 #define L3GD20H_IG_THS_XL 0x33 #define L3GD20H_IG_THS_YH 0x34 #define L3GD20H_IG_THS_YL 0x35 #define L3GD20H_IG_THS_ZH 0x36 #define L3GD20H_IG_THS_ZL 0x37 #define L3GD20H_IG_DURATION 0x38 #define L3GD20H_LOW_ODR 0x39 // Gyro sample rate defines #define L3GD20H_SAMPLERATE_12_5 0 #define L3GD20H_SAMPLERATE_25 1 #define L3GD20H_SAMPLERATE_50 2 #define L3GD20H_SAMPLERATE_100 3 #define L3GD20H_SAMPLERATE_200 4 #define L3GD20H_SAMPLERATE_400 5 #define L3GD20H_SAMPLERATE_800 6 // Gyro banwidth defines #define L3GD20H_BANDWIDTH_0 0 #define L3GD20H_BANDWIDTH_1 1 #define L3GD20H_BANDWIDTH_2 2 #define L3GD20H_BANDWIDTH_3 3 // Gyro FSR defines #define L3GD20H_FSR_245 0 #define L3GD20H_FSR_500 1 #define L3GD20H_FSR_2000 2 // Gyro high pass filter defines #define L3GD20H_HPF_0 0 #define L3GD20H_HPF_1 1 #define L3GD20H_HPF_2 2 #define L3GD20H_HPF_3 3 #define L3GD20H_HPF_4 4 #define L3GD20H_HPF_5 5 #define L3GD20H_HPF_6 6 #define L3GD20H_HPF_7 7 #define L3GD20H_HPF_8 8 #define L3GD20H_HPF_9 9 //---------------------------------------------------------- // // LSM303D #define LSM303D_ADDRESS0 0x1e #define LSM303D_ADDRESS1 0x1d #define LSM303D_ID 0x49 // LSM303D Register Map #define LSM303D_TEMP_OUT_L 0x05 #define LSM303D_TEMP_OUT_H 0x06 #define LSM303D_STATUS_M 0x07 #define LSM303D_OUT_X_L_M 0x08 #define LSM303D_OUT_X_H_M 0x09 #define LSM303D_OUT_Y_L_M 0x0a #define LSM303D_OUT_Y_H_M 0x0b #define LSM303D_OUT_Z_L_M 0x0c #define LSM303D_OUT_Z_H_M 0x0d #define LSM303D_WHO_AM_I 0x0f #define LSM303D_INT_CTRL_M 0x12 #define LSM303D_INT_SRC_M 0x13 #define LSM303D_INT_THS_L_M 0x14 #define LSM303D_INT_THS_H_M 0x15 #define LSM303D_OFFSET_X_L_M 0x16 #define LSM303D_OFFSET_X_H_M 0x17 #define LSM303D_OFFSET_Y_L_M 0x18 #define LSM303D_OFFSET_Y_H_M 0x19 #define LSM303D_OFFSET_Z_L_M 0x1a #define LSM303D_OFFSET_Z_H_M 0x1b #define LSM303D_REFERENCE_X 0x1c #define LSM303D_REFERENCE_Y 0x1d #define LSM303D_REFERENCE_Z 0x1e #define LSM303D_CTRL0 0x1f #define LSM303D_CTRL1 0x20 #define LSM303D_CTRL2 0x21 #define LSM303D_CTRL3 0x22 #define LSM303D_CTRL4 0x23 #define LSM303D_CTRL5 0x24 #define LSM303D_CTRL6 0x25 #define LSM303D_CTRL7 0x26 #define LSM303D_STATUS_A 0x27 #define LSM303D_OUT_X_L_A 0x28 #define LSM303D_OUT_X_H_A 0x29 #define LSM303D_OUT_Y_L_A 0x2a #define LSM303D_OUT_Y_H_A 0x2b #define LSM303D_OUT_Z_L_A 0x2c #define LSM303D_OUT_Z_H_A 0x2d #define LSM303D_FIFO_CTRL 0x2e #define LSM303D_FIFO_SRC 0x2f #define LSM303D_IG_CFG1 0x30 #define LSM303D_IG_SRC1 0x31 #define LSM303D_IG_THS1 0x32 #define LSM303D_IG_DUR1 0x33 #define LSM303D_IG_CFG2 0x34 #define LSM303D_IG_SRC2 0x35 #define LSM303D_IG_THS2 0x36 #define LSM303D_IG_DUR2 0x37 #define LSM303D_CLICK_CFG 0x38 #define LSM303D_CLICK_SRC 0x39 #define LSM303D_CLICK_THS 0x3a #define LSM303D_TIME_LIMIT 0x3b #define LSM303D_TIME_LATENCY 0x3c #define LSM303D_TIME_WINDOW 0x3d #define LSM303D_ACT_THIS 0x3e #define LSM303D_ACT_DUR 0x3f // Accel sample rate defines #define LSM303D_ACCEL_SAMPLERATE_3_125 1 #define LSM303D_ACCEL_SAMPLERATE_6_25 2 #define LSM303D_ACCEL_SAMPLERATE_12_5 3 #define LSM303D_ACCEL_SAMPLERATE_25 4 #define LSM303D_ACCEL_SAMPLERATE_50 5 #define LSM303D_ACCEL_SAMPLERATE_100 6 #define LSM303D_ACCEL_SAMPLERATE_200 7 #define LSM303D_ACCEL_SAMPLERATE_400 8 #define LSM303D_ACCEL_SAMPLERATE_800 9 #define LSM303D_ACCEL_SAMPLERATE_1600 10 // Accel FSR #define LSM303D_ACCEL_FSR_2 0 #define LSM303D_ACCEL_FSR_4 1 #define LSM303D_ACCEL_FSR_6 2 #define LSM303D_ACCEL_FSR_8 3 #define LSM303D_ACCEL_FSR_16 4 // Accel filter bandwidth #define LSM303D_ACCEL_LPF_773 0 #define LSM303D_ACCEL_LPF_194 1 #define LSM303D_ACCEL_LPF_362 2 #define LSM303D_ACCEL_LPF_50 3 // Compass sample rate defines #define LSM303D_COMPASS_SAMPLERATE_3_125 0 #define LSM303D_COMPASS_SAMPLERATE_6_25 1 #define LSM303D_COMPASS_SAMPLERATE_12_5 2 #define LSM303D_COMPASS_SAMPLERATE_25 3 #define LSM303D_COMPASS_SAMPLERATE_50 4 #define LSM303D_COMPASS_SAMPLERATE_100 5 // Compass FSR #define LSM303D_COMPASS_FSR_2 0 #define LSM303D_COMPASS_FSR_4 1 #define LSM303D_COMPASS_FSR_8 2 #define LSM303D_COMPASS_FSR_12 3 //---------------------------------------------------------- // // LSM303DLHC #define LSM303DLHC_ACCEL_ADDRESS 0x19 #define LSM303DLHC_COMPASS_ADDRESS 0x1e // LSM303DLHC Accel Register Map #define LSM303DLHC_CTRL1_A 0x20 #define LSM303DLHC_CTRL2_A 0x21 #define LSM303DLHC_CTRL3_A 0x22 #define LSM303DLHC_CTRL4_A 0x23 #define LSM303DLHC_CTRL5_A 0x24 #define LSM303DLHC_CTRL6_A 0x25 #define LSM303DLHC_REF_A 0x26 #define LSM303DLHC_STATUS_A 0x27 #define LSM303DLHC_OUT_X_L_A 0x28 #define LSM303DLHC_OUT_X_H_A 0x29 #define LSM303DLHC_OUT_Y_L_A 0x2a #define LSM303DLHC_OUT_Y_H_A 0x2b #define LSM303DLHC_OUT_Z_L_A 0x2c #define LSM303DLHC_OUT_Z_H_A 0x2d #define LSM303DLHC_FIFO_CTRL_A 0x2e #define LSM303DLHC_FIFO_SRC_A 0x2f // LSM303DLHC Compass Register Map #define LSM303DLHC_CRA_M 0x00 #define LSM303DLHC_CRB_M 0x01 #define LSM303DLHC_CRM_M 0x02 #define LSM303DLHC_OUT_X_H_M 0x03 #define LSM303DLHC_OUT_X_L_M 0x04 #define LSM303DLHC_OUT_Y_H_M 0x05 #define LSM303DLHC_OUT_Y_L_M 0x06 #define LSM303DLHC_OUT_Z_H_M 0x07 #define LSM303DLHC_OUT_Z_L_M 0x08 #define LSM303DLHC_STATUS_M 0x09 #define LSM303DLHC_TEMP_OUT_L_M 0x31 #define LSM303DLHC_TEMP_OUT_H_M 0x32 // Accel sample rate defines #define LSM303DLHC_ACCEL_SAMPLERATE_1 1 #define LSM303DLHC_ACCEL_SAMPLERATE_10 2 #define LSM303DLHC_ACCEL_SAMPLERATE_25 3 #define LSM303DLHC_ACCEL_SAMPLERATE_50 4 #define LSM303DLHC_ACCEL_SAMPLERATE_100 5 #define LSM303DLHC_ACCEL_SAMPLERATE_200 6 #define LSM303DLHC_ACCEL_SAMPLERATE_400 7 // Accel FSR #define LSM303DLHC_ACCEL_FSR_2 0 #define LSM303DLHC_ACCEL_FSR_4 1 #define LSM303DLHC_ACCEL_FSR_8 2 #define LSM303DLHC_ACCEL_FSR_16 3 // Compass sample rate defines #define LSM303DLHC_COMPASS_SAMPLERATE_0_75 0 #define LSM303DLHC_COMPASS_SAMPLERATE_1_5 1 #define LSM303DLHC_COMPASS_SAMPLERATE_3 2 #define LSM303DLHC_COMPASS_SAMPLERATE_7_5 3 #define LSM303DLHC_COMPASS_SAMPLERATE_15 4 #define LSM303DLHC_COMPASS_SAMPLERATE_30 5 #define LSM303DLHC_COMPASS_SAMPLERATE_75 6 #define LSM303DLHC_COMPASS_SAMPLERATE_220 7 // Compass FSR #define LSM303DLHC_COMPASS_FSR_1_3 1 #define LSM303DLHC_COMPASS_FSR_1_9 2 #define LSM303DLHC_COMPASS_FSR_2_5 3 #define LSM303DLHC_COMPASS_FSR_4 4 #define LSM303DLHC_COMPASS_FSR_4_7 5 #define LSM303DLHC_COMPASS_FSR_5_6 6 #define LSM303DLHC_COMPASS_FSR_8_1 7 //---------------------------------------------------------- // // LSM9DS0 // I2C Slave Addresses #define LSM9DS0_GYRO_ADDRESS0 0x6a #define LSM9DS0_GYRO_ADDRESS1 0x6b #define LSM9DS0_GYRO_ID 0xd4 #define LSM9DS0_ACCELMAG_ADDRESS0 0x1e #define LSM9DS0_ACCELMAG_ADDRESS1 0x1d #define LSM9DS0_ACCELMAG_ID 0x49 // LSM9DS0 Register map #define LSM9DS0_GYRO_WHO_AM_I 0x0f #define LSM9DS0_GYRO_CTRL1 0x20 #define LSM9DS0_GYRO_CTRL2 0x21 #define LSM9DS0_GYRO_CTRL3 0x22 #define LSM9DS0_GYRO_CTRL4 0x23 #define LSM9DS0_GYRO_CTRL5 0x24 #define LSM9DS0_GYRO_OUT_TEMP 0x26 #define LSM9DS0_GYRO_STATUS 0x27 #define LSM9DS0_GYRO_OUT_X_L 0x28 #define LSM9DS0_GYRO_OUT_X_H 0x29 #define LSM9DS0_GYRO_OUT_Y_L 0x2a #define LSM9DS0_GYRO_OUT_Y_H 0x2b #define LSM9DS0_GYRO_OUT_Z_L 0x2c #define LSM9DS0_GYRO_OUT_Z_H 0x2d #define LSM9DS0_GYRO_FIFO_CTRL 0x2e #define LSM9DS0_GYRO_FIFO_SRC 0x2f #define LSM9DS0_GYRO_IG_CFG 0x30 #define LSM9DS0_GYRO_IG_SRC 0x31 #define LSM9DS0_GYRO_IG_THS_XH 0x32 #define LSM9DS0_GYRO_IG_THS_XL 0x33 #define LSM9DS0_GYRO_IG_THS_YH 0x34 #define LSM9DS0_GYRO_IG_THS_YL 0x35 #define LSM9DS0_GYRO_IG_THS_ZH 0x36 #define LSM9DS0_GYRO_IG_THS_ZL 0x37 #define LSM9DS0_GYRO_IG_DURATION 0x38 // Gyro sample rate defines #define LSM9DS0_GYRO_SAMPLERATE_95 0 #define LSM9DS0_GYRO_SAMPLERATE_190 1 #define LSM9DS0_GYRO_SAMPLERATE_380 2 #define LSM9DS0_GYRO_SAMPLERATE_760 3 // Gyro banwidth defines #define LSM9DS0_GYRO_BANDWIDTH_0 0 #define LSM9DS0_GYRO_BANDWIDTH_1 1 #define LSM9DS0_GYRO_BANDWIDTH_2 2 #define LSM9DS0_GYRO_BANDWIDTH_3 3 // Gyro FSR defines #define LSM9DS0_GYRO_FSR_250 0 #define LSM9DS0_GYRO_FSR_500 1 #define LSM9DS0_GYRO_FSR_2000 2 // Gyro high pass filter defines #define LSM9DS0_GYRO_HPF_0 0 #define LSM9DS0_GYRO_HPF_1 1 #define LSM9DS0_GYRO_HPF_2 2 #define LSM9DS0_GYRO_HPF_3 3 #define LSM9DS0_GYRO_HPF_4 4 #define LSM9DS0_GYRO_HPF_5 5 #define LSM9DS0_GYRO_HPF_6 6 #define LSM9DS0_GYRO_HPF_7 7 #define LSM9DS0_GYRO_HPF_8 8 #define LSM9DS0_GYRO_HPF_9 9 // Accel/Mag Register Map #define LSM9DS0_TEMP_OUT_L 0x05 #define LSM9DS0_TEMP_OUT_H 0x06 #define LSM9DS0_STATUS_M 0x07 #define LSM9DS0_OUT_X_L_M 0x08 #define LSM9DS0_OUT_X_H_M 0x09 #define LSM9DS0_OUT_Y_L_M 0x0a #define LSM9DS0_OUT_Y_H_M 0x0b #define LSM9DS0_OUT_Z_L_M 0x0c #define LSM9DS0_OUT_Z_H_M 0x0d #define LSM9DS0_WHO_AM_I 0x0f #define LSM9DS0_INT_CTRL_M 0x12 #define LSM9DS0_INT_SRC_M 0x13 #define LSM9DS0_INT_THS_L_M 0x14 #define LSM9DS0_INT_THS_H_M 0x15 #define LSM9DS0_OFFSET_X_L_M 0x16 #define LSM9DS0_OFFSET_X_H_M 0x17 #define LSM9DS0_OFFSET_Y_L_M 0x18 #define LSM9DS0_OFFSET_Y_H_M 0x19 #define LSM9DS0_OFFSET_Z_L_M 0x1a #define LSM9DS0_OFFSET_Z_H_M 0x1b #define LSM9DS0_REFERENCE_X 0x1c #define LSM9DS0_REFERENCE_Y 0x1d #define LSM9DS0_REFERENCE_Z 0x1e #define LSM9DS0_CTRL0 0x1f #define LSM9DS0_CTRL1 0x20 #define LSM9DS0_CTRL2 0x21 #define LSM9DS0_CTRL3 0x22 #define LSM9DS0_CTRL4 0x23 #define LSM9DS0_CTRL5 0x24 #define LSM9DS0_CTRL6 0x25 #define LSM9DS0_CTRL7 0x26 #define LSM9DS0_STATUS_A 0x27 #define LSM9DS0_OUT_X_L_A 0x28 #define LSM9DS0_OUT_X_H_A 0x29 #define LSM9DS0_OUT_Y_L_A 0x2a #define LSM9DS0_OUT_Y_H_A 0x2b #define LSM9DS0_OUT_Z_L_A 0x2c #define LSM9DS0_OUT_Z_H_A 0x2d #define LSM9DS0_FIFO_CTRL 0x2e #define LSM9DS0_FIFO_SRC 0x2f #define LSM9DS0_IG_CFG1 0x30 #define LSM9DS0_IG_SRC1 0x31 #define LSM9DS0_IG_THS1 0x32 #define LSM9DS0_IG_DUR1 0x33 #define LSM9DS0_IG_CFG2 0x34 #define LSM9DS0_IG_SRC2 0x35 #define LSM9DS0_IG_THS2 0x36 #define LSM9DS0_IG_DUR2 0x37 #define LSM9DS0_CLICK_CFG 0x38 #define LSM9DS0_CLICK_SRC 0x39 #define LSM9DS0_CLICK_THS 0x3a #define LSM9DS0_TIME_LIMIT 0x3b #define LSM9DS0_TIME_LATENCY 0x3c #define LSM9DS0_TIME_WINDOW 0x3d #define LSM9DS0_ACT_THIS 0x3e #define LSM9DS0_ACT_DUR 0x3f // Accel sample rate defines #define LSM9DS0_ACCEL_SAMPLERATE_3_125 1 #define LSM9DS0_ACCEL_SAMPLERATE_6_25 2 #define LSM9DS0_ACCEL_SAMPLERATE_12_5 3 #define LSM9DS0_ACCEL_SAMPLERATE_25 4 #define LSM9DS0_ACCEL_SAMPLERATE_50 5 #define LSM9DS0_ACCEL_SAMPLERATE_100 6 #define LSM9DS0_ACCEL_SAMPLERATE_200 7 #define LSM9DS0_ACCEL_SAMPLERATE_400 8 #define LSM9DS0_ACCEL_SAMPLERATE_800 9 #define LSM9DS0_ACCEL_SAMPLERATE_1600 10 // Accel FSR #define LSM9DS0_ACCEL_FSR_2 0 #define LSM9DS0_ACCEL_FSR_4 1 #define LSM9DS0_ACCEL_FSR_6 2 #define LSM9DS0_ACCEL_FSR_8 3 #define LSM9DS0_ACCEL_FSR_16 4 // Accel filter bandwidth #define LSM9DS0_ACCEL_LPF_773 0 #define LSM9DS0_ACCEL_LPF_194 1 #define LSM9DS0_ACCEL_LPF_362 2 #define LSM9DS0_ACCEL_LPF_50 3 // Compass sample rate defines #define LSM9DS0_COMPASS_SAMPLERATE_3_125 0 #define LSM9DS0_COMPASS_SAMPLERATE_6_25 1 #define LSM9DS0_COMPASS_SAMPLERATE_12_5 2 #define LSM9DS0_COMPASS_SAMPLERATE_25 3 #define LSM9DS0_COMPASS_SAMPLERATE_50 4 #define LSM9DS0_COMPASS_SAMPLERATE_100 5 // Compass FSR #define LSM9DS0_COMPASS_FSR_2 0 #define LSM9DS0_COMPASS_FSR_4 1 #define LSM9DS0_COMPASS_FSR_8 2 #define LSM9DS0_COMPASS_FSR_12 3 //---------------------------------------------------------- // // LSM9DS1 // I2C Slave Addresses #define LSM9DS1_ADDRESS0 0x6a #define LSM9DS1_ADDRESS1 0x6b #define LSM9DS1_ID 0x68 #define LSM9DS1_MAG_ADDRESS0 0x1c #define LSM9DS1_MAG_ADDRESS1 0x1d #define LSM9DS1_MAG_ADDRESS2 0x1e #define LSM9DS1_MAG_ADDRESS3 0x1f #define LSM9DS1_MAG_ID 0x3d // LSM9DS1 Register map #define LSM9DS1_ACT_THS 0x04 #define LSM9DS1_ACT_DUR 0x05 #define LSM9DS1_INT_GEN_CFG_XL 0x06 #define LSM9DS1_INT_GEN_THS_X_XL 0x07 #define LSM9DS1_INT_GEN_THS_Y_XL 0x08 #define LSM9DS1_INT_GEN_THS_Z_XL 0x09 #define LSM9DS1_INT_GEN_DUR_XL 0x0A #define LSM9DS1_REFERENCE_G 0x0B #define LSM9DS1_INT1_CTRL 0x0C #define LSM9DS1_INT2_CTRL 0x0D #define LSM9DS1_WHO_AM_I 0x0F #define LSM9DS1_CTRL1 0x10 #define LSM9DS1_CTRL2 0x11 #define LSM9DS1_CTRL3 0x12 #define LSM9DS1_ORIENT_CFG_G 0x13 #define LSM9DS1_INT_GEN_SRC_G 0x14 #define LSM9DS1_OUT_TEMP_L 0x15 #define LSM9DS1_OUT_TEMP_H 0x16 #define LSM9DS1_STATUS 0x17 #define LSM9DS1_OUT_X_L_G 0x18 #define LSM9DS1_OUT_X_H_G 0x19 #define LSM9DS1_OUT_Y_L_G 0x1A #define LSM9DS1_OUT_Y_H_G 0x1B #define LSM9DS1_OUT_Z_L_G 0x1C #define LSM9DS1_OUT_Z_H_G 0x1D #define LSM9DS1_CTRL4 0x1E #define LSM9DS1_CTRL5 0x1F #define LSM9DS1_CTRL6 0x20 #define LSM9DS1_CTRL7 0x21 #define LSM9DS1_CTRL8 0x22 #define LSM9DS1_CTRL9 0x23 #define LSM9DS1_CTRL10 0x24 #define LSM9DS1_INT_GEN_SRC_XL 0x26 #define LSM9DS1_STATUS2 0x27 #define LSM9DS1_OUT_X_L_XL 0x28 #define LSM9DS1_OUT_X_H_XL 0x29 #define LSM9DS1_OUT_Y_L_XL 0x2A #define LSM9DS1_OUT_Y_H_XL 0x2B #define LSM9DS1_OUT_Z_L_XL 0x2C #define LSM9DS1_OUT_Z_H_XL 0x2D #define LSM9DS1_FIFO_CTRL 0x2E #define LSM9DS1_FIFO_SRC 0x2F #define LSM9DS1_INT_GEN_CFG_G 0x30 #define LSM9DS1_INT_GEN_THS_XH_G 0x31 #define LSM9DS1_INT_GEN_THS_XL_G 0x32 #define LSM9DS1_INT_GEN_THS_YH_G 0x33 #define LSM9DS1_INT_GEN_THS_YL_G 0x34 #define LSM9DS1_INT_GEN_THS_ZH_G 0x35 #define LSM9DS1_INT_GEN_THS_ZL_G 0x36 #define LSM9DS1_INT_GEN_DUR_G 0x37 // Gyro sample rate defines #define LSM9DS1_GYRO_SAMPLERATE_14_9 0 #define LSM9DS1_GYRO_SAMPLERATE_59_5 1 #define LSM9DS1_GYRO_SAMPLERATE_119 2 #define LSM9DS1_GYRO_SAMPLERATE_238 3 #define LSM9DS1_GYRO_SAMPLERATE_476 4 #define LSM9DS1_GYRO_SAMPLERATE_952 5 // Gyro banwidth defines #define LSM9DS1_GYRO_BANDWIDTH_0 0 #define LSM9DS1_GYRO_BANDWIDTH_1 1 #define LSM9DS1_GYRO_BANDWIDTH_2 2 #define LSM9DS1_GYRO_BANDWIDTH_3 3 // Gyro FSR defines #define LSM9DS1_GYRO_FSR_250 0 #define LSM9DS1_GYRO_FSR_500 1 #define LSM9DS1_GYRO_FSR_2000 2 // Gyro high pass filter defines #define LSM9DS1_GYRO_HPF_0 0 #define LSM9DS1_GYRO_HPF_1 1 #define LSM9DS1_GYRO_HPF_2 2 #define LSM9DS1_GYRO_HPF_3 3 #define LSM9DS1_GYRO_HPF_4 4 #define LSM9DS1_GYRO_HPF_5 5 #define LSM9DS1_GYRO_HPF_6 6 #define LSM9DS1_GYRO_HPF_7 7 #define LSM9DS1_GYRO_HPF_8 8 #define LSM9DS1_GYRO_HPF_9 9 // Mag Register Map #define LSM9DS1_MAG_OFFSET_X_L 0x05 #define LSM9DS1_MAG_OFFSET_X_H 0x06 #define LSM9DS1_MAG_OFFSET_Y_L 0x07 #define LSM9DS1_MAG_OFFSET_Y_H 0x08 #define LSM9DS1_MAG_OFFSET_Z_L 0x09 #define LSM9DS1_MAG_OFFSET_Z_H 0x0A #define LSM9DS1_MAG_WHO_AM_I 0x0F #define LSM9DS1_MAG_CTRL1 0x20 #define LSM9DS1_MAG_CTRL2 0x21 #define LSM9DS1_MAG_CTRL3 0x22 #define LSM9DS1_MAG_CTRL4 0x23 #define LSM9DS1_MAG_CTRL5 0x24 #define LSM9DS1_MAG_STATUS 0x27 #define LSM9DS1_MAG_OUT_X_L 0x28 #define LSM9DS1_MAG_OUT_X_H 0x29 #define LSM9DS1_MAG_OUT_Y_L 0x2A #define LSM9DS1_MAG_OUT_Y_H 0x2B #define LSM9DS1_MAG_OUT_Z_L 0x2C #define LSM9DS1_MAG_OUT_Z_H 0x2D #define LSM9DS1_MAG_INT_CFG 0x30 #define LSM9DS1_MAG_INT_SRC 0x31 #define LSM9DS1_MAG_INT_THS_L 0x32 #define LSM9DS1_MAG_INT_THS_H 0x33 // Accel sample rate defines #define LSM9DS1_ACCEL_SAMPLERATE_14_9 1 #define LSM9DS1_ACCEL_SAMPLERATE_59_5 2 #define LSM9DS1_ACCEL_SAMPLERATE_119 3 #define LSM9DS1_ACCEL_SAMPLERATE_238 4 #define LSM9DS1_ACCEL_SAMPLERATE_476 5 #define LSM9DS1_ACCEL_SAMPLERATE_952 6 // Accel FSR #define LSM9DS1_ACCEL_FSR_2 0 #define LSM9DS1_ACCEL_FSR_16 1 #define LSM9DS1_ACCEL_FSR_4 2 #define LSM9DS1_ACCEL_FSR_8 3 // Accel filter bandwidth #define LSM9DS1_ACCEL_LPF_408 0 #define LSM9DS1_ACCEL_LPF_211 1 #define LSM9DS1_ACCEL_LPF_105 2 #define LSM9DS1_ACCEL_LPF_50 3 // Compass sample rate defines #define LSM9DS1_COMPASS_SAMPLERATE_0_625 0 #define LSM9DS1_COMPASS_SAMPLERATE_1_25 1 #define LSM9DS1_COMPASS_SAMPLERATE_2_5 2 #define LSM9DS1_COMPASS_SAMPLERATE_5 3 #define LSM9DS1_COMPASS_SAMPLERATE_10 4 #define LSM9DS1_COMPASS_SAMPLERATE_20 5 #define LSM9DS1_COMPASS_SAMPLERATE_40 6 #define LSM9DS1_COMPASS_SAMPLERATE_80 7 // Compass FSR #define LSM9DS1_COMPASS_FSR_4 0 #define LSM9DS1_COMPASS_FSR_8 1 #define LSM9DS1_COMPASS_FSR_12 2 #define LSM9DS1_COMPASS_FSR_16 3 //---------------------------------------------------------- // // BMX055 // I2C Slave Addresses #define BMX055_GYRO_ADDRESS0 0x68 #define BMX055_GYRO_ADDRESS1 0x69 #define BMX055_GYRO_ID 0x0f #define BMX055_ACCEL_ADDRESS0 0x18 #define BMX055_ACCEL_ADDRESS1 0x19 #define BMX055_ACCEL_ID 0xfa #define BMX055_MAG_ADDRESS0 0x10 #define BMX055_MAG_ADDRESS1 0x11 #define BMX055_MAG_ADDRESS2 0x12 #define BMX055_MAG_ADDRESS3 0x13 #define BMX055_MAG_ID 0x32 // BMX055 Register map #define BMX055_GYRO_WHO_AM_I 0x00 #define BMX055_GYRO_X_LSB 0x02 #define BMX055_GYRO_X_MSB 0x03 #define BMX055_GYRO_Y_LSB 0x04 #define BMX055_GYRO_Y_MSB 0x05 #define BMX055_GYRO_Z_LSB 0x06 #define BMX055_GYRO_Z_MSB 0x07 #define BMX055_GYRO_INT_STATUS_0 0x09 #define BMX055_GYRO_INT_STATUS_1 0x0a #define BMX055_GYRO_INT_STATUS_2 0x0b #define BMX055_GYRO_INT_STATUS_3 0x0c #define BMX055_GYRO_FIFO_STATUS 0x0e #define BMX055_GYRO_RANGE 0x0f #define BMX055_GYRO_BW 0x10 #define BMX055_GYRO_LPM1 0x11 #define BMX055_GYRO_LPM2 0x12 #define BMX055_GYRO_RATE_HBW 0x13 #define BMX055_GYRO_SOFT_RESET 0x14 #define BMX055_GYRO_INT_EN_0 0x15 #define BMX055_GYRO_1A 0x1a #define BMX055_GYRO_1B 0x1b #define BMX055_GYRO_SOC 0x31 #define BMX055_GYRO_FOC 0x32 #define BMX055_GYRO_FIFO_CONFIG_0 0x3d #define BMX055_GYRO_FIFO_CONFIG_1 0x3e #define BMX055_GYRO_FIFO_DATA 0x3f #define BMX055_ACCEL_WHO_AM_I 0x00 #define BMX055_ACCEL_X_LSB 0x02 #define BMX055_ACCEL_X_MSB 0x03 #define BMX055_ACCEL_Y_LSB 0x04 #define BMX055_ACCEL_Y_MSB 0x05 #define BMX055_ACCEL_Z_LSB 0x06 #define BMX055_ACCEL_Z_MSB 0x07 #define BMX055_ACCEL_TEMP 0x08 #define BMX055_ACCEL_INT_STATUS_0 0x09 #define BMX055_ACCEL_INT_STATUS_1 0x0a #define BMX055_ACCEL_INT_STATUS_2 0x0b #define BMX055_ACCEL_INT_STATUS_3 0x0c #define BMX055_ACCEL_FIFO_STATUS 0x0e #define BMX055_ACCEL_PMU_RANGE 0x0f #define BMX055_ACCEL_PMU_BW 0x10 #define BMX055_ACCEL_PMU_LPW 0x11 #define BMX055_ACCEL_PMU_LOW_POWER 0x12 #define BMX055_ACCEL_HBW 0x13 #define BMX055_ACCEL_SOFT_RESET 0x14 #define BMX055_ACCEL_FIFO_CONFIG_0 0x30 #define BMX055_ACCEL_OFC_CTRL 0x36 #define BMX055_ACCEL_OFC_SETTING 0x37 #define BMX055_ACCEL_FIFO_CONFIG_1 0x3e #define BMX055_ACCEL_FIFO_DATA 0x3f #define BMX055_MAG_WHO_AM_I 0x40 #define BMX055_MAG_X_LSB 0x42 #define BMX055_MAG_X_MSB 0x43 #define BMX055_MAG_Y_LSB 0x44 #define BMX055_MAG_Y_MSB 0x45 #define BMX055_MAG_Z_LSB 0x46 #define BMX055_MAG_Z_MSB 0x47 #define BMX055_MAG_RHALL_LSB 0x48 #define BMX055_MAG_RHALL_MSB 0x49 #define BMX055_MAG_INT_STAT 0x4a #define BMX055_MAG_POWER 0x4b #define BMX055_MAG_MODE 0x4c #define BMX055_MAG_INT_ENABLE 0x4d #define BMX055_MAG_AXIS_ENABLE 0x4e #define BMX055_MAG_REPXY 0x51 #define BMX055_MAG_REPZ 0x52 #define BMX055_MAG_DIG_X1 0x5D #define BMX055_MAG_DIG_Y1 0x5E #define BMX055_MAG_DIG_Z4_LSB 0x62 #define BMX055_MAG_DIG_Z4_MSB 0x63 #define BMX055_MAG_DIG_X2 0x64 #define BMX055_MAG_DIG_Y2 0x65 #define BMX055_MAG_DIG_Z2_LSB 0x68 #define BMX055_MAG_DIG_Z2_MSB 0x69 #define BMX055_MAG_DIG_Z1_LSB 0x6A #define BMX055_MAG_DIG_Z1_MSB 0x6B #define BMX055_MAG_DIG_XYZ1_LSB 0x6C #define BMX055_MAG_DIG_XYZ1_MSB 0x6D #define BMX055_MAG_DIG_Z3_LSB 0x6E #define BMX055_MAG_DIG_Z3_MSB 0x6F #define BMX055_MAG_DIG_XY2 0x70 #define BMX055_MAG_DIG_XY1 0x71 // Gyro sample rate defines #define BMX055_GYRO_SAMPLERATE_100_32 0x07 #define BMX055_GYRO_SAMPLERATE_200_64 0x06 #define BMX055_GYRO_SAMPLERATE_100_12 0x05 #define BMX055_GYRO_SAMPLERATE_200_23 0x04 #define BMX055_GYRO_SAMPLERATE_400_47 0x03 #define BMX055_GYRO_SAMPLERATE_1000_116 0x02 #define BMX055_GYRO_SAMPLERATE_2000_230 0x01 #define BMX055_GYRO_SAMPLERATE_2000_523 0x00 // Gyro FSR defines #define BMX055_GYRO_FSR_2000 0x00 #define BMX055_GYRO_FSR_1000 0x01 #define BMX055_GYRO_FSR_500 0x02 #define BMX055_GYRO_FSR_250 0x03 #define BMX055_GYRO_FSR_125 0x04 // Accel sample rate defines #define BMX055_ACCEL_SAMPLERATE_15 0X00 #define BMX055_ACCEL_SAMPLERATE_31 0X01 #define BMX055_ACCEL_SAMPLERATE_62 0X02 #define BMX055_ACCEL_SAMPLERATE_125 0X03 #define BMX055_ACCEL_SAMPLERATE_250 0X04 #define BMX055_ACCEL_SAMPLERATE_500 0X05 #define BMX055_ACCEL_SAMPLERATE_1000 0X06 #define BMX055_ACCEL_SAMPLERATE_2000 0X07 // Accel FSR defines #define BMX055_ACCEL_FSR_2 0x00 #define BMX055_ACCEL_FSR_4 0x01 #define BMX055_ACCEL_FSR_8 0x02 #define BMX055_ACCEL_FSR_16 0x03 // Mag preset defines #define BMX055_MAG_LOW_POWER 0x00 #define BMX055_MAG_REGULAR 0x01 #define BMX055_MAG_ENHANCED 0x02 #define BMX055_MAG_HIGH_ACCURACY 0x03 //---------------------------------------------------------- // // BNO055 // I2C Slave Addresses #define BNO055_ADDRESS0 0x28 #define BNO055_ADDRESS1 0x29 #define BNO055_ID 0xa0 // Register map #define BNO055_WHO_AM_I 0x00 #define BNO055_PAGE_ID 0x07 #define BNO055_ACCEL_DATA 0x08 #define BNO055_MAG_DATA 0x0e #define BNO055_GYRO_DATA 0x14 #define BNO055_FUSED_EULER 0x1a #define BNO055_FUSED_QUAT 0x20 #define BNO055_UNIT_SEL 0x3b #define BNO055_OPER_MODE 0x3d #define BNO055_PWR_MODE 0x3e #define BNO055_SYS_TRIGGER 0x3f #define BNO055_AXIS_MAP_CONFIG 0x41 #define BNO055_AXIS_MAP_SIGN 0x42 // Operation modes #define BNO055_OPER_MODE_CONFIG 0x00 #define BNO055_OPER_MODE_NDOF 0x0c // Power modes #define BNO055_PWR_MODE_NORMAL 0x00 #endif // _RTIMUDEFS_H rtimulib-7.2.1/RTIMULib/IMUDrivers/RTIMUGD20HM303D.cpp000066400000000000000000000373411254201074400214370ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTIMUGD20HM303D.h" #include "RTIMUSettings.h" // this sets the learning rate for compass running average calculation #define COMPASS_ALPHA 0.2f RTIMUGD20HM303D::RTIMUGD20HM303D(RTIMUSettings *settings) : RTIMU(settings) { m_sampleRate = 100; } RTIMUGD20HM303D::~RTIMUGD20HM303D() { } bool RTIMUGD20HM303D::IMUInit() { unsigned char result; #ifdef GD20HM303D_CACHE_MODE m_firstTime = true; m_cacheIn = m_cacheOut = m_cacheCount = 0; #endif // set validity flags m_imuData.fusionPoseValid = false; m_imuData.fusionQPoseValid = false; m_imuData.gyroValid = true; m_imuData.accelValid = true; m_imuData.compassValid = true; m_imuData.pressureValid = false; m_imuData.temperatureValid = false; m_imuData.humidityValid = false; // configure IMU m_gyroSlaveAddr = m_settings->m_I2CSlaveAddress; // work out accel/mag address if (m_settings->HALRead(LSM303D_ADDRESS0, LSM303D_WHO_AM_I, 1, &result, "")) { if (result == LSM303D_ID) { m_accelCompassSlaveAddr = LSM303D_ADDRESS0; } } else { m_accelCompassSlaveAddr = LSM303D_ADDRESS1; } setCalibrationData(); // enable the I2C bus if (!m_settings->HALOpen()) return false; // Set up the gyro if (!m_settings->HALWrite(m_gyroSlaveAddr, L3GD20H_LOW_ODR, 0x04, "Failed to reset L3GD20H")) return false; if (!m_settings->HALWrite(m_gyroSlaveAddr, L3GD20H_CTRL5, 0x80, "Failed to boot L3GD20H")) return false; if (!m_settings->HALRead(m_gyroSlaveAddr, L3GD20H_WHO_AM_I, 1, &result, "Failed to read L3GD20H id")) return false; if (result != L3GD20H_ID) { HAL_ERROR1("Incorrect L3GD20H id %d\n", result); return false; } if (!setGyroSampleRate()) return false; if (!setGyroCTRL2()) return false; if (!setGyroCTRL4()) return false; // Set up the accel/compass if (!m_settings->HALRead(m_accelCompassSlaveAddr, LSM303D_WHO_AM_I, 1, &result, "Failed to read LSM303D id")) return false; if (result != LSM303D_ID) { HAL_ERROR1("Incorrect LSM303D id %d\n", result); return false; } if (!setAccelCTRL1()) return false; if (!setAccelCTRL2()) return false; if (!setCompassCTRL5()) return false; if (!setCompassCTRL6()) return false; if (!setCompassCTRL7()) return false; #ifdef GD20HM303D_CACHE_MODE // turn on gyro fifo if (!m_settings->HALWrite(m_gyroSlaveAddr, L3GD20H_FIFO_CTRL, 0x3f, "Failed to set L3GD20H FIFO mode")) return false; #endif if (!setGyroCTRL5()) return false; gyroBiasInit(); HAL_INFO("GD20HM303D init complete\n"); return true; } bool RTIMUGD20HM303D::setGyroSampleRate() { unsigned char ctrl1; unsigned char lowOdr = 0; switch (m_settings->m_GD20HM303DGyroSampleRate) { case L3GD20H_SAMPLERATE_12_5: ctrl1 = 0x0f; lowOdr = 1; m_sampleRate = 13; break; case L3GD20H_SAMPLERATE_25: ctrl1 = 0x4f; lowOdr = 1; m_sampleRate = 25; break; case L3GD20H_SAMPLERATE_50: ctrl1 = 0x8f; lowOdr = 1; m_sampleRate = 50; break; case L3GD20H_SAMPLERATE_100: ctrl1 = 0x0f; m_sampleRate = 100; break; case L3GD20H_SAMPLERATE_200: ctrl1 = 0x4f; m_sampleRate = 200; break; case L3GD20H_SAMPLERATE_400: ctrl1 = 0x8f; m_sampleRate = 400; break; case L3GD20H_SAMPLERATE_800: ctrl1 = 0xcf; m_sampleRate = 800; break; default: HAL_ERROR1("Illegal L3GD20H sample rate code %d\n", m_settings->m_GD20HM303DGyroSampleRate); return false; } m_sampleInterval = (uint64_t)1000000 / m_sampleRate; switch (m_settings->m_GD20HM303DGyroBW) { case L3GD20H_BANDWIDTH_0: ctrl1 |= 0x00; break; case L3GD20H_BANDWIDTH_1: ctrl1 |= 0x10; break; case L3GD20H_BANDWIDTH_2: ctrl1 |= 0x20; break; case L3GD20H_BANDWIDTH_3: ctrl1 |= 0x30; break; } if (!m_settings->HALWrite(m_gyroSlaveAddr, L3GD20H_LOW_ODR, lowOdr, "Failed to set L3GD20H LOW_ODR")) return false; return (m_settings->HALWrite(m_gyroSlaveAddr, L3GD20H_CTRL1, ctrl1, "Failed to set L3GD20H CTRL1")); } bool RTIMUGD20HM303D::setGyroCTRL2() { if ((m_settings->m_GD20HM303DGyroHpf < L3GD20H_HPF_0) || (m_settings->m_GD20HM303DGyroHpf > L3GD20H_HPF_9)) { HAL_ERROR1("Illegal L3GD20H high pass filter code %d\n", m_settings->m_GD20HM303DGyroHpf); return false; } return m_settings->HALWrite(m_gyroSlaveAddr, L3GD20H_CTRL2, m_settings->m_GD20HM303DGyroHpf, "Failed to set L3GD20H CTRL2"); } bool RTIMUGD20HM303D::setGyroCTRL4() { unsigned char ctrl4; switch (m_settings->m_GD20HM303DGyroFsr) { case L3GD20H_FSR_245: ctrl4 = 0x00; m_gyroScale = (RTFLOAT)0.00875 * RTMATH_DEGREE_TO_RAD; break; case L3GD20H_FSR_500: ctrl4 = 0x10; m_gyroScale = (RTFLOAT)0.0175 * RTMATH_DEGREE_TO_RAD; break; case L3GD20H_FSR_2000: ctrl4 = 0x20; m_gyroScale = (RTFLOAT)0.07 * RTMATH_DEGREE_TO_RAD; break; default: HAL_ERROR1("Illegal L3GD20H FSR code %d\n", m_settings->m_GD20HM303DGyroFsr); return false; } return m_settings->HALWrite(m_gyroSlaveAddr, L3GD20H_CTRL4, ctrl4, "Failed to set L3GD20H CTRL4"); } bool RTIMUGD20HM303D::setGyroCTRL5() { unsigned char ctrl5; // Turn on hpf ctrl5 = 0x10; #ifdef GD20HM303D_CACHE_MODE // turn on fifo ctrl5 |= 0x40; #endif return m_settings->HALWrite(m_gyroSlaveAddr, L3GD20H_CTRL5, ctrl5, "Failed to set L3GD20H CTRL5"); } bool RTIMUGD20HM303D::setAccelCTRL1() { unsigned char ctrl1; if ((m_settings->m_GD20HM303DAccelSampleRate < 0) || (m_settings->m_GD20HM303DAccelSampleRate > 10)) { HAL_ERROR1("Illegal LSM303D accel sample rate code %d\n", m_settings->m_GD20HM303DAccelSampleRate); return false; } ctrl1 = (m_settings->m_GD20HM303DAccelSampleRate << 4) | 0x07; return m_settings->HALWrite(m_accelCompassSlaveAddr, LSM303D_CTRL1, ctrl1, "Failed to set LSM303D CTRL1"); } bool RTIMUGD20HM303D::setAccelCTRL2() { unsigned char ctrl2; if ((m_settings->m_GD20HM303DAccelLpf < 0) || (m_settings->m_GD20HM303DAccelLpf > 3)) { HAL_ERROR1("Illegal LSM303D accel low pass fiter code %d\n", m_settings->m_GD20HM303DAccelLpf); return false; } switch (m_settings->m_GD20HM303DAccelFsr) { case LSM303D_ACCEL_FSR_2: m_accelScale = (RTFLOAT)0.000061; break; case LSM303D_ACCEL_FSR_4: m_accelScale = (RTFLOAT)0.000122; break; case LSM303D_ACCEL_FSR_6: m_accelScale = (RTFLOAT)0.000183; break; case LSM303D_ACCEL_FSR_8: m_accelScale = (RTFLOAT)0.000244; break; case LSM303D_ACCEL_FSR_16: m_accelScale = (RTFLOAT)0.000732; break; default: HAL_ERROR1("Illegal LSM303D accel FSR code %d\n", m_settings->m_GD20HM303DAccelFsr); return false; } ctrl2 = (m_settings->m_GD20HM303DAccelLpf << 6) | (m_settings->m_GD20HM303DAccelFsr << 3); return m_settings->HALWrite(m_accelCompassSlaveAddr, LSM303D_CTRL2, ctrl2, "Failed to set LSM303D CTRL2"); } bool RTIMUGD20HM303D::setCompassCTRL5() { unsigned char ctrl5; if ((m_settings->m_GD20HM303DCompassSampleRate < 0) || (m_settings->m_GD20HM303DCompassSampleRate > 5)) { HAL_ERROR1("Illegal LSM303D compass sample rate code %d\n", m_settings->m_GD20HM303DCompassSampleRate); return false; } ctrl5 = (m_settings->m_GD20HM303DCompassSampleRate << 2); #ifdef GD20HM303D_CACHE_MODE // enable fifo ctrl5 |= 0x40; #endif return m_settings->HALWrite(m_accelCompassSlaveAddr, LSM303D_CTRL5, ctrl5, "Failed to set LSM303D CTRL5"); } bool RTIMUGD20HM303D::setCompassCTRL6() { unsigned char ctrl6; // convert FSR to uT switch (m_settings->m_GD20HM303DCompassFsr) { case LSM303D_COMPASS_FSR_2: ctrl6 = 0; m_compassScale = (RTFLOAT)0.008; break; case LSM303D_COMPASS_FSR_4: ctrl6 = 0x20; m_compassScale = (RTFLOAT)0.016; break; case LSM303D_COMPASS_FSR_8: ctrl6 = 0x40; m_compassScale = (RTFLOAT)0.032; break; case LSM303D_COMPASS_FSR_12: ctrl6 = 0x60; m_compassScale = (RTFLOAT)0.0479; break; default: HAL_ERROR1("Illegal LSM303D compass FSR code %d\n", m_settings->m_GD20HM303DCompassFsr); return false; } return m_settings->HALWrite(m_accelCompassSlaveAddr, LSM303D_CTRL6, ctrl6, "Failed to set LSM303D CTRL6"); } bool RTIMUGD20HM303D::setCompassCTRL7() { return m_settings->HALWrite(m_accelCompassSlaveAddr, LSM303D_CTRL7, 0x60, "Failed to set LSM303D CTRL7"); } int RTIMUGD20HM303D::IMUGetPollInterval() { return (400 / m_sampleRate); } bool RTIMUGD20HM303D::IMURead() { unsigned char status; unsigned char gyroData[6]; unsigned char accelData[6]; unsigned char compassData[6]; #ifdef GD20HM303D_CACHE_MODE int count; if (!m_settings->HALRead(m_gyroSlaveAddr, L3GD20H_FIFO_SRC, 1, &status, "Failed to read L3GD20H fifo status")) return false; if ((status & 0x40) != 0) { HAL_INFO("L3GD20H fifo overrun\n"); if (!m_settings->HALWrite(m_gyroSlaveAddr, L3GD20H_CTRL5, 0x10, "Failed to set L3GD20H CTRL5")) return false; if (!m_settings->HALWrite(m_gyroSlaveAddr, L3GD20H_FIFO_CTRL, 0x0, "Failed to set L3GD20H FIFO mode")) return false; if (!m_settings->HALWrite(m_gyroSlaveAddr, L3GD20H_FIFO_CTRL, 0x3f, "Failed to set L3GD20H FIFO mode")) return false; if (!setGyroCTRL5()) return false; m_imuData.timestamp += m_sampleInterval * 32; return false; } // get count of samples in fifo count = status & 0x1f; if ((m_cacheCount == 0) && (count > 0) && (count < GD20HM303D_FIFO_THRESH)) { // special case of a small fifo and nothing cached - just handle as simple read if (!m_settings->HALRead(m_gyroSlaveAddr, 0x80 | L3GD20H_OUT_X_L, 6, gyroData, "Failed to read L3GD20H data")) return false; if (!m_settings->HALRead(m_accelCompassSlaveAddr, 0x80 | LSM303D_OUT_X_L_A, 6, accelData, "Failed to read LSM303D accel data")) return false; if (!m_settings->HALRead(m_accelCompassSlaveAddr, 0x80 | LSM303D_OUT_X_L_M, 6, compassData, "Failed to read LSM303D compass data")) return false; if (m_firstTime) m_imuData.timestamp = RTMath::currentUSecsSinceEpoch(); else m_imuData.timestamp += m_sampleInterval; m_firstTime = false; } else { if (count >= GD20HM303D_FIFO_THRESH) { // need to create a cache block if (m_cacheCount == GD20HM303D_CACHE_BLOCK_COUNT) { // all cache blocks are full - discard oldest and update timestamp to account for lost samples m_imuData.timestamp += m_sampleInterval * m_cache[m_cacheOut].count; if (++m_cacheOut == GD20HM303D_CACHE_BLOCK_COUNT) m_cacheOut = 0; m_cacheCount--; } if (!m_settings->HALRead(m_gyroSlaveAddr, 0x80 | L3GD20H_OUT_X_L, GD20HM303D_FIFO_CHUNK_SIZE * GD20HM303D_FIFO_THRESH, m_cache[m_cacheIn].data, "Failed to read L3GD20H fifo data")) return false; if (!m_settings->HALRead(m_accelCompassSlaveAddr, 0x80 | LSM303D_OUT_X_L_A, 6, m_cache[m_cacheIn].accel, "Failed to read LSM303D accel data")) return false; if (!m_settings->HALRead(m_accelCompassSlaveAddr, 0x80 | LSM303D_OUT_X_L_M, 6, m_cache[m_cacheIn].compass, "Failed to read LSM303D compass data")) return false; m_cache[m_cacheIn].count = GD20HM303D_FIFO_THRESH; m_cache[m_cacheIn].index = 0; m_cacheCount++; if (++m_cacheIn == GD20HM303D_CACHE_BLOCK_COUNT) m_cacheIn = 0; } // now fifo has been read if necessary, get something to process if (m_cacheCount == 0) return false; memcpy(gyroData, m_cache[m_cacheOut].data + m_cache[m_cacheOut].index, GD20HM303D_FIFO_CHUNK_SIZE); memcpy(accelData, m_cache[m_cacheOut].accel, 6); memcpy(compassData, m_cache[m_cacheOut].compass, 6); m_cache[m_cacheOut].index += GD20HM303D_FIFO_CHUNK_SIZE; if (--m_cache[m_cacheOut].count == 0) { // this cache block is now empty if (++m_cacheOut == GD20HM303D_CACHE_BLOCK_COUNT) m_cacheOut = 0; m_cacheCount--; } if (m_firstTime) m_imuData.timestamp = RTMath::currentUSecsSinceEpoch(); else m_imuData.timestamp += m_sampleInterval; m_firstTime = false; } #else if (!m_settings->HALRead(m_gyroSlaveAddr, L3GD20H_STATUS, 1, &status, "Failed to read L3GD20H status")) return false; if ((status & 0x8) == 0) return false; if (!m_settings->HALRead(m_gyroSlaveAddr, 0x80 | L3GD20H_OUT_X_L, 6, gyroData, "Failed to read L3GD20H data")) return false; m_imuData.timestamp = RTMath::currentUSecsSinceEpoch(); if (!m_settings->HALRead(m_accelCompassSlaveAddr, 0x80 | LSM303D_OUT_X_L_A, 6, accelData, "Failed to read LSM303D accel data")) return false; if (!m_settings->HALRead(m_accelCompassSlaveAddr, 0x80 | LSM303D_OUT_X_L_M, 6, compassData, "Failed to read LSM303D compass data")) return false; #endif RTMath::convertToVector(gyroData, m_imuData.gyro, m_gyroScale, false); RTMath::convertToVector(accelData, m_imuData.accel, m_accelScale, false); RTMath::convertToVector(compassData, m_imuData.compass, m_compassScale, false); // sort out gyro axes m_imuData.gyro.setX(m_imuData.gyro.x()); m_imuData.gyro.setY(-m_imuData.gyro.y()); m_imuData.gyro.setZ(-m_imuData.gyro.z()); // sort out accel data; m_imuData.accel.setX(-m_imuData.accel.x()); // sort out compass axes m_imuData.compass.setY(-m_imuData.compass.y()); m_imuData.compass.setZ(-m_imuData.compass.z()); // now do standard processing handleGyroBias(); calibrateAverageCompass(); calibrateAccel(); // now update the filter updateFusion(); return true; } rtimulib-7.2.1/RTIMULib/IMUDrivers/RTIMUGD20HM303D.h000066400000000000000000000070071254201074400211000ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTIMUGD20HM303D_H #define _RTIMUGD20HM303D_H #include "RTIMU.h" // Define this symbol to use cache mode //#define GD20HM303D_CACHE_MODE // not reliable at the moment #ifdef GD20HM303D_CACHE_MODE // Cache defs #define GD20HM303D_FIFO_CHUNK_SIZE 6 // 6 bytes of gyro data #define GD20HM303D_FIFO_THRESH 16 // threshold point in fifo #define GD20HM303D_CACHE_BLOCK_COUNT 16 // number of cache blocks typedef struct { unsigned char data[GD20HM303D_FIFO_THRESH * GD20HM303D_FIFO_CHUNK_SIZE]; int count; // number of chunks in the cache block int index; // current index into the cache unsigned char accel[6]; // the raw accel readings for the block unsigned char compass[6]; // the raw compass readings for the block } GD20HM303D_CACHE_BLOCK; #endif class RTIMUGD20HM303D : public RTIMU { public: RTIMUGD20HM303D(RTIMUSettings *settings); ~RTIMUGD20HM303D(); virtual const char *IMUName() { return "L3GD20H + LSM303D"; } virtual int IMUType() { return RTIMU_TYPE_GD20HM303D; } virtual bool IMUInit(); virtual int IMUGetPollInterval(); virtual bool IMURead(); private: bool setGyroSampleRate(); bool setGyroCTRL2(); bool setGyroCTRL4(); bool setGyroCTRL5(); bool setAccelCTRL1(); bool setAccelCTRL2(); bool setCompassCTRL5(); bool setCompassCTRL6(); bool setCompassCTRL7(); unsigned char m_gyroSlaveAddr; // I2C address of L3GD20H unsigned char m_accelCompassSlaveAddr; // I2C address of LSM303D RTFLOAT m_gyroScale; RTFLOAT m_accelScale; RTFLOAT m_compassScale; #ifdef GD20HM303D_CACHE_MODE bool m_firstTime; // if first sample GD20HM303D_CACHE_BLOCK m_cache[GD20HM303D_CACHE_BLOCK_COUNT]; // the cache itself int m_cacheIn; // the in index int m_cacheOut; // the out index int m_cacheCount; // number of used cache blocks #endif }; #endif // _RTIMUGD20HM303D_H rtimulib-7.2.1/RTIMULib/IMUDrivers/RTIMUGD20HM303DLHC.cpp000066400000000000000000000403611254201074400217620ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTIMUGD20HM303DLHC.h" #include "RTIMUSettings.h" // this sets the learning rate for compass running average calculation #define COMPASS_ALPHA 0.2f RTIMUGD20HM303DLHC::RTIMUGD20HM303DLHC(RTIMUSettings *settings) : RTIMU(settings) { m_sampleRate = 100; } RTIMUGD20HM303DLHC::~RTIMUGD20HM303DLHC() { } bool RTIMUGD20HM303DLHC::IMUInit() { unsigned char result; #ifdef GD20HM303DLHC_CACHE_MODE m_firstTime = true; m_cacheIn = m_cacheOut = m_cacheCount = 0; #endif // set validity flags m_imuData.fusionPoseValid = false; m_imuData.fusionQPoseValid = false; m_imuData.gyroValid = true; m_imuData.accelValid = true; m_imuData.compassValid = true; m_imuData.pressureValid = false; m_imuData.temperatureValid = false; m_imuData.humidityValid = false; // configure IMU m_gyroSlaveAddr = m_settings->m_I2CSlaveAddress; m_accelSlaveAddr = LSM303DLHC_ACCEL_ADDRESS; m_compassSlaveAddr = LSM303DLHC_COMPASS_ADDRESS; setCalibrationData(); // enable the I2C bus if (!m_settings->HALOpen()) return false; // Set up the gyro if (!m_settings->HALWrite(m_gyroSlaveAddr, L3GD20H_LOW_ODR, 0x04, "Failed to reset L3GD20H")) return false; if (!m_settings->HALWrite(m_gyroSlaveAddr, L3GD20H_CTRL5, 0x80, "Failed to boot L3GD20H")) return false; if (!m_settings->HALRead(m_gyroSlaveAddr, L3GD20H_WHO_AM_I, 1, &result, "Failed to read L3GD20H id")) return false; if (result != L3GD20H_ID) { HAL_ERROR1("Incorrect L3GD20H id %d\n", result); return false; } if (!setGyroSampleRate()) return false; if (!setGyroCTRL2()) return false; if (!setGyroCTRL4()) return false; // Set up the accel if (!setAccelCTRL1()) return false; if (!setAccelCTRL4()) return false; // Set up the compass if (!setCompassCRA()) return false; if (!setCompassCRB()) return false; if (!setCompassCRM()) return false; #ifdef GD20HM303DLHC_CACHE_MODE // turn on gyro fifo if (!m_settings->HALWrite(m_gyroSlaveAddr, L3GD20_FIFO_CTRL, 0x3f, "Failed to set L3GD20 FIFO mode")) return false; #endif if (!setGyroCTRL5()) return false; gyroBiasInit(); HAL_INFO("GD20HM303DLHC init complete\n"); return true; } bool RTIMUGD20HM303DLHC::setGyroSampleRate() { unsigned char ctrl1; unsigned char lowOdr = 0; switch (m_settings->m_GD20HM303DLHCGyroSampleRate) { case L3GD20H_SAMPLERATE_12_5: ctrl1 = 0x0f; lowOdr = 1; m_sampleRate = 13; break; case L3GD20H_SAMPLERATE_25: ctrl1 = 0x4f; lowOdr = 1; m_sampleRate = 25; break; case L3GD20H_SAMPLERATE_50: ctrl1 = 0x8f; lowOdr = 1; m_sampleRate = 50; break; case L3GD20H_SAMPLERATE_100: ctrl1 = 0x0f; m_sampleRate = 100; break; case L3GD20H_SAMPLERATE_200: ctrl1 = 0x4f; m_sampleRate = 200; break; case L3GD20H_SAMPLERATE_400: ctrl1 = 0x8f; m_sampleRate = 400; break; case L3GD20H_SAMPLERATE_800: ctrl1 = 0xcf; m_sampleRate = 800; break; default: HAL_ERROR1("Illegal L3GD20H sample rate code %d\n", m_settings->m_GD20HM303DLHCGyroSampleRate); return false; } m_sampleInterval = (uint64_t)1000000 / m_sampleRate; switch (m_settings->m_GD20HM303DLHCGyroBW) { case L3GD20H_BANDWIDTH_0: ctrl1 |= 0x00; break; case L3GD20H_BANDWIDTH_1: ctrl1 |= 0x10; break; case L3GD20H_BANDWIDTH_2: ctrl1 |= 0x20; break; case L3GD20H_BANDWIDTH_3: ctrl1 |= 0x30; break; } if (!m_settings->HALWrite(m_gyroSlaveAddr, L3GD20H_LOW_ODR, lowOdr, "Failed to set L3GD20H LOW_ODR")) return false; return (m_settings->HALWrite(m_gyroSlaveAddr, L3GD20H_CTRL1, ctrl1, "Failed to set L3GD20H CTRL1")); } bool RTIMUGD20HM303DLHC::setGyroCTRL2() { if ((m_settings->m_GD20HM303DLHCGyroHpf < L3GD20H_HPF_0) || (m_settings->m_GD20HM303DLHCGyroHpf > L3GD20H_HPF_9)) { HAL_ERROR1("Illegal L3GD20H high pass filter code %d\n", m_settings->m_GD20HM303DLHCGyroHpf); return false; } return m_settings->HALWrite(m_gyroSlaveAddr, L3GD20H_CTRL2, m_settings->m_GD20HM303DLHCGyroHpf, "Failed to set L3GD20H CTRL2"); } bool RTIMUGD20HM303DLHC::setGyroCTRL4() { unsigned char ctrl4; switch (m_settings->m_GD20HM303DLHCGyroFsr) { case L3GD20H_FSR_245: ctrl4 = 0x00; m_gyroScale = (RTFLOAT)0.00875 * RTMATH_DEGREE_TO_RAD; break; case L3GD20H_FSR_500: ctrl4 = 0x10; m_gyroScale = (RTFLOAT)0.0175 * RTMATH_DEGREE_TO_RAD; break; case L3GD20H_FSR_2000: ctrl4 = 0x20; m_gyroScale = (RTFLOAT)0.07 * RTMATH_DEGREE_TO_RAD; break; default: HAL_ERROR1("Illegal L3GD20H FSR code %d\n", m_settings->m_GD20HM303DLHCGyroFsr); return false; } return m_settings->HALWrite(m_gyroSlaveAddr, L3GD20H_CTRL4, ctrl4, "Failed to set L3GD20H CTRL4"); } bool RTIMUGD20HM303DLHC::setGyroCTRL5() { unsigned char ctrl5; // Turn on hpf ctrl5 = 0x10; #ifdef GD20HM303DLHC_CACHE_MODE // turn on fifo ctrl5 |= 0x40; #endif return m_settings->HALWrite(m_gyroSlaveAddr, L3GD20H_CTRL5, ctrl5, "Failed to set L3GD20H CTRL5"); } bool RTIMUGD20HM303DLHC::setAccelCTRL1() { unsigned char ctrl1; if ((m_settings->m_GD20HM303DLHCAccelSampleRate < 1) || (m_settings->m_GD20HM303DLHCAccelSampleRate > 7)) { HAL_ERROR1("Illegal LSM303DLHC accel sample rate code %d\n", m_settings->m_GD20HM303DLHCAccelSampleRate); return false; } ctrl1 = (m_settings->m_GD20HM303DLHCAccelSampleRate << 4) | 0x07; return m_settings->HALWrite(m_accelSlaveAddr, LSM303DLHC_CTRL1_A, ctrl1, "Failed to set LSM303D CTRL1"); } bool RTIMUGD20HM303DLHC::setAccelCTRL4() { unsigned char ctrl4; switch (m_settings->m_GD20HM303DLHCAccelFsr) { case LSM303DLHC_ACCEL_FSR_2: m_accelScale = (RTFLOAT)0.001 / (RTFLOAT)64; break; case LSM303DLHC_ACCEL_FSR_4: m_accelScale = (RTFLOAT)0.002 / (RTFLOAT)64; break; case LSM303DLHC_ACCEL_FSR_8: m_accelScale = (RTFLOAT)0.004 / (RTFLOAT)64; break; case LSM303DLHC_ACCEL_FSR_16: m_accelScale = (RTFLOAT)0.012 / (RTFLOAT)64; break; default: HAL_ERROR1("Illegal LSM303DLHC accel FSR code %d\n", m_settings->m_GD20HM303DLHCAccelFsr); return false; } ctrl4 = (m_settings->m_GD20HM303DLHCAccelFsr << 4); return m_settings->HALWrite(m_accelSlaveAddr, LSM303DLHC_CTRL2_A, ctrl4, "Failed to set LSM303DLHC CTRL4"); } bool RTIMUGD20HM303DLHC::setCompassCRA() { unsigned char cra; if ((m_settings->m_GD20HM303DLHCCompassSampleRate < 0) || (m_settings->m_GD20HM303DLHCCompassSampleRate > 7)) { HAL_ERROR1("Illegal LSM303DLHC compass sample rate code %d\n", m_settings->m_GD20HM303DLHCCompassSampleRate); return false; } cra = (m_settings->m_GD20HM303DLHCCompassSampleRate << 2); return m_settings->HALWrite(m_compassSlaveAddr, LSM303DLHC_CRA_M, cra, "Failed to set LSM303DLHC CRA_M"); } bool RTIMUGD20HM303DLHC::setCompassCRB() { unsigned char crb; // convert FSR to uT switch (m_settings->m_GD20HM303DLHCCompassFsr) { case LSM303DLHC_COMPASS_FSR_1_3: crb = 0x20; m_compassScaleXY = (RTFLOAT)100 / (RTFLOAT)1100; m_compassScaleZ = (RTFLOAT)100 / (RTFLOAT)980; break; case LSM303DLHC_COMPASS_FSR_1_9: crb = 0x40; m_compassScaleXY = (RTFLOAT)100 / (RTFLOAT)855; m_compassScaleZ = (RTFLOAT)100 / (RTFLOAT)760; break; case LSM303DLHC_COMPASS_FSR_2_5: crb = 0x60; m_compassScaleXY = (RTFLOAT)100 / (RTFLOAT)670; m_compassScaleZ = (RTFLOAT)100 / (RTFLOAT)600; break; case LSM303DLHC_COMPASS_FSR_4: crb = 0x80; m_compassScaleXY = (RTFLOAT)100 / (RTFLOAT)450; m_compassScaleZ = (RTFLOAT)100 / (RTFLOAT)400; break; case LSM303DLHC_COMPASS_FSR_4_7: crb = 0xa0; m_compassScaleXY = (RTFLOAT)100 / (RTFLOAT)400; m_compassScaleZ = (RTFLOAT)100 / (RTFLOAT)355; break; case LSM303DLHC_COMPASS_FSR_5_6: crb = 0xc0; m_compassScaleXY = (RTFLOAT)100 / (RTFLOAT)330; m_compassScaleZ = (RTFLOAT)100 / (RTFLOAT)295; break; case LSM303DLHC_COMPASS_FSR_8_1: crb = 0xe0; m_compassScaleXY = (RTFLOAT)100 / (RTFLOAT)230; m_compassScaleZ = (RTFLOAT)100 / (RTFLOAT)205; break; default: HAL_ERROR1("Illegal LSM303DLHC compass FSR code %d\n", m_settings->m_GD20HM303DLHCCompassFsr); return false; } return m_settings->HALWrite(m_compassSlaveAddr, LSM303DLHC_CRB_M, crb, "Failed to set LSM303DLHC CRB_M"); } bool RTIMUGD20HM303DLHC::setCompassCRM() { return m_settings->HALWrite(m_compassSlaveAddr, LSM303DLHC_CRM_M, 0x00, "Failed to set LSM303DLHC CRM_M"); } int RTIMUGD20HM303DLHC::IMUGetPollInterval() { return (400 / m_sampleRate); } bool RTIMUGD20HM303DLHC::IMURead() { unsigned char status; unsigned char gyroData[6]; unsigned char accelData[6]; unsigned char compassData[6]; #ifdef GD20HM303DLHC_CACHE_MODE int count; if (!m_settings->HALRead(m_gyroSlaveAddr, L3GD20H_FIFO_SRC, 1, &status, "Failed to read L3GD20 fifo status")) return false; if ((status & 0x40) != 0) { HAL_INFO("L3GD20 fifo overrun\n"); if (!m_settings->HALWrite(m_gyroSlaveAddr, L3GD20H_CTRL5, 0x10, "Failed to set L3GD20 CTRL5")) return false; if (!m_settings->HALWrite(m_gyroSlaveAddr, L3GD20H_FIFO_CTRL, 0x0, "Failed to set L3GD20 FIFO mode")) return false; if (!m_settings->HALWrite(m_gyroSlaveAddr, L3GD20H_FIFO_CTRL, 0x3f, "Failed to set L3GD20 FIFO mode")) return false; if (!setGyroCTRL5()) return false; m_imuData.timestamp += m_sampleInterval * 32; return false; } // get count of samples in fifo count = status & 0x1f; if ((m_cacheCount == 0) && (count > 0) && (count < GD20HM303DLHC_FIFO_THRESH)) { // special case of a small fifo and nothing cached - just handle as simple read if (!m_settings->HALRead(m_gyroSlaveAddr, 0x80 | L3GD20H_OUT_X_L, 6, gyroData, "Failed to read L3GD20 data")) return false; if (!m_settings->HALRead(m_accelSlaveAddr, 0x80 | LSM303DLHC_OUT_X_L_A, 6, accelData, "Failed to read LSM303DLHC accel data")) return false; if (!m_settings->HALRead(m_compassSlaveAddr, 0x80 | LSM303DLHC_OUT_X_H_M, 6, compassData, "Failed to read LSM303DLHC compass data")) return false; if (m_firstTime) m_imuData.timestamp = RTMath::currentUSecsSinceEpoch(); else m_imuData.timestamp += m_sampleInterval; m_firstTime = false; } else { if (count >= GD20HM303DLHC_FIFO_THRESH) { // need to create a cache block if (m_cacheCount == GD20HM303DLHC_CACHE_BLOCK_COUNT) { // all cache blocks are full - discard oldest and update timestamp to account for lost samples m_imuData.timestamp += m_sampleInterval * m_cache[m_cacheOut].count; if (++m_cacheOut == GD20HM303DLHC_CACHE_BLOCK_COUNT) m_cacheOut = 0; m_cacheCount--; } if (!m_settings->HALRead(m_gyroSlaveAddr, 0x80 | L3GD20H_OUT_X_L, GD20HM303DLHC_FIFO_CHUNK_SIZE * GD20HM303DLHC_FIFO_THRESH, m_cache[m_cacheIn].data, "Failed to read L3GD20 fifo data")) return false; if (!m_settings->HALRead(m_accelSlaveAddr, 0x80 | LSM303DLHC_OUT_X_L_A, 6, m_cache[m_cacheIn].accel, "Failed to read LSM303DLHC accel data")) return false; if (!m_settings->HALRead(m_compassSlaveAddr, 0x80 | LSM303DLHC_OUT_X_H_M, 6, m_cache[m_cacheIn].compass, "Failed to read LSM303DLHC compass data")) return false; m_cache[m_cacheIn].count = GD20HM303DLHC_FIFO_THRESH; m_cache[m_cacheIn].index = 0; m_cacheCount++; if (++m_cacheIn == GD20HM303DLHC_CACHE_BLOCK_COUNT) m_cacheIn = 0; } // now fifo has been read if necessary, get something to process if (m_cacheCount == 0) return false; memcpy(gyroData, m_cache[m_cacheOut].data + m_cache[m_cacheOut].index, GD20HM303DLHC_FIFO_CHUNK_SIZE); memcpy(accelData, m_cache[m_cacheOut].accel, 6); memcpy(compassData, m_cache[m_cacheOut].compass, 6); m_cache[m_cacheOut].index += GD20HM303DLHC_FIFO_CHUNK_SIZE; if (--m_cache[m_cacheOut].count == 0) { // this cache block is now empty if (++m_cacheOut == GD20HM303DLHC_CACHE_BLOCK_COUNT) m_cacheOut = 0; m_cacheCount--; } if (m_firstTime) m_imuData.timestamp = RTMath::currentUSecsSinceEpoch(); else m_imuData.timestamp += m_sampleInterval; m_firstTime = false; } #else if (!m_settings->HALRead(m_gyroSlaveAddr, L3GD20H_STATUS, 1, &status, "Failed to read L3GD20H status")) return false; if ((status & 0x8) == 0) return false; if (!m_settings->HALRead(m_gyroSlaveAddr, 0x80 | L3GD20H_OUT_X_L, 6, gyroData, "Failed to read L3GD20H data")) return false; m_imuData.timestamp = RTMath::currentUSecsSinceEpoch(); if (!m_settings->HALRead(m_accelSlaveAddr, 0x80 | LSM303DLHC_OUT_X_L_A, 6, accelData, "Failed to read LSM303DLHC accel data")) return false; if (!m_settings->HALRead(m_compassSlaveAddr, 0x80 | LSM303DLHC_OUT_X_H_M, 6, compassData, "Failed to read LSM303DLHC compass data")) return false; #endif RTMath::convertToVector(gyroData, m_imuData.gyro, m_gyroScale, false); RTMath::convertToVector(accelData, m_imuData.accel, m_accelScale, false); m_imuData.compass.setX((RTFLOAT)((int16_t)(((uint16_t)compassData[0] << 8) | (uint16_t)compassData[1])) * m_compassScaleXY); m_imuData.compass.setY((RTFLOAT)((int16_t)(((uint16_t)compassData[2] << 8) | (uint16_t)compassData[3])) * m_compassScaleXY); m_imuData.compass.setZ((RTFLOAT)((int16_t)(((uint16_t)compassData[4] << 8) | (uint16_t)compassData[5])) * m_compassScaleZ); // sort out gyro axes m_imuData.gyro.setX(m_imuData.gyro.x()); m_imuData.gyro.setY(-m_imuData.gyro.y()); m_imuData.gyro.setZ(-m_imuData.gyro.z()); // sort out accel data; m_imuData.accel.setX(-m_imuData.accel.x()); // sort out compass axes RTFLOAT temp; temp = m_imuData.compass.z(); m_imuData.compass.setZ(-m_imuData.compass.y()); m_imuData.compass.setY(-temp); // now do standard processing handleGyroBias(); calibrateAverageCompass(); calibrateAccel(); // now update the filter updateFusion(); return true; } rtimulib-7.2.1/RTIMULib/IMUDrivers/RTIMUGD20HM303DLHC.h000066400000000000000000000072701254201074400214310ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTIMUGD20HM303DLHC_H #define _RTIMUGD20HM303DLHC_H #include "RTIMU.h" // Define this symbol to use cache mode //#define GD20M303DLHC_CACHE_MODE // not reliable at the moment #ifdef GD20M303DLHC_CACHE_MODE // Cache defs #define GD20M303DLHC_FIFO_CHUNK_SIZE 6 // 6 bytes of gyro data #define GD20M303DLHC_FIFO_THRESH 16 // threshold point in fifo #define GD20M303DLHC_CACHE_BLOCK_COUNT 16 // number of cache blocks typedef struct { unsigned char data[GD20M303DLHC_FIFO_THRESH * GD20M303DLHC_FIFO_CHUNK_SIZE]; int count; // number of chunks in the cache block int index; // current index into the cache unsigned char accel[6]; // the raw accel readings for the block unsigned char compass[6]; // the raw compass readings for the block } GD20M303DLHC_CACHE_BLOCK; #endif class RTIMUGD20HM303DLHC : public RTIMU { public: RTIMUGD20HM303DLHC(RTIMUSettings *settings); ~RTIMUGD20HM303DLHC(); virtual const char *IMUName() { return "L3GD20H + LSM303DLHC"; } virtual int IMUType() { return RTIMU_TYPE_GD20HM303DLHC; } virtual bool IMUInit(); virtual int IMUGetPollInterval(); virtual bool IMURead(); private: bool setGyroSampleRate(); bool setGyroCTRL2(); bool setGyroCTRL4(); bool setGyroCTRL5(); bool setAccelCTRL1(); bool setAccelCTRL4(); bool setCompassCRA(); bool setCompassCRB(); bool setCompassCRM(); unsigned char m_gyroSlaveAddr; // I2C address of L3GD20 unsigned char m_accelSlaveAddr; // I2C address of LSM303DLHC accel unsigned char m_compassSlaveAddr; // I2C address of LSM303DLHC compass RTFLOAT m_gyroScale; RTFLOAT m_accelScale; RTFLOAT m_compassScaleXY; RTFLOAT m_compassScaleZ; #ifdef GD20M303DLHC_CACHE_MODE bool m_firstTime; // if first sample GD20M303DLHC_CACHE_BLOCK m_cache[GD20M303DLHC_CACHE_BLOCK_COUNT]; // the cache itself int m_cacheIn; // the in index int m_cacheOut; // the out index int m_cacheCount; // number of used cache blocks #endif }; #endif // _RTIMUGD20HM303DLHC_H rtimulib-7.2.1/RTIMULib/IMUDrivers/RTIMUGD20M303DLHC.cpp000066400000000000000000000370271254201074400216570ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTIMUGD20M303DLHC.h" #include "RTIMUSettings.h" // this sets the learning rate for compass running average calculation #define COMPASS_ALPHA 0.2f RTIMUGD20M303DLHC::RTIMUGD20M303DLHC(RTIMUSettings *settings) : RTIMU(settings) { m_sampleRate = 100; } RTIMUGD20M303DLHC::~RTIMUGD20M303DLHC() { } bool RTIMUGD20M303DLHC::IMUInit() { unsigned char result; #ifdef GD20M303DLHC_CACHE_MODE m_firstTime = true; m_cacheIn = m_cacheOut = m_cacheCount = 0; #endif // set validity flags m_imuData.fusionPoseValid = false; m_imuData.fusionQPoseValid = false; m_imuData.gyroValid = true; m_imuData.accelValid = true; m_imuData.compassValid = true; m_imuData.pressureValid = false; m_imuData.temperatureValid = false; m_imuData.humidityValid = false; // configure IMU m_gyroSlaveAddr = m_settings->m_I2CSlaveAddress; m_accelSlaveAddr = LSM303DLHC_ACCEL_ADDRESS; m_compassSlaveAddr = LSM303DLHC_COMPASS_ADDRESS; setCalibrationData(); // enable the I2C bus if (!m_settings->HALOpen()) return false; // Set up the gyro if (!m_settings->HALWrite(m_gyroSlaveAddr, L3GD20_CTRL5, 0x80, "Failed to boot L3GD20")) return false; if (!m_settings->HALRead(m_gyroSlaveAddr, L3GD20_WHO_AM_I, 1, &result, "Failed to read L3GD20 id")) return false; if (result != L3GD20_ID) { HAL_ERROR1("Incorrect L3GD20 id %d\n", result); return false; } if (!setGyroSampleRate()) return false; if (!setGyroCTRL2()) return false; if (!setGyroCTRL4()) return false; // Set up the accel if (!setAccelCTRL1()) return false; if (!setAccelCTRL4()) return false; // Set up the compass if (!setCompassCRA()) return false; if (!setCompassCRB()) return false; if (!setCompassCRM()) return false; #ifdef GD20M303DLHC_CACHE_MODE // turn on gyro fifo if (!m_settings->HALWrite(m_gyroSlaveAddr, L3GD20_FIFO_CTRL, 0x3f, "Failed to set L3GD20 FIFO mode")) return false; #endif if (!setGyroCTRL5()) return false; gyroBiasInit(); HAL_INFO("GD20M303DLHC init complete\n"); return true; } bool RTIMUGD20M303DLHC::setGyroSampleRate() { unsigned char ctrl1; switch (m_settings->m_GD20M303DLHCGyroSampleRate) { case L3GD20_SAMPLERATE_95: ctrl1 = 0x0f; m_sampleRate = 95; break; case L3GD20_SAMPLERATE_190: ctrl1 = 0x4f; m_sampleRate = 190; break; case L3GD20_SAMPLERATE_380: ctrl1 = 0x8f; m_sampleRate = 380; break; case L3GD20_SAMPLERATE_760: ctrl1 = 0xcf; m_sampleRate = 760; break; default: HAL_ERROR1("Illegal L3GD20 sample rate code %d\n", m_settings->m_GD20M303DLHCGyroSampleRate); return false; } m_sampleInterval = (uint64_t)1000000 / m_sampleRate; switch (m_settings->m_GD20M303DLHCGyroBW) { case L3GD20_BANDWIDTH_0: ctrl1 |= 0x00; break; case L3GD20_BANDWIDTH_1: ctrl1 |= 0x10; break; case L3GD20_BANDWIDTH_2: ctrl1 |= 0x20; break; case L3GD20_BANDWIDTH_3: ctrl1 |= 0x30; break; } return (m_settings->HALWrite(m_gyroSlaveAddr, L3GD20_CTRL1, ctrl1, "Failed to set L3GD20 CTRL1")); } bool RTIMUGD20M303DLHC::setGyroCTRL2() { if ((m_settings->m_GD20M303DLHCGyroHpf < L3GD20_HPF_0) || (m_settings->m_GD20M303DLHCGyroHpf > L3GD20_HPF_9)) { HAL_ERROR1("Illegal L3GD20 high pass filter code %d\n", m_settings->m_GD20M303DLHCGyroHpf); return false; } return m_settings->HALWrite(m_gyroSlaveAddr, L3GD20_CTRL2, m_settings->m_GD20M303DLHCGyroHpf, "Failed to set L3GD20 CTRL2"); } bool RTIMUGD20M303DLHC::setGyroCTRL4() { unsigned char ctrl4; switch (m_settings->m_GD20M303DLHCGyroFsr) { case L3GD20_FSR_250: ctrl4 = 0x00; m_gyroScale = (RTFLOAT)0.00875 * RTMATH_DEGREE_TO_RAD; break; case L3GD20_FSR_500: ctrl4 = 0x10; m_gyroScale = (RTFLOAT)0.0175 * RTMATH_DEGREE_TO_RAD; break; case L3GD20_FSR_2000: ctrl4 = 0x20; m_gyroScale = (RTFLOAT)0.07 * RTMATH_DEGREE_TO_RAD; break; default: HAL_ERROR1("Illegal L3GD20 FSR code %d\n", m_settings->m_GD20M303DLHCGyroFsr); return false; } return m_settings->HALWrite(m_gyroSlaveAddr, L3GD20_CTRL4, ctrl4, "Failed to set L3GD20 CTRL4"); } bool RTIMUGD20M303DLHC::setGyroCTRL5() { unsigned char ctrl5; // Turn on hpf ctrl5 = 0x10; #ifdef GD20M303DLHC_CACHE_MODE // turn on fifo ctrl5 |= 0x40; #endif return m_settings->HALWrite(m_gyroSlaveAddr, L3GD20_CTRL5, ctrl5, "Failed to set L3GD20 CTRL5"); } bool RTIMUGD20M303DLHC::setAccelCTRL1() { unsigned char ctrl1; if ((m_settings->m_GD20M303DLHCAccelSampleRate < 1) || (m_settings->m_GD20M303DLHCAccelSampleRate > 7)) { HAL_ERROR1("Illegal LSM303DLHC accel sample rate code %d\n", m_settings->m_GD20M303DLHCAccelSampleRate); return false; } ctrl1 = (m_settings->m_GD20M303DLHCAccelSampleRate << 4) | 0x07; return m_settings->HALWrite(m_accelSlaveAddr, LSM303DLHC_CTRL1_A, ctrl1, "Failed to set LSM303D CTRL1"); } bool RTIMUGD20M303DLHC::setAccelCTRL4() { unsigned char ctrl4; switch (m_settings->m_GD20M303DLHCAccelFsr) { case LSM303DLHC_ACCEL_FSR_2: m_accelScale = (RTFLOAT)0.001 / (RTFLOAT)64; break; case LSM303DLHC_ACCEL_FSR_4: m_accelScale = (RTFLOAT)0.002 / (RTFLOAT)64; break; case LSM303DLHC_ACCEL_FSR_8: m_accelScale = (RTFLOAT)0.004 / (RTFLOAT)64; break; case LSM303DLHC_ACCEL_FSR_16: m_accelScale = (RTFLOAT)0.012 / (RTFLOAT)64; break; default: HAL_ERROR1("Illegal LSM303DLHC accel FSR code %d\n", m_settings->m_GD20M303DLHCAccelFsr); return false; } ctrl4 = (m_settings->m_GD20M303DLHCAccelFsr << 4); return m_settings->HALWrite(m_accelSlaveAddr, LSM303DLHC_CTRL2_A, ctrl4, "Failed to set LSM303DLHC CTRL4"); } bool RTIMUGD20M303DLHC::setCompassCRA() { unsigned char cra; if ((m_settings->m_GD20M303DLHCCompassSampleRate < 0) || (m_settings->m_GD20M303DLHCCompassSampleRate > 7)) { HAL_ERROR1("Illegal LSM303DLHC compass sample rate code %d\n", m_settings->m_GD20M303DLHCCompassSampleRate); return false; } cra = (m_settings->m_GD20M303DLHCCompassSampleRate << 2); return m_settings->HALWrite(m_compassSlaveAddr, LSM303DLHC_CRA_M, cra, "Failed to set LSM303DLHC CRA_M"); } bool RTIMUGD20M303DLHC::setCompassCRB() { unsigned char crb; // convert FSR to uT switch (m_settings->m_GD20M303DLHCCompassFsr) { case LSM303DLHC_COMPASS_FSR_1_3: crb = 0x20; m_compassScaleXY = (RTFLOAT)100 / (RTFLOAT)1100; m_compassScaleZ = (RTFLOAT)100 / (RTFLOAT)980; break; case LSM303DLHC_COMPASS_FSR_1_9: crb = 0x40; m_compassScaleXY = (RTFLOAT)100 / (RTFLOAT)855; m_compassScaleZ = (RTFLOAT)100 / (RTFLOAT)760; break; case LSM303DLHC_COMPASS_FSR_2_5: crb = 0x60; m_compassScaleXY = (RTFLOAT)100 / (RTFLOAT)670; m_compassScaleZ = (RTFLOAT)100 / (RTFLOAT)600; break; case LSM303DLHC_COMPASS_FSR_4: crb = 0x80; m_compassScaleXY = (RTFLOAT)100 / (RTFLOAT)450; m_compassScaleZ = (RTFLOAT)100 / (RTFLOAT)400; break; case LSM303DLHC_COMPASS_FSR_4_7: crb = 0xa0; m_compassScaleXY = (RTFLOAT)100 / (RTFLOAT)400; m_compassScaleZ = (RTFLOAT)100 / (RTFLOAT)355; break; case LSM303DLHC_COMPASS_FSR_5_6: crb = 0xc0; m_compassScaleXY = (RTFLOAT)100 / (RTFLOAT)330; m_compassScaleZ = (RTFLOAT)100 / (RTFLOAT)295; break; case LSM303DLHC_COMPASS_FSR_8_1: crb = 0xe0; m_compassScaleXY = (RTFLOAT)100 / (RTFLOAT)230; m_compassScaleZ = (RTFLOAT)100 / (RTFLOAT)205; break; default: HAL_ERROR1("Illegal LSM303DLHC compass FSR code %d\n", m_settings->m_GD20M303DLHCCompassFsr); return false; } return m_settings->HALWrite(m_compassSlaveAddr, LSM303DLHC_CRB_M, crb, "Failed to set LSM303DLHC CRB_M"); } bool RTIMUGD20M303DLHC::setCompassCRM() { return m_settings->HALWrite(m_compassSlaveAddr, LSM303DLHC_CRM_M, 0x00, "Failed to set LSM303DLHC CRM_M"); } int RTIMUGD20M303DLHC::IMUGetPollInterval() { return (400 / m_sampleRate); } bool RTIMUGD20M303DLHC::IMURead() { unsigned char status; unsigned char gyroData[6]; unsigned char accelData[6]; unsigned char compassData[6]; #ifdef GD20M303DLHC_CACHE_MODE int count; if (!m_settings->HALRead(m_gyroSlaveAddr, L3GD20_FIFO_SRC, 1, &status, "Failed to read L3GD20 fifo status")) return false; if ((status & 0x40) != 0) { HAL_INFO("L3GD20 fifo overrun\n"); if (!m_settings->HALWrite(m_gyroSlaveAddr, L3GD20_CTRL5, 0x10, "Failed to set L3GD20 CTRL5")) return false; if (!m_settings->HALWrite(m_gyroSlaveAddr, L3GD20_FIFO_CTRL, 0x0, "Failed to set L3GD20 FIFO mode")) return false; if (!m_settings->HALWrite(m_gyroSlaveAddr, L3GD20_FIFO_CTRL, 0x3f, "Failed to set L3GD20 FIFO mode")) return false; if (!setGyroCTRL5()) return false; m_imuData.timestamp += m_sampleInterval * 32; return false; } // get count of samples in fifo count = status & 0x1f; if ((m_cacheCount == 0) && (count > 0) && (count < GD20M303DLHC_FIFO_THRESH)) { // special case of a small fifo and nothing cached - just handle as simple read if (!m_settings->HALRead(m_gyroSlaveAddr, 0x80 | L3GD20_OUT_X_L, 6, gyroData, "Failed to read L3GD20 data")) return false; if (!m_settings->HALRead(m_accelSlaveAddr, 0x80 | LSM303DLHC_OUT_X_L_A, 6, accelData, "Failed to read LSM303DLHC accel data")) return false; if (!m_settings->HALRead(m_compassSlaveAddr, 0x80 | LSM303DLHC_OUT_X_H_M, 6, compassData, "Failed to read LSM303DLHC compass data")) return false; if (m_firstTime) m_imuData.timestamp = RTMath::currentUSecsSinceEpoch(); else m_imuData.timestamp += m_sampleInterval; m_firstTime = false; } else { if (count >= GD20M303DLHC_FIFO_THRESH) { // need to create a cache block if (m_cacheCount == GD20M303DLHC_CACHE_BLOCK_COUNT) { // all cache blocks are full - discard oldest and update timestamp to account for lost samples m_imuData.timestamp += m_sampleInterval * m_cache[m_cacheOut].count; if (++m_cacheOut == GD20M303DLHC_CACHE_BLOCK_COUNT) m_cacheOut = 0; m_cacheCount--; } if (!m_settings->HALRead(m_gyroSlaveAddr, 0x80 | L3GD20_OUT_X_L, GD20M303DLHC_FIFO_CHUNK_SIZE * GD20M303DLHC_FIFO_THRESH, m_cache[m_cacheIn].data, "Failed to read L3GD20 fifo data")) return false; if (!m_settings->HALRead(m_accelSlaveAddr, 0x80 | LSM303DLHC_OUT_X_L_A, 6, m_cache[m_cacheIn].accel, "Failed to read LSM303DLHC accel data")) return false; if (!m_settings->HALRead(m_compassSlaveAddr, 0x80 | LSM303DLHC_OUT_X_H_M, 6, m_cache[m_cacheIn].compass, "Failed to read LSM303DLHC compass data")) return false; m_cache[m_cacheIn].count = GD20M303DLHC_FIFO_THRESH; m_cache[m_cacheIn].index = 0; m_cacheCount++; if (++m_cacheIn == GD20M303DLHC_CACHE_BLOCK_COUNT) m_cacheIn = 0; } // now fifo has been read if necessary, get something to process if (m_cacheCount == 0) return false; memcpy(gyroData, m_cache[m_cacheOut].data + m_cache[m_cacheOut].index, GD20M303DLHC_FIFO_CHUNK_SIZE); memcpy(accelData, m_cache[m_cacheOut].accel, 6); memcpy(compassData, m_cache[m_cacheOut].compass, 6); m_cache[m_cacheOut].index += GD20M303DLHC_FIFO_CHUNK_SIZE; if (--m_cache[m_cacheOut].count == 0) { // this cache block is now empty if (++m_cacheOut == GD20M303DLHC_CACHE_BLOCK_COUNT) m_cacheOut = 0; m_cacheCount--; } if (m_firstTime) m_imuData.timestamp = RTMath::currentUSecsSinceEpoch(); else m_imuData.timestamp += m_sampleInterval; m_firstTime = false; } #else if (!m_settings->HALRead(m_gyroSlaveAddr, L3GD20_STATUS, 1, &status, "Failed to read L3GD20 status")) return false; if ((status & 0x8) == 0) return false; if (!m_settings->HALRead(m_gyroSlaveAddr, 0x80 | L3GD20_OUT_X_L, 6, gyroData, "Failed to read L3GD20 data")) return false; m_imuData.timestamp = RTMath::currentUSecsSinceEpoch(); if (!m_settings->HALRead(m_accelSlaveAddr, 0x80 | LSM303DLHC_OUT_X_L_A, 6, accelData, "Failed to read LSM303DLHC accel data")) return false; if (!m_settings->HALRead(m_compassSlaveAddr, 0x80 | LSM303DLHC_OUT_X_H_M, 6, compassData, "Failed to read LSM303DLHC compass data")) return false; #endif RTMath::convertToVector(gyroData, m_imuData.gyro, m_gyroScale, false); RTMath::convertToVector(accelData, m_imuData.accel, m_accelScale, false); m_imuData.compass.setX((RTFLOAT)((int16_t)(((uint16_t)compassData[0] << 8) | (uint16_t)compassData[1])) * m_compassScaleXY); m_imuData.compass.setY((RTFLOAT)((int16_t)(((uint16_t)compassData[2] << 8) | (uint16_t)compassData[3])) * m_compassScaleXY); m_imuData.compass.setZ((RTFLOAT)((int16_t)(((uint16_t)compassData[4] << 8) | (uint16_t)compassData[5])) * m_compassScaleZ); // sort out gyro axes m_imuData.gyro.setX(m_imuData.gyro.x()); m_imuData.gyro.setY(-m_imuData.gyro.y()); m_imuData.gyro.setZ(-m_imuData.gyro.z()); // sort out accel data; m_imuData.accel.setX(-m_imuData.accel.x()); // sort out compass axes RTFLOAT temp; temp = m_imuData.compass.z(); m_imuData.compass.setZ(-m_imuData.compass.y()); m_imuData.compass.setY(-temp); // now do standard processing handleGyroBias(); calibrateAverageCompass(); calibrateAccel(); // now update the filter updateFusion(); return true; } rtimulib-7.2.1/RTIMULib/IMUDrivers/RTIMUGD20M303DLHC.h000066400000000000000000000072571254201074400213260ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTIMUGD20M303DLHC_H #define _RTIMUGD20M303DLHC_H #include "RTIMU.h" // Define this symbol to use cache mode //#define GD20M303DLHC_CACHE_MODE // not reliable at the moment #ifdef GD20M303DLHC_CACHE_MODE // Cache defs #define GD20M303DLHC_FIFO_CHUNK_SIZE 6 // 6 bytes of gyro data #define GD20M303DLHC_FIFO_THRESH 16 // threshold point in fifo #define GD20M303DLHC_CACHE_BLOCK_COUNT 16 // number of cache blocks typedef struct { unsigned char data[GD20M303DLHC_FIFO_THRESH * GD20M303DLHC_FIFO_CHUNK_SIZE]; int count; // number of chunks in the cache block int index; // current index into the cache unsigned char accel[6]; // the raw accel readings for the block unsigned char compass[6]; // the raw compass readings for the block } GD20M303DLHC_CACHE_BLOCK; #endif class RTIMUGD20M303DLHC : public RTIMU { public: RTIMUGD20M303DLHC(RTIMUSettings *settings); ~RTIMUGD20M303DLHC(); virtual const char *IMUName() { return "L3GD20 + LSM303DLHC"; } virtual int IMUType() { return RTIMU_TYPE_GD20M303DLHC; } virtual bool IMUInit(); virtual int IMUGetPollInterval(); virtual bool IMURead(); private: bool setGyroSampleRate(); bool setGyroCTRL2(); bool setGyroCTRL4(); bool setGyroCTRL5(); bool setAccelCTRL1(); bool setAccelCTRL4(); bool setCompassCRA(); bool setCompassCRB(); bool setCompassCRM(); unsigned char m_gyroSlaveAddr; // I2C address of L3GD20 unsigned char m_accelSlaveAddr; // I2C address of LSM303DLHC accel unsigned char m_compassSlaveAddr; // I2C address of LSM303DLHC compass RTFLOAT m_gyroScale; RTFLOAT m_accelScale; RTFLOAT m_compassScaleXY; RTFLOAT m_compassScaleZ; #ifdef GD20M303DLHC_CACHE_MODE bool m_firstTime; // if first sample GD20M303DLHC_CACHE_BLOCK m_cache[GD20M303DLHC_CACHE_BLOCK_COUNT]; // the cache itself int m_cacheIn; // the in index int m_cacheOut; // the out index int m_cacheCount; // number of used cache blocks #endif }; #endif // _RTIMUGD20M303DLHC_H rtimulib-7.2.1/RTIMULib/IMUDrivers/RTIMULSM9DS0.cpp000066400000000000000000000363101254201074400212520ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTIMULSM9DS0.h" #include "RTIMUSettings.h" // this sets the learning rate for compass running average calculation #define COMPASS_ALPHA 0.2f RTIMULSM9DS0::RTIMULSM9DS0(RTIMUSettings *settings) : RTIMU(settings) { m_sampleRate = 100; } RTIMULSM9DS0::~RTIMULSM9DS0() { } bool RTIMULSM9DS0::IMUInit() { unsigned char result; #ifdef LSM9DS0_CACHE_MODE m_firstTime = true; m_cacheIn = m_cacheOut = m_cacheCount = 0; #endif // set validity flags m_imuData.fusionPoseValid = false; m_imuData.fusionQPoseValid = false; m_imuData.gyroValid = true; m_imuData.accelValid = true; m_imuData.compassValid = true; m_imuData.pressureValid = false; m_imuData.temperatureValid = false; m_imuData.humidityValid = false; // configure IMU m_gyroSlaveAddr = m_settings->m_I2CSlaveAddress; // work out accelmag address if (m_settings->HALRead(LSM9DS0_ACCELMAG_ADDRESS0, LSM9DS0_WHO_AM_I, 1, &result, "")) { if (result == LSM9DS0_ACCELMAG_ID) { m_accelCompassSlaveAddr = LSM9DS0_ACCELMAG_ADDRESS0; } } else { m_accelCompassSlaveAddr = LSM9DS0_ACCELMAG_ADDRESS1; } setCalibrationData(); // enable the I2C bus if (!m_settings->HALOpen()) return false; // Set up the gyro if (!m_settings->HALWrite(m_gyroSlaveAddr, LSM9DS0_GYRO_CTRL5, 0x80, "Failed to boot LSM9DS0")) return false; if (!m_settings->HALRead(m_gyroSlaveAddr, LSM9DS0_GYRO_WHO_AM_I, 1, &result, "Failed to read LSM9DS0 gyro id")) return false; if (result != LSM9DS0_GYRO_ID) { HAL_ERROR1("Incorrect LSM9DS0 gyro id %d\n", result); return false; } if (!setGyroSampleRate()) return false; if (!setGyroCTRL2()) return false; if (!setGyroCTRL4()) return false; // Set up the accel if (!m_settings->HALRead(m_accelCompassSlaveAddr, LSM9DS0_WHO_AM_I, 1, &result, "Failed to read LSM9DS0 accel/mag id")) return false; if (result != LSM9DS0_ACCELMAG_ID) { HAL_ERROR1("Incorrect LSM9DS0 accel/mag id %d\n", result); return false; } if (!setAccelCTRL1()) return false; if (!setAccelCTRL2()) return false; if (!setCompassCTRL5()) return false; if (!setCompassCTRL6()) return false; if (!setCompassCTRL7()) return false; #ifdef LSM9DS0_CACHE_MODE // turn on gyro fifo if (!m_settings->HALWrite(m_gyroSlaveAddr, LSM9DS0_GYRO_FIFO_CTRL, 0x3f, "Failed to set LSM9DS0 FIFO mode")) return false; #endif if (!setGyroCTRL5()) return false; gyroBiasInit(); HAL_INFO("LSM9DS0 init complete\n"); return true; } bool RTIMULSM9DS0::setGyroSampleRate() { unsigned char ctrl1; switch (m_settings->m_LSM9DS0GyroSampleRate) { case LSM9DS0_GYRO_SAMPLERATE_95: ctrl1 = 0x0f; m_sampleRate = 95; break; case LSM9DS0_GYRO_SAMPLERATE_190: ctrl1 = 0x4f; m_sampleRate = 190; break; case LSM9DS0_GYRO_SAMPLERATE_380: ctrl1 = 0x8f; m_sampleRate = 380; break; case LSM9DS0_GYRO_SAMPLERATE_760: ctrl1 = 0xcf; m_sampleRate = 760; break; default: HAL_ERROR1("Illegal LSM9DS0 gyro sample rate code %d\n", m_settings->m_LSM9DS0GyroSampleRate); return false; } m_sampleInterval = (uint64_t)1000000 / m_sampleRate; switch (m_settings->m_LSM9DS0GyroBW) { case LSM9DS0_GYRO_BANDWIDTH_0: ctrl1 |= 0x00; break; case LSM9DS0_GYRO_BANDWIDTH_1: ctrl1 |= 0x10; break; case LSM9DS0_GYRO_BANDWIDTH_2: ctrl1 |= 0x20; break; case LSM9DS0_GYRO_BANDWIDTH_3: ctrl1 |= 0x30; break; } return (m_settings->HALWrite(m_gyroSlaveAddr, LSM9DS0_GYRO_CTRL1, ctrl1, "Failed to set LSM9DS0 gyro CTRL1")); } bool RTIMULSM9DS0::setGyroCTRL2() { if ((m_settings->m_LSM9DS0GyroHpf < LSM9DS0_GYRO_HPF_0) || (m_settings->m_LSM9DS0GyroHpf > LSM9DS0_GYRO_HPF_9)) { HAL_ERROR1("Illegal LSM9DS0 gyro high pass filter code %d\n", m_settings->m_LSM9DS0GyroHpf); return false; } return m_settings->HALWrite(m_gyroSlaveAddr, LSM9DS0_GYRO_CTRL2, m_settings->m_LSM9DS0GyroHpf, "Failed to set LSM9DS0 gyro CTRL2"); } bool RTIMULSM9DS0::setGyroCTRL4() { unsigned char ctrl4; switch (m_settings->m_LSM9DS0GyroFsr) { case LSM9DS0_GYRO_FSR_250: ctrl4 = 0x00; m_gyroScale = (RTFLOAT)0.00875 * RTMATH_DEGREE_TO_RAD; break; case LSM9DS0_GYRO_FSR_500: ctrl4 = 0x10; m_gyroScale = (RTFLOAT)0.0175 * RTMATH_DEGREE_TO_RAD; break; case LSM9DS0_GYRO_FSR_2000: ctrl4 = 0x20; m_gyroScale = (RTFLOAT)0.07 * RTMATH_DEGREE_TO_RAD; break; default: HAL_ERROR1("Illegal LSM9DS0 gyro FSR code %d\n", m_settings->m_LSM9DS0GyroFsr); return false; } return m_settings->HALWrite(m_gyroSlaveAddr, LSM9DS0_GYRO_CTRL4, ctrl4, "Failed to set LSM9DS0 gyro CTRL4"); } bool RTIMULSM9DS0::setGyroCTRL5() { unsigned char ctrl5; // Turn on hpf ctrl5 = 0x10; #ifdef LSM9DS0_CACHE_MODE // turn on fifo ctrl5 |= 0x40; #endif return m_settings->HALWrite(m_gyroSlaveAddr, LSM9DS0_GYRO_CTRL5, ctrl5, "Failed to set LSM9DS0 gyro CTRL5"); } bool RTIMULSM9DS0::setAccelCTRL1() { unsigned char ctrl1; if ((m_settings->m_LSM9DS0AccelSampleRate < 0) || (m_settings->m_LSM9DS0AccelSampleRate > 10)) { HAL_ERROR1("Illegal LSM9DS0 accel sample rate code %d\n", m_settings->m_LSM9DS0AccelSampleRate); return false; } ctrl1 = (m_settings->m_LSM9DS0AccelSampleRate << 4) | 0x07; return m_settings->HALWrite(m_accelCompassSlaveAddr, LSM9DS0_CTRL1, ctrl1, "Failed to set LSM9DS0 accell CTRL1"); } bool RTIMULSM9DS0::setAccelCTRL2() { unsigned char ctrl2; if ((m_settings->m_LSM9DS0AccelLpf < 0) || (m_settings->m_LSM9DS0AccelLpf > 3)) { HAL_ERROR1("Illegal LSM9DS0 accel low pass fiter code %d\n", m_settings->m_LSM9DS0AccelLpf); return false; } switch (m_settings->m_LSM9DS0AccelFsr) { case LSM9DS0_ACCEL_FSR_2: m_accelScale = (RTFLOAT)0.000061; break; case LSM9DS0_ACCEL_FSR_4: m_accelScale = (RTFLOAT)0.000122; break; case LSM9DS0_ACCEL_FSR_6: m_accelScale = (RTFLOAT)0.000183; break; case LSM9DS0_ACCEL_FSR_8: m_accelScale = (RTFLOAT)0.000244; break; case LSM9DS0_ACCEL_FSR_16: m_accelScale = (RTFLOAT)0.000732; break; default: HAL_ERROR1("Illegal LSM9DS0 accel FSR code %d\n", m_settings->m_LSM9DS0AccelFsr); return false; } ctrl2 = (m_settings->m_LSM9DS0AccelLpf << 6) | (m_settings->m_LSM9DS0AccelFsr << 3); return m_settings->HALWrite(m_accelCompassSlaveAddr, LSM9DS0_CTRL2, ctrl2, "Failed to set LSM9DS0 accel CTRL2"); } bool RTIMULSM9DS0::setCompassCTRL5() { unsigned char ctrl5; if ((m_settings->m_LSM9DS0CompassSampleRate < 0) || (m_settings->m_LSM9DS0CompassSampleRate > 5)) { HAL_ERROR1("Illegal LSM9DS0 compass sample rate code %d\n", m_settings->m_LSM9DS0CompassSampleRate); return false; } ctrl5 = (m_settings->m_LSM9DS0CompassSampleRate << 2); #ifdef LSM9DS0_CACHE_MODE // enable fifo ctrl5 |= 0x40; #endif return m_settings->HALWrite(m_accelCompassSlaveAddr, LSM9DS0_CTRL5, ctrl5, "Failed to set LSM9DS0 compass CTRL5"); } bool RTIMULSM9DS0::setCompassCTRL6() { unsigned char ctrl6; // convert FSR to uT switch (m_settings->m_LSM9DS0CompassFsr) { case LSM9DS0_COMPASS_FSR_2: ctrl6 = 0; m_compassScale = (RTFLOAT)0.008; break; case LSM9DS0_COMPASS_FSR_4: ctrl6 = 0x20; m_compassScale = (RTFLOAT)0.016; break; case LSM9DS0_COMPASS_FSR_8: ctrl6 = 0x40; m_compassScale = (RTFLOAT)0.032; break; case LSM9DS0_COMPASS_FSR_12: ctrl6 = 0x60; m_compassScale = (RTFLOAT)0.0479; break; default: HAL_ERROR1("Illegal LSM9DS0 compass FSR code %d\n", m_settings->m_LSM9DS0CompassFsr); return false; } return m_settings->HALWrite(m_accelCompassSlaveAddr, LSM9DS0_CTRL6, ctrl6, "Failed to set LSM9DS0 compass CTRL6"); } bool RTIMULSM9DS0::setCompassCTRL7() { return m_settings->HALWrite(m_accelCompassSlaveAddr, LSM9DS0_CTRL7, 0x60, "Failed to set LSM9DS0CTRL7"); } int RTIMULSM9DS0::IMUGetPollInterval() { return (400 / m_sampleRate); } bool RTIMULSM9DS0::IMURead() { unsigned char status; unsigned char gyroData[6]; unsigned char accelData[6]; unsigned char compassData[6]; #ifdef LSM9DS0_CACHE_MODE int count; if (!m_settings->HALRead(m_gyroSlaveAddr, LSM9DS0_GYRO_FIFO_SRC, 1, &status, "Failed to read LSM9DS0 gyro fifo status")) return false; if ((status & 0x40) != 0) { HAL_INFO("LSM9DS0 gyro fifo overrun\n"); if (!m_settings->HALWrite(m_gyroSlaveAddr, LSM9DS0_GYRO_CTRL5, 0x10, "Failed to set LSM9DS0 gyro CTRL5")) return false; if (!m_settings->HALWrite(m_gyroSlaveAddr, LSM9DS0_GYRO_FIFO_CTRL, 0x0, "Failed to set LSM9DS0 gyro FIFO mode")) return false; if (!m_settings->HALWrite(m_gyroSlaveAddr, LSM9DS0_GYRO_FIFO_CTRL, 0x3f, "Failed to set LSM9DS0 gyro FIFO mode")) return false; if (!setGyroCTRL5()) return false; m_imuData.timestamp += m_sampleInterval * 32; return false; } // get count of samples in fifo count = status & 0x1f; if ((m_cacheCount == 0) && (count > 0) && (count < LSM9DS0_FIFO_THRESH)) { // special case of a small fifo and nothing cached - just handle as simple read if (!m_settings->HALRead(m_gyroSlaveAddr, 0x80 | LSM9DS0_GYRO_OUT_X_L, 6, gyroData, "Failed to read LSM9DS0 gyro data")) return false; if (!m_settings->HALRead(m_accelCompassSlaveAddr, 0x80 | LSM9DS0_OUT_X_L_A, 6, accelData, "Failed to read LSM9DS0 accel data")) return false; if (!m_settings->HALRead(m_accelCompassSlaveAddr, 0x80 | LSM9DS0_OUT_X_L_M, 6, compassData, "Failed to read LSM9DS0 compass data")) return false; if (m_firstTime) m_imuData.timestamp = RTMath::currentUSecsSinceEpoch(); else m_imuData.timestamp += m_sampleInterval; m_firstTime = false; } else { if (count >= LSM9DS0_FIFO_THRESH) { // need to create a cache block if (m_cacheCount == LSM9DS0_CACHE_BLOCK_COUNT) { // all cache blocks are full - discard oldest and update timestamp to account for lost samples m_imuData.timestamp += m_sampleInterval * m_cache[m_cacheOut].count; if (++m_cacheOut == LSM9DS0_CACHE_BLOCK_COUNT) m_cacheOut = 0; m_cacheCount--; } if (!m_settings->HALRead(m_gyroSlaveAddr, 0x80 | LSM9DS0_GYRO_OUT_X_L, LSM9DS0_FIFO_CHUNK_SIZE * LSM9DS0_FIFO_THRESH, m_cache[m_cacheIn].data, "Failed to read LSM9DS0 fifo data")) return false; if (!m_settings->HALRead(m_accelCompassSlaveAddr, 0x80 | LSM9DS0_OUT_X_L_A, 6, m_cache[m_cacheIn].accel, "Failed to read LSM9DS0 accel data")) return false; if (!m_settings->HALRead(m_accelCompassSlaveAddr, 0x80 | LSM9DS0_OUT_X_L_M, 6, m_cache[m_cacheIn].compass, "Failed to read LSM9DS0 compass data")) return false; m_cache[m_cacheIn].count = LSM9DS0_FIFO_THRESH; m_cache[m_cacheIn].index = 0; m_cacheCount++; if (++m_cacheIn == LSM9DS0_CACHE_BLOCK_COUNT) m_cacheIn = 0; } // now fifo has been read if necessary, get something to process if (m_cacheCount == 0) return false; memcpy(gyroData, m_cache[m_cacheOut].data + m_cache[m_cacheOut].index, LSM9DS0_FIFO_CHUNK_SIZE); memcpy(accelData, m_cache[m_cacheOut].accel, 6); memcpy(compassData, m_cache[m_cacheOut].compass, 6); m_cache[m_cacheOut].index += LSM9DS0_FIFO_CHUNK_SIZE; if (--m_cache[m_cacheOut].count == 0) { // this cache block is now empty if (++m_cacheOut == LSM9DS0_CACHE_BLOCK_COUNT) m_cacheOut = 0; m_cacheCount--; } if (m_firstTime) m_imuData.timestamp = RTMath::currentUSecsSinceEpoch(); else m_imuData.timestamp += m_sampleInterval; m_firstTime = false; } #else if (!m_settings->HALRead(m_gyroSlaveAddr, LSM9DS0_GYRO_STATUS, 1, &status, "Failed to read LSM9DS0 status")) return false; if ((status & 0x8) == 0) return false; if (!m_settings->HALRead(m_gyroSlaveAddr, 0x80 | LSM9DS0_GYRO_OUT_X_L, 6, gyroData, "Failed to read LSM9DS0 gyro data")) return false; m_imuData.timestamp = RTMath::currentUSecsSinceEpoch(); if (!m_settings->HALRead(m_accelCompassSlaveAddr, 0x80 | LSM9DS0_OUT_X_L_A, 6, accelData, "Failed to read LSM9DS0 accel data")) return false; if (!m_settings->HALRead(m_accelCompassSlaveAddr, 0x80 | LSM9DS0_OUT_X_L_M, 6, compassData, "Failed to read LSM9DS0 compass data")) return false; #endif RTMath::convertToVector(gyroData, m_imuData.gyro, m_gyroScale, false); RTMath::convertToVector(accelData, m_imuData.accel, m_accelScale, false); RTMath::convertToVector(compassData, m_imuData.compass, m_compassScale, false); // sort out gyro axes and correct for bias m_imuData.gyro.setX(m_imuData.gyro.x()); m_imuData.gyro.setY(-m_imuData.gyro.y()); m_imuData.gyro.setZ(-m_imuData.gyro.z()); // sort out accel data; m_imuData.accel.setX(-m_imuData.accel.x()); // sort out compass axes m_imuData.compass.setY(-m_imuData.compass.y()); // now do standard processing handleGyroBias(); calibrateAverageCompass(); calibrateAccel(); // now update the filter updateFusion(); return true; } rtimulib-7.2.1/RTIMULib/IMUDrivers/RTIMULSM9DS0.h000066400000000000000000000067121254201074400207220ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTIMULSM9DS0_H #define _RTIMULSM9DS0_H #include "RTIMU.h" // Define this symbol to use cache mode //#define LSM9DS0_CACHE_MODE // not reliable at the moment #ifdef LSM9DS0_CACHE_MODE // Cache defs #define LSM9DS0_FIFO_CHUNK_SIZE 6 // 6 bytes of gyro data #define LSM9DS0_FIFO_THRESH 16 // threshold point in fifo #define LSM9DS0_CACHE_BLOCK_COUNT 16 // number of cache blocks typedef struct { unsigned char data[LSM9DS0_FIFO_THRESH * LSM9DS0_FIFO_CHUNK_SIZE]; int count; // number of chunks in the cache block int index; // current index into the cache unsigned char accel[6]; // the raw accel readings for the block unsigned char compass[6]; // the raw compass readings for the block } LSM9DS0_CACHE_BLOCK; #endif class RTIMULSM9DS0 : public RTIMU { public: RTIMULSM9DS0(RTIMUSettings *settings); ~RTIMULSM9DS0(); virtual const char *IMUName() { return "LSM9DS0"; } virtual int IMUType() { return RTIMU_TYPE_LSM9DS0; } virtual bool IMUInit(); virtual int IMUGetPollInterval(); virtual bool IMURead(); private: bool setGyroSampleRate(); bool setGyroCTRL2(); bool setGyroCTRL4(); bool setGyroCTRL5(); bool setAccelCTRL1(); bool setAccelCTRL2(); bool setCompassCTRL5(); bool setCompassCTRL6(); bool setCompassCTRL7(); unsigned char m_gyroSlaveAddr; // I2C address of gyro unsigned char m_accelCompassSlaveAddr; // I2C address of accel and mag RTFLOAT m_gyroScale; RTFLOAT m_accelScale; RTFLOAT m_compassScale; #ifdef LSM9DS0_CACHE_MODE bool m_firstTime; // if first sample LSM9DS0_CACHE_BLOCK m_cache[LSM9DS0_CACHE_BLOCK_COUNT]; // the cache itself int m_cacheIn; // the in index int m_cacheOut; // the out index int m_cacheCount; // number of used cache blocks #endif }; #endif // _RTIMULSM9DS0_H rtimulib-7.2.1/RTIMULib/IMUDrivers/RTIMULSM9DS1.cpp000066400000000000000000000262451254201074400212610ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTIMULSM9DS1.h" #include "RTIMUSettings.h" // this sets the learning rate for compass running average calculation #define COMPASS_ALPHA 0.2f RTIMULSM9DS1::RTIMULSM9DS1(RTIMUSettings *settings) : RTIMU(settings) { m_sampleRate = 100; } RTIMULSM9DS1::~RTIMULSM9DS1() { } bool RTIMULSM9DS1::IMUInit() { unsigned char result; // set validity flags m_imuData.fusionPoseValid = false; m_imuData.fusionQPoseValid = false; m_imuData.gyroValid = true; m_imuData.accelValid = true; m_imuData.compassValid = true; m_imuData.pressureValid = false; m_imuData.temperatureValid = false; m_imuData.humidityValid = false; // configure IMU m_accelGyroSlaveAddr = m_settings->m_I2CSlaveAddress; // work outmag address if (m_settings->HALRead(LSM9DS1_MAG_ADDRESS0, LSM9DS1_MAG_WHO_AM_I, 1, &result, "")) { if (result == LSM9DS1_MAG_ID) { m_magSlaveAddr = LSM9DS1_MAG_ADDRESS0; } } else if (m_settings->HALRead(LSM9DS1_MAG_ADDRESS1, LSM9DS1_MAG_WHO_AM_I, 1, &result, "")) { if (result == LSM9DS1_MAG_ID) { m_magSlaveAddr = LSM9DS1_MAG_ADDRESS1; } } else if (m_settings->HALRead(LSM9DS1_MAG_ADDRESS2, LSM9DS1_MAG_WHO_AM_I, 1, &result, "")) { if (result == LSM9DS1_MAG_ID) { m_magSlaveAddr = LSM9DS1_MAG_ADDRESS2; } } else if (m_settings->HALRead(LSM9DS1_MAG_ADDRESS3, LSM9DS1_MAG_WHO_AM_I, 1, &result, "")) { if (result == LSM9DS1_MAG_ID) { m_magSlaveAddr = LSM9DS1_MAG_ADDRESS3; } } setCalibrationData(); // enable the I2C bus if (!m_settings->HALOpen()) return false; // Set up the gyro/accel if (!m_settings->HALWrite(m_accelGyroSlaveAddr, LSM9DS1_CTRL8, 0x80, "Failed to boot LSM9DS1")) return false; m_settings->delayMs(100); if (!m_settings->HALRead(m_accelGyroSlaveAddr, LSM9DS1_WHO_AM_I, 1, &result, "Failed to read LSM9DS1 accel/gyro id")) return false; if (result != LSM9DS1_ID) { HAL_ERROR1("Incorrect LSM9DS1 gyro id %d\n", result); return false; } if (!setGyroSampleRate()) return false; if (!setGyroCTRL3()) return false; // Set up the mag if (!m_settings->HALRead(m_magSlaveAddr, LSM9DS1_MAG_WHO_AM_I, 1, &result, "Failed to read LSM9DS1 accel/mag id")) return false; if (result != LSM9DS1_MAG_ID) { HAL_ERROR1("Incorrect LSM9DS1 accel/mag id %d\n", result); return false; } if (!setAccelCTRL6()) return false; if (!setAccelCTRL7()) return false; if (!setCompassCTRL1()) return false; if (!setCompassCTRL2()) return false; if (!setCompassCTRL3()) return false; gyroBiasInit(); HAL_INFO("LSM9DS1 init complete\n"); return true; } bool RTIMULSM9DS1::setGyroSampleRate() { unsigned char ctrl1; switch (m_settings->m_LSM9DS1GyroSampleRate) { case LSM9DS1_GYRO_SAMPLERATE_14_9: ctrl1 = 0x20; m_sampleRate = 15; break; case LSM9DS1_GYRO_SAMPLERATE_59_5: ctrl1 = 0x40; m_sampleRate = 60; break; case LSM9DS1_GYRO_SAMPLERATE_119: ctrl1 = 0x60; m_sampleRate = 119; break; case LSM9DS1_GYRO_SAMPLERATE_238: ctrl1 = 0x80; m_sampleRate = 238; break; case LSM9DS1_GYRO_SAMPLERATE_476: ctrl1 = 0xa0; m_sampleRate = 476; break; case LSM9DS1_GYRO_SAMPLERATE_952: ctrl1 = 0xc0; m_sampleRate = 952; break; default: HAL_ERROR1("Illegal LSM9DS1 gyro sample rate code %d\n", m_settings->m_LSM9DS1GyroSampleRate); return false; } m_sampleInterval = (uint64_t)1000000 / m_sampleRate; switch (m_settings->m_LSM9DS1GyroBW) { case LSM9DS1_GYRO_BANDWIDTH_0: ctrl1 |= 0x00; break; case LSM9DS1_GYRO_BANDWIDTH_1: ctrl1 |= 0x01; break; case LSM9DS1_GYRO_BANDWIDTH_2: ctrl1 |= 0x02; break; case LSM9DS1_GYRO_BANDWIDTH_3: ctrl1 |= 0x03; break; } switch (m_settings->m_LSM9DS1GyroFsr) { case LSM9DS1_GYRO_FSR_250: ctrl1 |= 0x00; m_gyroScale = (RTFLOAT)0.00875 * RTMATH_DEGREE_TO_RAD; break; case LSM9DS1_GYRO_FSR_500: ctrl1 |= 0x08; m_gyroScale = (RTFLOAT)0.0175 * RTMATH_DEGREE_TO_RAD; break; case LSM9DS1_GYRO_FSR_2000: ctrl1 |= 0x18; m_gyroScale = (RTFLOAT)0.07 * RTMATH_DEGREE_TO_RAD; break; default: HAL_ERROR1("Illegal LSM9DS1 gyro FSR code %d\n", m_settings->m_LSM9DS1GyroFsr); return false; } return (m_settings->HALWrite(m_accelGyroSlaveAddr, LSM9DS1_CTRL1, ctrl1, "Failed to set LSM9DS1 gyro CTRL1")); } bool RTIMULSM9DS1::setGyroCTRL3() { unsigned char ctrl3; if ((m_settings->m_LSM9DS1GyroHpf < LSM9DS1_GYRO_HPF_0) || (m_settings->m_LSM9DS1GyroHpf > LSM9DS1_GYRO_HPF_9)) { HAL_ERROR1("Illegal LSM9DS1 gyro high pass filter code %d\n", m_settings->m_LSM9DS1GyroHpf); return false; } ctrl3 = m_settings->m_LSM9DS1GyroHpf; // Turn on hpf ctrl3 |= 0x40; return m_settings->HALWrite(m_accelGyroSlaveAddr, LSM9DS1_CTRL3, ctrl3, "Failed to set LSM9DS1 gyro CTRL3"); } bool RTIMULSM9DS1::setAccelCTRL6() { unsigned char ctrl6; if ((m_settings->m_LSM9DS1AccelSampleRate < 0) || (m_settings->m_LSM9DS1AccelSampleRate > 6)) { HAL_ERROR1("Illegal LSM9DS1 accel sample rate code %d\n", m_settings->m_LSM9DS1AccelSampleRate); return false; } ctrl6 = (m_settings->m_LSM9DS1AccelSampleRate << 5); if ((m_settings->m_LSM9DS1AccelLpf < 0) || (m_settings->m_LSM9DS1AccelLpf > 3)) { HAL_ERROR1("Illegal LSM9DS1 accel low pass fiter code %d\n", m_settings->m_LSM9DS1AccelLpf); return false; } switch (m_settings->m_LSM9DS1AccelFsr) { case LSM9DS1_ACCEL_FSR_2: m_accelScale = (RTFLOAT)0.000061; break; case LSM9DS1_ACCEL_FSR_4: m_accelScale = (RTFLOAT)0.000122; break; case LSM9DS1_ACCEL_FSR_8: m_accelScale = (RTFLOAT)0.000244; break; case LSM9DS1_ACCEL_FSR_16: m_accelScale = (RTFLOAT)0.000732; break; default: HAL_ERROR1("Illegal LSM9DS1 accel FSR code %d\n", m_settings->m_LSM9DS1AccelFsr); return false; } ctrl6 |= (m_settings->m_LSM9DS1AccelLpf) | (m_settings->m_LSM9DS1AccelFsr << 3); return m_settings->HALWrite(m_accelGyroSlaveAddr, LSM9DS1_CTRL6, ctrl6, "Failed to set LSM9DS1 accel CTRL6"); } bool RTIMULSM9DS1::setAccelCTRL7() { unsigned char ctrl7; ctrl7 = 0x00; //Bug: Bad things happen. //ctrl7 = 0x05; return m_settings->HALWrite(m_accelGyroSlaveAddr, LSM9DS1_CTRL7, ctrl7, "Failed to set LSM9DS1 accel CTRL7"); } bool RTIMULSM9DS1::setCompassCTRL1() { unsigned char ctrl1; if ((m_settings->m_LSM9DS1CompassSampleRate < 0) || (m_settings->m_LSM9DS1CompassSampleRate > 5)) { HAL_ERROR1("Illegal LSM9DS1 compass sample rate code %d\n", m_settings->m_LSM9DS1CompassSampleRate); return false; } ctrl1 = (m_settings->m_LSM9DS1CompassSampleRate << 2); return m_settings->HALWrite(m_magSlaveAddr, LSM9DS1_MAG_CTRL1, ctrl1, "Failed to set LSM9DS1 compass CTRL5"); } bool RTIMULSM9DS1::setCompassCTRL2() { unsigned char ctrl2; // convert FSR to uT switch (m_settings->m_LSM9DS1CompassFsr) { case LSM9DS1_COMPASS_FSR_4: ctrl2 = 0; m_compassScale = (RTFLOAT)0.014; break; case LSM9DS1_COMPASS_FSR_8: ctrl2 = 0x20; m_compassScale = (RTFLOAT)0.029; break; case LSM9DS1_COMPASS_FSR_12: ctrl2 = 0x40; m_compassScale = (RTFLOAT)0.043; break; case LSM9DS1_COMPASS_FSR_16: ctrl2 = 0x60; m_compassScale = (RTFLOAT)0.058; break; default: HAL_ERROR1("Illegal LSM9DS1 compass FSR code %d\n", m_settings->m_LSM9DS1CompassFsr); return false; } return m_settings->HALWrite(m_magSlaveAddr, LSM9DS1_MAG_CTRL2, ctrl2, "Failed to set LSM9DS1 compass CTRL6"); } bool RTIMULSM9DS1::setCompassCTRL3() { return m_settings->HALWrite(m_magSlaveAddr, LSM9DS1_MAG_CTRL3, 0x00, "Failed to set LSM9DS1 compass CTRL3"); } int RTIMULSM9DS1::IMUGetPollInterval() { return (400 / m_sampleRate); } bool RTIMULSM9DS1::IMURead() { unsigned char status; unsigned char gyroData[6]; unsigned char accelData[6]; unsigned char compassData[6]; if (!m_settings->HALRead(m_accelGyroSlaveAddr, LSM9DS1_STATUS, 1, &status, "Failed to read LSM9DS1 status")) return false; if ((status & 0x3) == 0) return false; for (int i = 0; i<6; i++){ if (!m_settings->HALRead(m_accelGyroSlaveAddr, LSM9DS1_OUT_X_L_G + i, 1, &gyroData[i], "Failed to read LSM9DS1 gyro data")) return false; if (!m_settings->HALRead(m_accelGyroSlaveAddr, LSM9DS1_OUT_X_L_XL + i, 1, &accelData[i], "Failed to read LSM9DS1 accel data")) return false; if (!m_settings->HALRead(m_magSlaveAddr, LSM9DS1_MAG_OUT_X_L + i, 1, &compassData[i], "Failed to read LSM9DS1 compass data")) return false; } m_imuData.timestamp = RTMath::currentUSecsSinceEpoch(); RTMath::convertToVector(gyroData, m_imuData.gyro, m_gyroScale, false); RTMath::convertToVector(accelData, m_imuData.accel, m_accelScale, false); RTMath::convertToVector(compassData, m_imuData.compass, m_compassScale, false); // sort out gyro axes and correct for bias m_imuData.gyro.setZ(-m_imuData.gyro.z()); // sort out accel data; m_imuData.accel.setX(-m_imuData.accel.x()); m_imuData.accel.setY(-m_imuData.accel.y()); // sort out compass axes m_imuData.compass.setX(-m_imuData.compass.x()); m_imuData.compass.setZ(-m_imuData.compass.z()); // now do standard processing handleGyroBias(); calibrateAverageCompass(); calibrateAccel(); // now update the filter updateFusion(); return true; } rtimulib-7.2.1/RTIMULib/IMUDrivers/RTIMULSM9DS1.h000066400000000000000000000066271254201074400207300ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTIMULSM9DS1_H #define _RTIMULSM9DS1_H #include "RTIMU.h" // Define this symbol to use cache mode //#define LSM9DS1_CACHE_MODE // not reliable at the moment #ifdef LSM9DS1_CACHE_MODE // Cache defs #define LSM9DS1_FIFO_CHUNK_SIZE 6 // 6 bytes of gyro data #define LSM9DS1_FIFO_THRESH 16 // threshold point in fifo #define LSM9DS1_CACHE_BLOCK_COUNT 16 // number of cache blocks typedef struct { unsigned char data[LSM9DS1_FIFO_THRESH * LSM9DS1_FIFO_CHUNK_SIZE]; int count; // number of chunks in the cache block int index; // current index into the cache unsigned char accel[6]; // the raw accel readings for the block unsigned char compass[6]; // the raw compass readings for the block } LSM9DS1_CACHE_BLOCK; #endif class RTIMULSM9DS1 : public RTIMU { public: RTIMULSM9DS1(RTIMUSettings *settings); ~RTIMULSM9DS1(); virtual const char *IMUName() { return "LSM9DS1"; } virtual int IMUType() { return RTIMU_TYPE_LSM9DS1; } virtual bool IMUInit(); virtual int IMUGetPollInterval(); virtual bool IMURead(); private: bool setGyroSampleRate(); bool setGyroCTRL3(); bool setAccelCTRL6(); bool setAccelCTRL7(); bool setCompassCTRL1(); bool setCompassCTRL2(); bool setCompassCTRL3(); unsigned char m_accelGyroSlaveAddr; // I2C address of accel andgyro unsigned char m_magSlaveAddr; // I2C address of mag RTFLOAT m_gyroScale; RTFLOAT m_accelScale; RTFLOAT m_compassScale; #ifdef LSM9DS1_CACHE_MODE bool m_firstTime; // if first sample LSM9DS1_CACHE_BLOCK m_cache[LSM9DS1_CACHE_BLOCK_COUNT]; // the cache itself int m_cacheIn; // the in index int m_cacheOut; // the out index int m_cacheCount; // number of used cache blocks #endif }; #endif // _RTIMULSM9DS1_H rtimulib-7.2.1/RTIMULib/IMUDrivers/RTIMUMPU9150.cpp000066400000000000000000000446401254201074400212040ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTIMUMPU9150.h" #include "RTIMUSettings.h" RTIMUMPU9150::RTIMUMPU9150(RTIMUSettings *settings) : RTIMU(settings) { } RTIMUMPU9150::~RTIMUMPU9150() { } bool RTIMUMPU9150::setLpf(unsigned char lpf) { switch (lpf) { case MPU9150_LPF_256: case MPU9150_LPF_188: case MPU9150_LPF_98: case MPU9150_LPF_42: case MPU9150_LPF_20: case MPU9150_LPF_10: case MPU9150_LPF_5: m_lpf = lpf; return true; default: HAL_ERROR1("Illegal MPU9150 lpf %d\n", lpf); return false; } } bool RTIMUMPU9150::setSampleRate(int rate) { if ((rate < MPU9150_SAMPLERATE_MIN) || (rate > MPU9150_SAMPLERATE_MAX)) { HAL_ERROR1("Illegal sample rate %d\n", rate); return false; } m_sampleRate = rate; m_sampleInterval = (uint64_t)1000000 / m_sampleRate; return true; } bool RTIMUMPU9150::setCompassRate(int rate) { if ((rate < MPU9150_COMPASSRATE_MIN) || (rate > MPU9150_COMPASSRATE_MAX)) { HAL_ERROR1("Illegal compass rate %d\n", rate); return false; } m_compassRate = rate; return true; } bool RTIMUMPU9150::setGyroFsr(unsigned char fsr) { switch (fsr) { case MPU9150_GYROFSR_250: m_gyroFsr = fsr; m_gyroScale = RTMATH_PI / (131.0 * 180.0); return true; case MPU9150_GYROFSR_500: m_gyroFsr = fsr; m_gyroScale = RTMATH_PI / (62.5 * 180.0); return true; case MPU9150_GYROFSR_1000: m_gyroFsr = fsr; m_gyroScale = RTMATH_PI / (32.8 * 180.0); return true; case MPU9150_GYROFSR_2000: m_gyroFsr = fsr; m_gyroScale = RTMATH_PI / (16.4 * 180.0); return true; default: HAL_ERROR1("Illegal MPU9150 gyro fsr %d\n", fsr); return false; } } bool RTIMUMPU9150::setAccelFsr(unsigned char fsr) { switch (fsr) { case MPU9150_ACCELFSR_2: m_accelFsr = fsr; m_accelScale = 1.0/16384.0; return true; case MPU9150_ACCELFSR_4: m_accelFsr = fsr; m_accelScale = 1.0/8192.0; return true; case MPU9150_ACCELFSR_8: m_accelFsr = fsr; m_accelScale = 1.0/4096.0; return true; case MPU9150_ACCELFSR_16: m_accelFsr = fsr; m_accelScale = 1.0/2048.0; return true; default: HAL_ERROR1("Illegal MPU9150 accel fsr %d\n", fsr); return false; } } bool RTIMUMPU9150::IMUInit() { unsigned char result; m_firstTime = true; #ifdef MPU9150_CACHE_MODE m_cacheIn = m_cacheOut = m_cacheCount = 0; #endif // set validity flags m_imuData.fusionPoseValid = false; m_imuData.fusionQPoseValid = false; m_imuData.gyroValid = true; m_imuData.accelValid = true; m_imuData.compassValid = true; m_imuData.pressureValid = false; m_imuData.temperatureValid = false; m_imuData.humidityValid = false; // configure IMU m_slaveAddr = m_settings->m_I2CSlaveAddress; setSampleRate(m_settings->m_MPU9150GyroAccelSampleRate); setCompassRate(m_settings->m_MPU9150CompassSampleRate); setLpf(m_settings->m_MPU9150GyroAccelLpf); setGyroFsr(m_settings->m_MPU9150GyroFsr); setAccelFsr(m_settings->m_MPU9150AccelFsr); setCalibrationData(); // enable the I2C bus if (!m_settings->HALOpen()) return false; // reset the MPU9150 if (!m_settings->HALWrite(m_slaveAddr, MPU9150_PWR_MGMT_1, 0x80, "Failed to initiate MPU9150 reset")) return false; m_settings->delayMs(100); if (!m_settings->HALWrite(m_slaveAddr, MPU9150_PWR_MGMT_1, 0x00, "Failed to stop MPU9150 reset")) return false; if (!m_settings->HALRead(m_slaveAddr, MPU9150_WHO_AM_I, 1, &result, "Failed to read MPU9150 id")) return false; if (result != MPU9150_ID) { HAL_ERROR1("Incorrect MPU9150 id %d\n", result); return false; } // now configure the various components if (!m_settings->HALWrite(m_slaveAddr, MPU9150_LPF_CONFIG, m_lpf, "Failed to set lpf")) return false; if (!setSampleRate()) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9150_GYRO_CONFIG, m_gyroFsr, "Failed to set gyro fsr")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9150_ACCEL_CONFIG, m_accelFsr, "Failed to set accel fsr")) return false; // now configure compass if (!configureCompass()) return false; // enable the sensors if (!m_settings->HALWrite(m_slaveAddr, MPU9150_PWR_MGMT_1, 1, "Failed to set pwr_mgmt_1")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9150_PWR_MGMT_2, 0, "Failed to set pwr_mgmt_2")) return false; // select the data to go into the FIFO and enable if (!resetFifo()) return false; gyroBiasInit(); HAL_INFO("MPU9150 init complete\n"); return true; } bool RTIMUMPU9150::configureCompass() { unsigned char asa[3]; unsigned char id; m_compassIs5883 = false; m_compassDataLength = 8; bypassOn(); // get fuse ROM data if (!m_settings->HALWrite(AK8975_ADDRESS, AK8975_CNTL, 0, "Failed to set compass in power down mode 1")) { bypassOff(); return false; } if (!m_settings->HALWrite(AK8975_ADDRESS, AK8975_CNTL, 0x0f, "Failed to set compass in fuse ROM mode")) { bypassOff(); return false; } if (!m_settings->HALRead(AK8975_ADDRESS, AK8975_ASAX, 3, asa, "")) { // check to see if an HMC5883L is fitted if (!m_settings->HALRead(HMC5883_ADDRESS, HMC5883_ID, 1, &id, "Failed to find 5883")) { bypassOff(); // this is returning true so that MPU-6050 by itself will work HAL_INFO("Detected MPU-6050 without compass\n"); m_imuData.compassValid = false; return true; } if (id != 0x48) { // incorrect id for HMC5883L bypassOff(); // this is returning true so that MPU-6050 by itself will work HAL_INFO("Detected MPU-6050 without compass\n"); m_imuData.compassValid = false; return true; } // HMC5883 is present - use that if (!m_settings->HALWrite(HMC5883_ADDRESS, HMC5883_CONFIG_A, 0x38, "Failed to set HMC5883 config A")) { bypassOff(); return false; } if (!m_settings->HALWrite(HMC5883_ADDRESS, HMC5883_CONFIG_B, 0x20, "Failed to set HMC5883 config B")) { bypassOff(); return false; } if (!m_settings->HALWrite(HMC5883_ADDRESS, HMC5883_MODE, 0x00, "Failed to set HMC5883 mode")) { bypassOff(); return false; } HAL_INFO("Detected MPU-6050 with HMC5883\n"); m_compassDataLength = 6; m_compassIs5883 = true; } else { // convert asa to usable scale factor m_compassAdjust[0] = ((float)asa[0] - 128.0) / 256.0 + 1.0f; m_compassAdjust[1] = ((float)asa[1] - 128.0) / 256.0 + 1.0f; m_compassAdjust[2] = ((float)asa[2] - 128.0) / 256.0 + 1.0f; if (!m_settings->HALWrite(AK8975_ADDRESS, AK8975_CNTL, 0, "Failed to set compass in power down mode 2")) { bypassOff(); return false; } } bypassOff(); // now set up MPU9150 to talk to the compass chip if (!m_settings->HALWrite(m_slaveAddr, MPU9150_I2C_MST_CTRL, 0x40, "Failed to set I2C master mode")) return false; if (m_compassIs5883) { if (!m_settings->HALWrite(m_slaveAddr, MPU9150_I2C_SLV0_ADDR, 0x80 | HMC5883_ADDRESS, "Failed to set slave 0 address")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9150_I2C_SLV0_REG, HMC5883_DATA_X_HI, "Failed to set slave 0 reg")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9150_I2C_SLV0_CTRL, 0x86, "Failed to set slave 0 ctrl")) return false; } else { if (!m_settings->HALWrite(m_slaveAddr, MPU9150_I2C_SLV0_ADDR, 0x80 | AK8975_ADDRESS, "Failed to set slave 0 address")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9150_I2C_SLV0_REG, AK8975_ST1, "Failed to set slave 0 reg")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9150_I2C_SLV0_CTRL, 0x88, "Failed to set slave 0 ctrl")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9150_I2C_SLV1_ADDR, AK8975_ADDRESS, "Failed to set slave 1 address")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9150_I2C_SLV1_REG, AK8975_CNTL, "Failed to set slave 1 reg")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9150_I2C_SLV1_CTRL, 0x81, "Failed to set slave 1 ctrl")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9150_I2C_SLV1_DO, 0x1, "Failed to set slave 1 DO")) return false; } if (!m_settings->HALWrite(m_slaveAddr, MPU9150_I2C_MST_DELAY_CTRL, 0x3, "Failed to set mst delay")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9150_YG_OFFS_TC, 0x80, "Failed to set yg offs tc")) return false; if (!setCompassRate()) return false; return true; } bool RTIMUMPU9150::resetFifo() { if (!m_settings->HALWrite(m_slaveAddr, MPU9150_INT_ENABLE, 0, "Writing int enable")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9150_FIFO_EN, 0, "Writing fifo enable")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9150_USER_CTRL, 0, "Writing user control")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9150_USER_CTRL, 0x04, "Resetting fifo")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9150_USER_CTRL, 0x60, "Enabling the fifo")) return false; m_settings->delayMs(50); if (!m_settings->HALWrite(m_slaveAddr, MPU9150_INT_ENABLE, 1, "Writing int enable")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9150_FIFO_EN, 0x78, "Failed to set FIFO enables")) return false; return true; } bool RTIMUMPU9150::bypassOn() { unsigned char userControl; if (!m_settings->HALRead(m_slaveAddr, MPU9150_USER_CTRL, 1, &userControl, "Failed to read user_ctrl reg")) return false; userControl &= ~0x20; if (!m_settings->HALWrite(m_slaveAddr, MPU9150_USER_CTRL, 1, &userControl, "Failed to write user_ctrl reg")) return false; m_settings->delayMs(50); if (!m_settings->HALWrite(m_slaveAddr, MPU9150_INT_PIN_CFG, 0x82, "Failed to write int_pin_cfg reg")) return false; m_settings->delayMs(50); return true; } bool RTIMUMPU9150::bypassOff() { unsigned char userControl; if (!m_settings->HALRead(m_slaveAddr, MPU9150_USER_CTRL, 1, &userControl, "Failed to read user_ctrl reg")) return false; userControl |= 0x20; if (!m_settings->HALWrite(m_slaveAddr, MPU9150_USER_CTRL, 1, &userControl, "Failed to write user_ctrl reg")) return false; m_settings->delayMs(50); if (!m_settings->HALWrite(m_slaveAddr, MPU9150_INT_PIN_CFG, 0x80, "Failed to write int_pin_cfg reg")) return false; m_settings->delayMs(50); return true; } bool RTIMUMPU9150::setSampleRate() { int clockRate = 1000; if (m_lpf == MPU9150_LPF_256) clockRate = 8000; if (!m_settings->HALWrite(m_slaveAddr, MPU9150_SMPRT_DIV, (unsigned char)(clockRate / m_sampleRate - 1), "Failed to set sample rate")) return false; return true; } bool RTIMUMPU9150::setCompassRate() { int rate; rate = m_sampleRate / m_compassRate - 1; if (rate > 31) rate = 31; if (!m_settings->HALWrite(m_slaveAddr, MPU9150_I2C_SLV4_CTRL, rate, "Failed to set slave ctrl 4")) return false; return true; } int RTIMUMPU9150::IMUGetPollInterval() { return (400 / m_sampleRate); } bool RTIMUMPU9150::IMURead() { unsigned char fifoCount[2]; unsigned int count; unsigned char fifoData[12]; unsigned char compassData[8]; if (!m_settings->HALRead(m_slaveAddr, MPU9150_FIFO_COUNT_H, 2, fifoCount, "Failed to read fifo count")) return false; count = ((unsigned int)fifoCount[0] << 8) + fifoCount[1]; if (count == 1024) { HAL_INFO("MPU9150 fifo has overflowed"); resetFifo(); m_imuData.timestamp += m_sampleInterval * (1024 / MPU9150_FIFO_CHUNK_SIZE + 1); // try to fix timestamp return false; } #ifdef MPU9150_CACHE_MODE if ((m_cacheCount == 0) && (count >= MPU9150_FIFO_CHUNK_SIZE) && (count < (MPU9150_CACHE_SIZE * MPU9150_FIFO_CHUNK_SIZE))) { // special case of a small fifo and nothing cached - just handle as simple read if (!m_settings->HALRead(m_slaveAddr, MPU9150_FIFO_R_W, MPU9150_FIFO_CHUNK_SIZE, fifoData, "Failed to read fifo data")) return false; if (!m_settings->HALRead(m_slaveAddr, MPU9150_EXT_SENS_DATA_00, m_compassDataLength, compassData, "Failed to read compass data")) return false; } else { if (count >= (MPU9150_CACHE_SIZE * MPU9150_FIFO_CHUNK_SIZE)) { if (m_cacheCount == MPU9150_CACHE_BLOCK_COUNT) { // all cache blocks are full - discard oldest and update timestamp to account for lost samples m_imuData.timestamp += m_sampleInterval * m_cache[m_cacheOut].count; if (++m_cacheOut == MPU9150_CACHE_BLOCK_COUNT) m_cacheOut = 0; m_cacheCount--; } int blockCount = count / MPU9150_FIFO_CHUNK_SIZE; // number of chunks in fifo if (blockCount > MPU9150_CACHE_SIZE) blockCount = MPU9150_CACHE_SIZE; if (!m_settings->HALRead(m_slaveAddr, MPU9150_FIFO_R_W, MPU9150_FIFO_CHUNK_SIZE * blockCount, m_cache[m_cacheIn].data, "Failed to read fifo data")) return false; if (!m_settings->HALRead(m_slaveAddr, MPU9150_EXT_SENS_DATA_00, 8, m_cache[m_cacheIn].compass, "Failed to read compass data")) return false; m_cache[m_cacheIn].count = blockCount; m_cache[m_cacheIn].index = 0; m_cacheCount++; if (++m_cacheIn == MPU9150_CACHE_BLOCK_COUNT) m_cacheIn = 0; } // now fifo has been read if necessary, get something to process if (m_cacheCount == 0) return false; memcpy(fifoData, m_cache[m_cacheOut].data + m_cache[m_cacheOut].index, MPU9150_FIFO_CHUNK_SIZE); memcpy(compassData, m_cache[m_cacheOut].compass, 8); m_cache[m_cacheOut].index += MPU9150_FIFO_CHUNK_SIZE; if (--m_cache[m_cacheOut].count == 0) { // this cache block is now empty if (++m_cacheOut == MPU9150_CACHE_BLOCK_COUNT) m_cacheOut = 0; m_cacheCount--; } } #else if (count > MPU9150_FIFO_CHUNK_SIZE * 40) { // more than 40 samples behind - going too slowly so discard some samples but maintain timestamp correctly while (count >= MPU9150_FIFO_CHUNK_SIZE * 10) { if (!m_settings->HALRead(m_slaveAddr, MPU9150_FIFO_R_W, MPU9150_FIFO_CHUNK_SIZE, fifoData, "Failed to read fifo data")) return false; count -= MPU9150_FIFO_CHUNK_SIZE; m_imuData.timestamp += m_sampleInterval; } } if (count < MPU9150_FIFO_CHUNK_SIZE) return false; if (!m_settings->HALRead(m_slaveAddr, MPU9150_FIFO_R_W, MPU9150_FIFO_CHUNK_SIZE, fifoData, "Failed to read fifo data")) return false; if (!m_settings->HALRead(m_slaveAddr, MPU9150_EXT_SENS_DATA_00, m_compassDataLength, compassData, "Failed to read compass data")) return false; #endif RTMath::convertToVector(fifoData, m_imuData.accel, m_accelScale, true); RTMath::convertToVector(fifoData + 6, m_imuData.gyro, m_gyroScale, true); if (m_compassIs5883) RTMath::convertToVector(compassData, m_imuData.compass, 0.092f, true); else RTMath::convertToVector(compassData + 1, m_imuData.compass, 0.3f, false); // sort out gyro axes m_imuData.gyro.setX(m_imuData.gyro.x()); m_imuData.gyro.setY(-m_imuData.gyro.y()); m_imuData.gyro.setZ(-m_imuData.gyro.z()); // sort out accel data; m_imuData.accel.setX(-m_imuData.accel.x()); if (m_compassIs5883) { // sort out compass axes float temp; temp = m_imuData.compass.y(); m_imuData.compass.setY(-m_imuData.compass.z()); m_imuData.compass.setZ(-temp); } else { // use the compass fuse data adjustments m_imuData.compass.setX(m_imuData.compass.x() * m_compassAdjust[0]); m_imuData.compass.setY(m_imuData.compass.y() * m_compassAdjust[1]); m_imuData.compass.setZ(m_imuData.compass.z() * m_compassAdjust[2]); // sort out compass axes float temp; temp = m_imuData.compass.x(); m_imuData.compass.setX(m_imuData.compass.y()); m_imuData.compass.setY(-temp); } // now do standard processing handleGyroBias(); calibrateAverageCompass(); calibrateAccel(); if (m_firstTime) m_imuData.timestamp = RTMath::currentUSecsSinceEpoch(); else m_imuData.timestamp += m_sampleInterval; m_firstTime = false; // now update the filter updateFusion(); return true; } rtimulib-7.2.1/RTIMULib/IMUDrivers/RTIMUMPU9150.h000066400000000000000000000100451254201074400206410ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTIMUMPU9150_H #define _RTIMUMPU9150_H #include "RTIMU.h" // Define this symbol to use cache mode #define MPU9150_CACHE_MODE // FIFO transfer size #define MPU9150_FIFO_CHUNK_SIZE 12 // gyro and accels take 12 bytes #ifdef MPU9150_CACHE_MODE // Cache mode defines #define MPU9150_CACHE_SIZE 16 // number of chunks in a block #define MPU9150_CACHE_BLOCK_COUNT 16 // number of cache blocks typedef struct { unsigned char data[MPU9150_FIFO_CHUNK_SIZE * MPU9150_CACHE_SIZE]; int count; // number of chunks in the cache block int index; // current index into the cache unsigned char compass[8]; // the raw compass readings for the block } MPU9150_CACHE_BLOCK; #endif class RTIMUMPU9150 : public RTIMU { public: RTIMUMPU9150(RTIMUSettings *settings); ~RTIMUMPU9150(); bool setLpf(unsigned char lpf); bool setSampleRate(int rate); bool setCompassRate(int rate); bool setGyroFsr(unsigned char fsr); bool setAccelFsr(unsigned char fsr); virtual const char *IMUName() { return "MPU-9150"; } virtual int IMUType() { return RTIMU_TYPE_MPU9150; } virtual bool IMUInit(); virtual bool IMURead(); virtual int IMUGetPollInterval(); private: bool configureCompass(); // configures the compass bool bypassOn(); // talk to compass bool bypassOff(); // talk to MPU9150 bool setSampleRate(); bool setCompassRate(); bool resetFifo(); bool m_firstTime; // if first sample unsigned char m_slaveAddr; // I2C address of MPU9150 unsigned char m_lpf; // low pass filter setting int m_compassRate; // compass sample rate in Hz unsigned char m_gyroFsr; unsigned char m_accelFsr; RTFLOAT m_gyroScale; RTFLOAT m_accelScale; bool m_compassIs5883; // if it is an MPU-6050/HMC5883 combo int m_compassDataLength; // 8 for MPU-9150, 6 for HMC5883 RTFLOAT m_compassAdjust[3]; // the compass fuse ROM values converted for use #ifdef MPU9150_CACHE_MODE MPU9150_CACHE_BLOCK m_cache[MPU9150_CACHE_BLOCK_COUNT]; // the cache itself int m_cacheIn; // the in index int m_cacheOut; // the out index int m_cacheCount; // number of used cache blocks #endif }; #endif // _RTIMUMPU9150_H rtimulib-7.2.1/RTIMULib/IMUDrivers/RTIMUMPU9250.cpp000066400000000000000000000463061254201074400212060ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. // The MPU-9250 and SPI driver code is based on code generously supplied by // staslock@gmail.com (www.clickdrive.io) #include "RTIMUMPU9250.h" #include "RTIMUSettings.h" RTIMUMPU9250::RTIMUMPU9250(RTIMUSettings *settings) : RTIMU(settings) { } RTIMUMPU9250::~RTIMUMPU9250() { } bool RTIMUMPU9250::setSampleRate(int rate) { if ((rate < MPU9250_SAMPLERATE_MIN) || (rate > MPU9250_SAMPLERATE_MAX)) { HAL_ERROR1("Illegal sample rate %d\n", rate); return false; } // Note: rates interact with the lpf settings if ((rate < MPU9250_SAMPLERATE_MAX) && (rate >= 8000)) rate = 8000; if ((rate < 8000) && (rate >= 1000)) rate = 1000; if (rate < 1000) { int sampleDiv = (1000 / rate) - 1; m_sampleRate = 1000 / (1 + sampleDiv); } else { m_sampleRate = rate; } m_sampleInterval = (uint64_t)1000000 / m_sampleRate; return true; } bool RTIMUMPU9250::setGyroLpf(unsigned char lpf) { switch (lpf) { case MPU9250_GYRO_LPF_8800: case MPU9250_GYRO_LPF_3600: case MPU9250_GYRO_LPF_250: case MPU9250_GYRO_LPF_184: case MPU9250_GYRO_LPF_92: case MPU9250_GYRO_LPF_41: case MPU9250_GYRO_LPF_20: case MPU9250_GYRO_LPF_10: case MPU9250_GYRO_LPF_5: m_gyroLpf = lpf; return true; default: HAL_ERROR1("Illegal MPU9250 gyro lpf %d\n", lpf); return false; } } bool RTIMUMPU9250::setAccelLpf(unsigned char lpf) { switch (lpf) { case MPU9250_ACCEL_LPF_1130: case MPU9250_ACCEL_LPF_460: case MPU9250_ACCEL_LPF_184: case MPU9250_ACCEL_LPF_92: case MPU9250_ACCEL_LPF_41: case MPU9250_ACCEL_LPF_20: case MPU9250_ACCEL_LPF_10: case MPU9250_ACCEL_LPF_5: m_accelLpf = lpf; return true; default: HAL_ERROR1("Illegal MPU9250 accel lpf %d\n", lpf); return false; } } bool RTIMUMPU9250::setCompassRate(int rate) { if ((rate < MPU9250_COMPASSRATE_MIN) || (rate > MPU9250_COMPASSRATE_MAX)) { HAL_ERROR1("Illegal compass rate %d\n", rate); return false; } m_compassRate = rate; return true; } bool RTIMUMPU9250::setGyroFsr(unsigned char fsr) { switch (fsr) { case MPU9250_GYROFSR_250: m_gyroFsr = fsr; m_gyroScale = RTMATH_PI / (131.0 * 180.0); return true; case MPU9250_GYROFSR_500: m_gyroFsr = fsr; m_gyroScale = RTMATH_PI / (62.5 * 180.0); return true; case MPU9250_GYROFSR_1000: m_gyroFsr = fsr; m_gyroScale = RTMATH_PI / (32.8 * 180.0); return true; case MPU9250_GYROFSR_2000: m_gyroFsr = fsr; m_gyroScale = RTMATH_PI / (16.4 * 180.0); return true; default: HAL_ERROR1("Illegal MPU9250 gyro fsr %d\n", fsr); return false; } } bool RTIMUMPU9250::setAccelFsr(unsigned char fsr) { switch (fsr) { case MPU9250_ACCELFSR_2: m_accelFsr = fsr; m_accelScale = 1.0/16384.0; return true; case MPU9250_ACCELFSR_4: m_accelFsr = fsr; m_accelScale = 1.0/8192.0; return true; case MPU9250_ACCELFSR_8: m_accelFsr = fsr; m_accelScale = 1.0/4096.0; return true; case MPU9250_ACCELFSR_16: m_accelFsr = fsr; m_accelScale = 1.0/2048.0; return true; default: HAL_ERROR1("Illegal MPU9250 accel fsr %d\n", fsr); return false; } } bool RTIMUMPU9250::IMUInit() { unsigned char result; m_firstTime = true; #ifdef MPU9250_CACHE_MODE m_cacheIn = m_cacheOut = m_cacheCount = 0; #endif // set validity flags m_imuData.fusionPoseValid = false; m_imuData.fusionQPoseValid = false; m_imuData.gyroValid = true; m_imuData.accelValid = true; m_imuData.compassValid = true; m_imuData.pressureValid = false; m_imuData.temperatureValid = false; m_imuData.humidityValid = false; // configure IMU m_slaveAddr = m_settings->m_I2CSlaveAddress; setSampleRate(m_settings->m_MPU9250GyroAccelSampleRate); setCompassRate(m_settings->m_MPU9250CompassSampleRate); setGyroLpf(m_settings->m_MPU9250GyroLpf); setAccelLpf(m_settings->m_MPU9250AccelLpf); setGyroFsr(m_settings->m_MPU9250GyroFsr); setAccelFsr(m_settings->m_MPU9250AccelFsr); setCalibrationData(); // enable the bus if (!m_settings->HALOpen()) return false; // reset the MPU9250 if (!m_settings->HALWrite(m_slaveAddr, MPU9250_PWR_MGMT_1, 0x80, "Failed to initiate MPU9250 reset")) return false; m_settings->delayMs(100); if (!m_settings->HALWrite(m_slaveAddr, MPU9250_PWR_MGMT_1, 0x00, "Failed to stop MPU9250 reset")) return false; if (!m_settings->HALRead(m_slaveAddr, MPU9250_WHO_AM_I, 1, &result, "Failed to read MPU9250 id")) return false; if (result != MPU9250_ID) { HAL_ERROR2("Incorrect %s id %d\n", IMUName(), result); return false; } // now configure the various components if (!setGyroConfig()) return false; if (!setAccelConfig()) return false; if (!setSampleRate()) return false; if(!compassSetup()) { return false; } if (!setCompassRate()) return false; // enable the sensors if (!m_settings->HALWrite(m_slaveAddr, MPU9250_PWR_MGMT_1, 1, "Failed to set pwr_mgmt_1")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_PWR_MGMT_2, 0, "Failed to set pwr_mgmt_2")) return false; // select the data to go into the FIFO and enable if (!resetFifo()) return false; gyroBiasInit(); HAL_INFO1("%s init complete\n", IMUName()); return true; } bool RTIMUMPU9250::resetFifo() { if (!m_settings->HALWrite(m_slaveAddr, MPU9250_INT_ENABLE, 0, "Writing int enable")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_FIFO_EN, 0, "Writing fifo enable")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_USER_CTRL, 0, "Writing user control")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_USER_CTRL, 0x04, "Resetting fifo")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_USER_CTRL, 0x60, "Enabling the fifo")) return false; m_settings->delayMs(50); if (!m_settings->HALWrite(m_slaveAddr, MPU9250_INT_ENABLE, 1, "Writing int enable")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_FIFO_EN, 0x78, "Failed to set FIFO enables")) return false; return true; } bool RTIMUMPU9250::setGyroConfig() { unsigned char gyroConfig = m_gyroFsr + ((m_gyroLpf >> 3) & 3); unsigned char gyroLpf = m_gyroLpf & 7; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_GYRO_CONFIG, gyroConfig, "Failed to write gyro config")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_GYRO_LPF, gyroLpf, "Failed to write gyro lpf")) return false; return true; } bool RTIMUMPU9250::setAccelConfig() { if (!m_settings->HALWrite(m_slaveAddr, MPU9250_ACCEL_CONFIG, m_accelFsr, "Failed to write accel config")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_ACCEL_LPF, m_accelLpf, "Failed to write accel lpf")) return false; return true; } bool RTIMUMPU9250::setSampleRate() { if (m_sampleRate > 1000) return true; // SMPRT not used above 1000Hz if (!m_settings->HALWrite(m_slaveAddr, MPU9250_SMPRT_DIV, (unsigned char) (1000 / m_sampleRate - 1), "Failed to set sample rate")) return false; return true; } bool RTIMUMPU9250::compassSetup() { unsigned char asa[3]; if (m_settings->m_busIsI2C) { // I2C mode bypassOn(); // get fuse ROM data if (!m_settings->HALWrite(AK8963_ADDRESS, AK8963_CNTL, 0, "Failed to set compass in power down mode 1")) { bypassOff(); return false; } if (!m_settings->HALWrite(AK8963_ADDRESS, AK8963_CNTL, 0x0f, "Failed to set compass in fuse ROM mode")) { bypassOff(); return false; } if (!m_settings->HALRead(AK8963_ADDRESS, AK8963_ASAX, 3, asa, "Failed to read compass fuse ROM")) { bypassOff(); return false; } if (!m_settings->HALWrite(AK8963_ADDRESS, AK8963_CNTL, 0, "Failed to set compass in power down mode 2")) { bypassOff(); return false; } bypassOff(); } else { // SPI mode bypassOff(); if (!m_settings->HALWrite(m_slaveAddr, MPU9250_I2C_MST_CTRL, 0x40, "Failed to set I2C master mode")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_I2C_SLV0_ADDR, 0x80 | AK8963_ADDRESS, "Failed to set slave 0 address")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_I2C_SLV0_REG, AK8963_ASAX, "Failed to set slave 0 reg")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_I2C_SLV0_CTRL, 0x83, "Failed to set slave 0 ctrl")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_I2C_SLV1_ADDR, AK8963_ADDRESS, "Failed to set slave 1 address")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_I2C_SLV1_REG, AK8963_CNTL, "Failed to set slave 1 reg")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_I2C_SLV1_CTRL, 0x81, "Failed to set slave 1 ctrl")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_I2C_SLV1_DO, 0x00, "Failed to set compass in power down mode 2")) return false; m_settings->delayMs(10); if (!m_settings->HALWrite(m_slaveAddr, MPU9250_I2C_SLV1_DO, 0x0f, "Failed to set compass in fuse mode")) return false; if (!m_settings->HALRead(m_slaveAddr, MPU9250_EXT_SENS_DATA_00, 3, asa, "Failed to read compass rom")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_I2C_SLV1_DO, 0x0, "Failed to set compass in power down mode 2")) return false; } // both interfaces // convert asa to usable scale factor m_compassAdjust[0] = ((float)asa[0] - 128.0) / 256.0 + 1.0f; m_compassAdjust[1] = ((float)asa[1] - 128.0) / 256.0 + 1.0f; m_compassAdjust[2] = ((float)asa[2] - 128.0) / 256.0 + 1.0f; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_I2C_MST_CTRL, 0x40, "Failed to set I2C master mode")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_I2C_SLV0_ADDR, 0x80 | AK8963_ADDRESS, "Failed to set slave 0 address")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_I2C_SLV0_REG, AK8963_ST1, "Failed to set slave 0 reg")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_I2C_SLV0_CTRL, 0x88, "Failed to set slave 0 ctrl")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_I2C_SLV1_ADDR, AK8963_ADDRESS, "Failed to set slave 1 address")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_I2C_SLV1_REG, AK8963_CNTL, "Failed to set slave 1 reg")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_I2C_SLV1_CTRL, 0x81, "Failed to set slave 1 ctrl")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_I2C_SLV1_DO, 0x1, "Failed to set slave 1 DO")) return false; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_I2C_MST_DELAY_CTRL, 0x3, "Failed to set mst delay")) return false; return true; } bool RTIMUMPU9250::setCompassRate() { int rate; rate = m_sampleRate / m_compassRate - 1; if (rate > 31) rate = 31; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_I2C_SLV4_CTRL, rate, "Failed to set slave ctrl 4")) return false; return true; } bool RTIMUMPU9250::bypassOn() { unsigned char userControl; if (!m_settings->HALRead(m_slaveAddr, MPU9250_USER_CTRL, 1, &userControl, "Failed to read user_ctrl reg")) return false; userControl &= ~0x20; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_USER_CTRL, 1, &userControl, "Failed to write user_ctrl reg")) return false; m_settings->delayMs(50); if (!m_settings->HALWrite(m_slaveAddr, MPU9250_INT_PIN_CFG, 0x82, "Failed to write int_pin_cfg reg")) return false; m_settings->delayMs(50); return true; } bool RTIMUMPU9250::bypassOff() { unsigned char userControl; if (!m_settings->HALRead(m_slaveAddr, MPU9250_USER_CTRL, 1, &userControl, "Failed to read user_ctrl reg")) return false; userControl |= 0x20; if (!m_settings->HALWrite(m_slaveAddr, MPU9250_USER_CTRL, 1, &userControl, "Failed to write user_ctrl reg")) return false; m_settings->delayMs(50); if (!m_settings->HALWrite(m_slaveAddr, MPU9250_INT_PIN_CFG, 0x80, "Failed to write int_pin_cfg reg")) return false; m_settings->delayMs(50); return true; } int RTIMUMPU9250::IMUGetPollInterval() { if (m_sampleRate > 400) return 1; else return (400 / m_sampleRate); } bool RTIMUMPU9250::IMURead() { unsigned char fifoCount[2]; unsigned int count; unsigned char fifoData[12]; unsigned char compassData[8]; if (!m_settings->HALRead(m_slaveAddr, MPU9250_FIFO_COUNT_H, 2, fifoCount, "Failed to read fifo count")) return false; count = ((unsigned int)fifoCount[0] << 8) + fifoCount[1]; if (count == 512) { HAL_INFO("MPU-9250 fifo has overflowed"); resetFifo(); m_imuData.timestamp += m_sampleInterval * (512 / MPU9250_FIFO_CHUNK_SIZE + 1); // try to fix timestamp return false; } #ifdef MPU9250_CACHE_MODE if ((m_cacheCount == 0) && (count >= MPU9250_FIFO_CHUNK_SIZE) && (count < (MPU9250_CACHE_SIZE * MPU9250_FIFO_CHUNK_SIZE))) { // special case of a small fifo and nothing cached - just handle as simple read if (!m_settings->HALRead(m_slaveAddr, MPU9250_FIFO_R_W, MPU9250_FIFO_CHUNK_SIZE, fifoData, "Failed to read fifo data")) return false; if (!m_settings->HALRead(m_slaveAddr, MPU9250_EXT_SENS_DATA_00, 8, compassData, "Failed to read compass data")) return false; } else { if (count >= (MPU9250_CACHE_SIZE * MPU9250_FIFO_CHUNK_SIZE)) { if (m_cacheCount == MPU9250_CACHE_BLOCK_COUNT) { // all cache blocks are full - discard oldest and update timestamp to account for lost samples m_imuData.timestamp += m_sampleInterval * m_cache[m_cacheOut].count; if (++m_cacheOut == MPU9250_CACHE_BLOCK_COUNT) m_cacheOut = 0; m_cacheCount--; } int blockCount = count / MPU9250_FIFO_CHUNK_SIZE; // number of chunks in fifo if (blockCount > MPU9250_CACHE_SIZE) blockCount = MPU9250_CACHE_SIZE; if (!m_settings->HALRead(m_slaveAddr, MPU9250_FIFO_R_W, MPU9250_FIFO_CHUNK_SIZE * blockCount, m_cache[m_cacheIn].data, "Failed to read fifo data")) return false; if (!m_settings->HALRead(m_slaveAddr, MPU9250_EXT_SENS_DATA_00, 8, m_cache[m_cacheIn].compass, "Failed to read compass data")) return false; m_cache[m_cacheIn].count = blockCount; m_cache[m_cacheIn].index = 0; m_cacheCount++; if (++m_cacheIn == MPU9250_CACHE_BLOCK_COUNT) m_cacheIn = 0; } // now fifo has been read if necessary, get something to process if (m_cacheCount == 0) return false; memcpy(fifoData, m_cache[m_cacheOut].data + m_cache[m_cacheOut].index, MPU9250_FIFO_CHUNK_SIZE); memcpy(compassData, m_cache[m_cacheOut].compass, 8); m_cache[m_cacheOut].index += MPU9250_FIFO_CHUNK_SIZE; if (--m_cache[m_cacheOut].count == 0) { // this cache block is now empty if (++m_cacheOut == MPU9250_CACHE_BLOCK_COUNT) m_cacheOut = 0; m_cacheCount--; } } #else if (count > MPU9250_FIFO_CHUNK_SIZE * 40) { // more than 40 samples behind - going too slowly so discard some samples but maintain timestamp correctly while (count >= MPU9250_FIFO_CHUNK_SIZE * 10) { if (!m_settings->HALRead(m_slaveAddr, MPU9250_FIFO_R_W, MPU9250_FIFO_CHUNK_SIZE, fifoData, "Failed to read fifo data")) return false; count -= MPU9250_FIFO_CHUNK_SIZE; m_imuData.timestamp += m_sampleInterval; } } if (count < MPU9250_FIFO_CHUNK_SIZE) return false; if (!m_settings->HALRead(m_slaveAddr, MPU9250_FIFO_R_W, MPU9250_FIFO_CHUNK_SIZE, fifoData, "Failed to read fifo data")) return false; if (!m_settings->HALRead(m_slaveAddr, MPU9250_EXT_SENS_DATA_00, 8, compassData, "Failed to read compass data")) return false; #endif RTMath::convertToVector(fifoData, m_imuData.accel, m_accelScale, true); RTMath::convertToVector(fifoData + 6, m_imuData.gyro, m_gyroScale, true); RTMath::convertToVector(compassData + 1, m_imuData.compass, 0.6f, false); // sort out gyro axes m_imuData.gyro.setX(m_imuData.gyro.x()); m_imuData.gyro.setY(-m_imuData.gyro.y()); m_imuData.gyro.setZ(-m_imuData.gyro.z()); // sort out accel data; m_imuData.accel.setX(-m_imuData.accel.x()); // use the compass fuse data adjustments m_imuData.compass.setX(m_imuData.compass.x() * m_compassAdjust[0]); m_imuData.compass.setY(m_imuData.compass.y() * m_compassAdjust[1]); m_imuData.compass.setZ(m_imuData.compass.z() * m_compassAdjust[2]); // sort out compass axes float temp; temp = m_imuData.compass.x(); m_imuData.compass.setX(m_imuData.compass.y()); m_imuData.compass.setY(-temp); // now do standard processing handleGyroBias(); calibrateAverageCompass(); calibrateAccel(); if (m_firstTime) m_imuData.timestamp = RTMath::currentUSecsSinceEpoch(); else m_imuData.timestamp += m_sampleInterval; m_firstTime = false; // now update the filter updateFusion(); return true; } rtimulib-7.2.1/RTIMULib/IMUDrivers/RTIMUMPU9250.h000066400000000000000000000100001254201074400206310ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. // The MPU-9250 and SPI driver code is based on code generously supplied by // staslock@gmail.com (www.clickdrive.io) #ifndef _RTIMUMPU9250_H #define _RTIMUMPU9250_H #include "RTIMU.h" // Define this symbol to use cache mode #define MPU9250_CACHE_MODE // FIFO transfer size #define MPU9250_FIFO_CHUNK_SIZE 12 // gyro and accels take 12 bytes #ifdef MPU9250_CACHE_MODE // Cache mode defines #define MPU9250_CACHE_SIZE 16 // number of chunks in a block #define MPU9250_CACHE_BLOCK_COUNT 16 // number of cache blocks typedef struct { unsigned char data[MPU9250_FIFO_CHUNK_SIZE * MPU9250_CACHE_SIZE]; int count; // number of chunks in the cache block int index; // current index into the cache unsigned char compass[8]; // the raw compass readings for the block } MPU9250_CACHE_BLOCK; #endif class RTIMUMPU9250 : public RTIMU { public: RTIMUMPU9250(RTIMUSettings *settings); ~RTIMUMPU9250(); bool setGyroLpf(unsigned char lpf); bool setAccelLpf(unsigned char lpf); bool setSampleRate(int rate); bool setCompassRate(int rate); bool setGyroFsr(unsigned char fsr); bool setAccelFsr(unsigned char fsr); virtual const char *IMUName() { return "MPU-9250"; } virtual int IMUType() { return RTIMU_TYPE_MPU9250; } virtual bool IMUInit(); virtual bool IMURead(); virtual int IMUGetPollInterval(); protected: RTFLOAT m_compassAdjust[3]; // the compass fuse ROM values converted for use private: bool setGyroConfig(); bool setAccelConfig(); bool setSampleRate(); bool compassSetup(); bool setCompassRate(); bool resetFifo(); bool bypassOn(); bool bypassOff(); bool m_firstTime; // if first sample unsigned char m_slaveAddr; // I2C address of MPU9150 unsigned char m_gyroLpf; // gyro low pass filter setting unsigned char m_accelLpf; // accel low pass filter setting int m_compassRate; // compass sample rate in Hz unsigned char m_gyroFsr; unsigned char m_accelFsr; RTFLOAT m_gyroScale; RTFLOAT m_accelScale; #ifdef MPU9250_CACHE_MODE MPU9250_CACHE_BLOCK m_cache[MPU9250_CACHE_BLOCK_COUNT]; // the cache itself int m_cacheIn; // the in index int m_cacheOut; // the out index int m_cacheCount; // number of used cache blocks #endif }; #endif // _RTIMUMPU9250_H rtimulib-7.2.1/RTIMULib/IMUDrivers/RTIMUNull.cpp000066400000000000000000000032741254201074400211340ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTIMUNull.h" #include "RTIMUSettings.h" RTIMUNull::RTIMUNull(RTIMUSettings *settings) : RTIMU(settings) { } RTIMUNull::~RTIMUNull() { } bool RTIMUNull::IMUInit() { return true; } int RTIMUNull::IMUGetPollInterval() { return (100); // just a dummy value really } bool RTIMUNull::IMURead() { updateFusion(); return true; } void RTIMUNull::setIMUData(const RTIMU_DATA& data) { m_imuData = data; } rtimulib-7.2.1/RTIMULib/IMUDrivers/RTIMUNull.h000066400000000000000000000041231254201074400205730ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTIMUNULL_H #define _RTIMUNULL_H // IMUNull is a dummy IMU that assumes sensor data is coming from elsewhere, // for example, across a network. // // Call IMUInit in the normal way. Then for every update, call setIMUData and then IMURead // to kick the kalman filter. #include "RTIMU.h" class RTIMUSettings; class RTIMUNull : public RTIMU { public: RTIMUNull(RTIMUSettings *settings); ~RTIMUNull(); // The timestamp parameter is assumed to be from RTMath::currentUSecsSinceEpoch() void setIMUData(const RTIMU_DATA& data); virtual const char *IMUName() { return "Null IMU"; } virtual int IMUType() { return RTIMU_TYPE_NULL; } virtual bool IMUInit(); virtual int IMUGetPollInterval(); virtual bool IMURead(); virtual bool IMUGyroBiasValid() { return true; } private: uint64_t m_timestamp; }; #endif // _RTIMUNULL_H rtimulib-7.2.1/RTIMULib/IMUDrivers/RTPressure.cpp000066400000000000000000000043761254201074400214630ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTPressure.h" #include "RTPressureBMP180.h" #include "RTPressureLPS25H.h" #include "RTPressureMS5611.h" #include "RTPressureMS5637.h" RTPressure *RTPressure::createPressure(RTIMUSettings *settings) { switch (settings->m_pressureType) { case RTPRESSURE_TYPE_BMP180: return new RTPressureBMP180(settings); case RTPRESSURE_TYPE_LPS25H: return new RTPressureLPS25H(settings); case RTPRESSURE_TYPE_MS5611: return new RTPressureMS5611(settings); case RTPRESSURE_TYPE_MS5637: return new RTPressureMS5637(settings); case RTPRESSURE_TYPE_AUTODISCOVER: if (settings->discoverPressure(settings->m_pressureType, settings->m_I2CPressureAddress)) { settings->saveSettings(); return RTPressure::createPressure(settings); } return NULL; case RTPRESSURE_TYPE_NULL: return NULL; default: return NULL; } } RTPressure::RTPressure(RTIMUSettings *settings) { m_settings = settings; } RTPressure::~RTPressure() { } rtimulib-7.2.1/RTIMULib/IMUDrivers/RTPressure.h000066400000000000000000000042401254201074400211160ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTPRESSURE_H #define _RTPRESSURE_H #include "RTIMUSettings.h" #include "RTIMULibDefs.h" #include "RTPressureDefs.h" class RTPressure { public: // Pressure sensor objects should always be created with the following call static RTPressure *createPressure(RTIMUSettings *settings); // Constructor/destructor RTPressure(RTIMUSettings *settings); virtual ~RTPressure(); // These functions must be provided by sub classes virtual const char *pressureName() = 0; // the name of the pressure sensor virtual int pressureType() = 0; // the type code of the pressure sensor virtual bool pressureInit() = 0; // set up the pressure sensor virtual bool pressureRead(RTIMU_DATA& data) = 0; // get latest value protected: RTIMUSettings *m_settings; // the settings object pointer }; #endif // _RTPRESSURE_H rtimulib-7.2.1/RTIMULib/IMUDrivers/RTPressureBMP180.cpp000066400000000000000000000174421254201074400222510ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTPressureBMP180.h" RTPressureBMP180::RTPressureBMP180(RTIMUSettings *settings) : RTPressure(settings) { m_validReadings = false; } RTPressureBMP180::~RTPressureBMP180() { } bool RTPressureBMP180::pressureInit() { unsigned char result; unsigned char data[22]; m_pressureAddr = m_settings->m_I2CPressureAddress; // check ID of chip if (!m_settings->HALRead(m_pressureAddr, BMP180_REG_ID, 1, &result, "Failed to read BMP180 id")) return false; if (result != BMP180_ID) { HAL_ERROR1("Incorrect BMP180 id %d\n", result); return false; } // get calibration data if (!m_settings->HALRead(m_pressureAddr, BMP180_REG_AC1, 22, data, "Failed to read BMP180 calibration data")) return false; m_AC1 = (int16_t)(((uint16_t)data[0]) << 8) + (uint16_t)data[1]; m_AC2 = (int16_t)(((uint16_t)data[2]) << 8) + (uint16_t)data[3]; m_AC3 = (int16_t)(((uint16_t)data[4]) << 8) + (uint16_t)data[4]; m_AC4 = (((uint16_t)data[6]) << 8) + (uint16_t)data[7]; m_AC5 = (((uint16_t)data[8]) << 8) + (uint16_t)data[9]; m_AC6 = (((uint16_t)data[10]) << 8) + (uint16_t)data[11]; m_B1 = (int16_t)(((uint16_t)data[12]) << 8) + (uint16_t)data[13]; m_B2 = (int16_t)(((uint16_t)data[14]) << 8) + (uint16_t)data[15]; m_MB = (int16_t)(((uint16_t)data[16]) << 8) + (uint16_t)data[17]; m_MC = (int16_t)(((uint16_t)data[18]) << 8) + (uint16_t)data[19]; m_MD = (int16_t)(((uint16_t)data[20]) << 8) + (uint16_t)data[21]; m_state = BMP180_STATE_IDLE; m_oss = BMP180_SCO_PRESSURECONV_ULP; return true; } bool RTPressureBMP180::pressureRead(RTIMU_DATA& data) { data.pressureValid = false; data.temperatureValid = false; data.temperature = 0; data.pressure = 0; if (m_state == BMP180_STATE_IDLE) { // start a temperature conversion if (!m_settings->HALWrite(m_pressureAddr, BMP180_REG_SCO, BMP180_SCO_TEMPCONV, "Failed to start temperature conversion")) { return false; } else { m_state = BMP180_STATE_TEMPERATURE; } } pressureBackground(); if (m_validReadings) { data.pressureValid = true; data.temperatureValid = true; data.temperature = m_temperature; data.pressure = m_pressure; // printf("P: %f, T: %f\n", m_pressure, m_temperature); } return true; } void RTPressureBMP180::pressureBackground() { uint8_t data[2]; switch (m_state) { case BMP180_STATE_IDLE: break; case BMP180_STATE_TEMPERATURE: if (!m_settings->HALRead(m_pressureAddr, BMP180_REG_SCO, 1, data, "Failed to read BMP180 temp conv status")) { break; } if ((data[0] & 0x20) == 0x20) break; // conversion not finished if (!m_settings->HALRead(m_pressureAddr, BMP180_REG_RESULT, 2, data, "Failed to read BMP180 temp conv result")) { m_state = BMP180_STATE_IDLE; break; } m_rawTemperature = (((uint16_t)data[0]) << 8) + (uint16_t)data[1]; data[0] = 0x34 + (m_oss << 6); if (!m_settings->HALWrite(m_pressureAddr, BMP180_REG_SCO, 1, data, "Failed to start pressure conversion")) { m_state = BMP180_STATE_IDLE; break; } m_state = BMP180_STATE_PRESSURE; break; case BMP180_STATE_PRESSURE: if (!m_settings->HALRead(m_pressureAddr, BMP180_REG_SCO, 1, data, "Failed to read BMP180 pressure conv status")) { break; } if ((data[0] & 0x20) == 0x20) break; // conversion not finished if (!m_settings->HALRead(m_pressureAddr, BMP180_REG_RESULT, 2, data, "Failed to read BMP180 temp conv result")) { m_state = BMP180_STATE_IDLE; break; } m_rawPressure = (((uint16_t)data[0]) << 8) + (uint16_t)data[1]; if (!m_settings->HALRead(m_pressureAddr, BMP180_REG_XLSB, 1, data, "Failed to read BMP180 XLSB")) { m_state = BMP180_STATE_IDLE; break; } // call this function for testing only // should give T = 150 (15.0C) and pressure 6996 (699.6hPa) // setTestData(); int32_t pressure = ((((uint32_t)(m_rawPressure)) << 8) + (uint32_t)(data[0])) >> (8 - m_oss); m_state = BMP180_STATE_IDLE; // calculate compensated temperature int32_t X1 = (((int32_t)m_rawTemperature - m_AC6) * m_AC5) / 32768; if ((X1 + m_MD) == 0) { break; } int32_t X2 = (m_MC * 2048) / (X1 + m_MD); int32_t B5 = X1 + X2; m_temperature = (RTFLOAT)((B5 + 8) / 16) / (RTFLOAT)10; // calculate compensated pressure int32_t B6 = B5 - 4000; // printf("B6 = %d\n", B6); X1 = (m_B2 * ((B6 * B6) / 4096)) / 2048; // printf("X1 = %d\n", X1); X2 = (m_AC2 * B6) / 2048; // printf("X2 = %d\n", X2); int32_t X3 = X1 + X2; // printf("X3 = %d\n", X3); int32_t B3 = (((m_AC1 * 4 + X3) << m_oss) + 2) / 4; // printf("B3 = %d\n", B3); X1 = (m_AC3 * B6) / 8192; // printf("X1 = %d\n", X1); X2 = (m_B1 * ((B6 * B6) / 4096)) / 65536; // printf("X2 = %d\n", X2); X3 = ((X1 + X2) + 2) / 4; // printf("X3 = %d\n", X3); int32_t B4 = (m_AC4 * (unsigned long)(X3 + 32768)) / 32768; // printf("B4 = %d\n", B4); uint32_t B7 = ((unsigned long)pressure - B3) * (50000 >> m_oss); // printf("B7 = %d\n", B7); int32_t p; if (B7 < 0x80000000) p = (B7 * 2) / B4; else p = (B7 / B4) * 2; // printf("p = %d\n", p); X1 = (p / 256) * (p / 256); // printf("X1 = %d\n", X1); X1 = (X1 * 3038) / 65536; // printf("X1 = %d\n", X1); X2 = (-7357 * p) / 65536; // printf("X2 = %d\n", X2); m_pressure = (RTFLOAT)(p + (X1 + X2 + 3791) / 16) / (RTFLOAT)100; // the extra 100 factor is to get 1hPa units m_validReadings = true; // printf("UP = %d, P = %f, UT = %d, T = %f\n", m_rawPressure, m_pressure, m_rawTemperature, m_temperature); break; } } void RTPressureBMP180::setTestData() { m_AC1 = 408; m_AC2 = -72; m_AC3 = -14383; m_AC4 = 32741; m_AC5 = 32757; m_AC6 = 23153; m_B1 = 6190; m_B2 = 4; m_MB = -32767; m_MC = -8711; m_MD = 2868; m_rawTemperature = 27898; m_rawPressure = 23843; } rtimulib-7.2.1/RTIMULib/IMUDrivers/RTPressureBMP180.h000066400000000000000000000057751254201074400217240ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTPRESSUREBMP180_H_ #define _RTPRESSUREBMP180_H_ #include "RTPressure.h" // State definitions #define BMP180_STATE_IDLE 0 #define BMP180_STATE_TEMPERATURE 1 #define BMP180_STATE_PRESSURE 2 // Conversion reg defs #define BMP180_SCO_TEMPCONV 0x2e // temperature conversion #define BMP180_SCO_PRESSURECONV_ULP 0 // ultra low power pressure conversion #define BMP180_SCO_PRESSURECONV_STD 1 // standard pressure conversion #define BMP180_SCO_PRESSURECONV_HR 2 // high res pressure conversion #define BMP180_SCO_PRESSURECONV_UHR 3 // ultra high res pressure conversion class RTIMUSettings; class RTPressureBMP180 : public RTPressure { public: RTPressureBMP180(RTIMUSettings *settings); ~RTPressureBMP180(); virtual const char *pressureName() { return "BMP180"; } virtual int pressureType() { return RTPRESSURE_TYPE_BMP180; } virtual bool pressureInit(); virtual bool pressureRead(RTIMU_DATA& data); private: void pressureBackground(); void setTestData(); unsigned char m_pressureAddr; // I2C address RTFLOAT m_pressure; // the current pressure RTFLOAT m_temperature; // the current temperature // This is the calibration data read from the sensor int32_t m_AC1; int32_t m_AC2; int32_t m_AC3; uint32_t m_AC4; uint32_t m_AC5; uint32_t m_AC6; int32_t m_B1; int32_t m_B2; int32_t m_MB; int32_t m_MC; int32_t m_MD; int m_state; int m_oss; uint16_t m_rawPressure; uint16_t m_rawTemperature; bool m_validReadings; }; #endif // _RTPRESSUREBMP180_H_ rtimulib-7.2.1/RTIMULib/IMUDrivers/RTPressureDefs.h000066400000000000000000000074001254201074400217210ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTPRESSUREDEFS_H #define _RTPRESSUREDEFS_H // Pressure sensor type codes #define RTPRESSURE_TYPE_AUTODISCOVER 0 // audodiscover the pressure sensor #define RTPRESSURE_TYPE_NULL 1 // if no physical hardware #define RTPRESSURE_TYPE_BMP180 2 // BMP180 #define RTPRESSURE_TYPE_LPS25H 3 // LPS25H #define RTPRESSURE_TYPE_MS5611 4 // MS5611 #define RTPRESSURE_TYPE_MS5637 5 // MS5637 //---------------------------------------------------------- // // BMP180 // BMP180 I2C Slave Addresses #define BMP180_ADDRESS 0x77 #define BMP180_REG_ID 0xd0 #define BMP180_ID 0x55 // Register map #define BMP180_REG_AC1 0xaa #define BMP180_REG_SCO 0xf4 #define BMP180_REG_RESULT 0xf6 #define BMP180_REG_XLSB 0xf8 //---------------------------------------------------------- // // LPS25H // LPS25H I2C Slave Addresses #define LPS25H_ADDRESS0 0x5c #define LPS25H_ADDRESS1 0x5d #define LPS25H_REG_ID 0x0f #define LPS25H_ID 0xbd // Register map #define LPS25H_REF_P_XL 0x08 #define LPS25H_REF_P_XH 0x09 #define LPS25H_RES_CONF 0x10 #define LPS25H_CTRL_REG_1 0x20 #define LPS25H_CTRL_REG_2 0x21 #define LPS25H_CTRL_REG_3 0x22 #define LPS25H_CTRL_REG_4 0x23 #define LPS25H_INT_CFG 0x24 #define LPS25H_INT_SOURCE 0x25 #define LPS25H_STATUS_REG 0x27 #define LPS25H_PRESS_OUT_XL 0x28 #define LPS25H_PRESS_OUT_L 0x29 #define LPS25H_PRESS_OUT_H 0x2a #define LPS25H_TEMP_OUT_L 0x2b #define LPS25H_TEMP_OUT_H 0x2c #define LPS25H_FIFO_CTRL 0x2e #define LPS25H_FIFO_STATUS 0x2f #define LPS25H_THS_P_L 0x30 #define LPS25H_THS_P_H 0x31 #define LPS25H_RPDS_L 0x39 #define LPS25H_RPDS_H 0x3a //---------------------------------------------------------- // // MS5611 and MS5637 // MS5611 I2C Slave Addresses #define MS5611_ADDRESS0 0x76 #define MS5611_ADDRESS1 0x77 // commands #define MS5611_CMD_RESET 0x1e #define MS5611_CMD_CONV_D1 0x48 #define MS5611_CMD_CONV_D2 0x58 #define MS5611_CMD_PROM 0xa0 #define MS5611_CMD_ADC 0x00 #endif // _RTPRESSUREDEFS_H rtimulib-7.2.1/RTIMULib/IMUDrivers/RTPressureLPS25H.cpp000066400000000000000000000064631254201074400223200ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTPressureLPS25H.h" #include "RTPressureDefs.h" RTPressureLPS25H::RTPressureLPS25H(RTIMUSettings *settings) : RTPressure(settings) { m_pressureValid = false; m_temperatureValid = false; } RTPressureLPS25H::~RTPressureLPS25H() { } bool RTPressureLPS25H::pressureInit() { m_pressureAddr = m_settings->m_I2CPressureAddress; if (!m_settings->HALWrite(m_pressureAddr, LPS25H_CTRL_REG_1, 0xc4, "Failed to set LPS25H CTRL_REG_1")) return false; if (!m_settings->HALWrite(m_pressureAddr, LPS25H_RES_CONF, 0x05, "Failed to set LPS25H RES_CONF")) return false; if (!m_settings->HALWrite(m_pressureAddr, LPS25H_FIFO_CTRL, 0xc0, "Failed to set LPS25H FIFO_CTRL")) return false; if (!m_settings->HALWrite(m_pressureAddr, LPS25H_CTRL_REG_2, 0x40, "Failed to set LPS25H CTRL_REG_2")) return false; return true; } bool RTPressureLPS25H::pressureRead(RTIMU_DATA& data) { unsigned char rawData[3]; unsigned char status; data.pressureValid = false; data.temperatureValid = false; data.temperature = 0; data.pressure = 0; if (!m_settings->HALRead(m_pressureAddr, LPS25H_STATUS_REG, 1, &status, "Failed to read LPS25H status")) return false; if (status & 2) { if (!m_settings->HALRead(m_pressureAddr, LPS25H_PRESS_OUT_XL + 0x80, 3, rawData, "Failed to read LPS25H pressure")) return false; m_pressure = (RTFLOAT)((((unsigned int)rawData[2]) << 16) | (((unsigned int)rawData[1]) << 8) | (unsigned int)rawData[0]) / (RTFLOAT)4096; m_pressureValid = true; } if (status & 1) { if (!m_settings->HALRead(m_pressureAddr, LPS25H_TEMP_OUT_L + 0x80, 2, rawData, "Failed to read LPS25H temperature")) return false; m_temperature = (int16_t)((((unsigned int)rawData[1]) << 8) | (unsigned int)rawData[0]) / (RTFLOAT)480 + (RTFLOAT)42.5; m_temperatureValid = true; } data.pressureValid = m_pressureValid; data.pressure = m_pressure; data.temperatureValid = m_temperatureValid; data.temperature = m_temperature; return true; } rtimulib-7.2.1/RTIMULib/IMUDrivers/RTPressureLPS25H.h000066400000000000000000000037711254201074400217640ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTPRESSURELPS25H_H_ #define _RTPRESSURELPS25H_H_ #include "RTPressure.h" class RTIMUSettings; class RTPressureLPS25H : public RTPressure { public: RTPressureLPS25H(RTIMUSettings *settings); ~RTPressureLPS25H(); virtual const char *pressureName() { return "LPS25H"; } virtual int pressureType() { return RTPRESSURE_TYPE_LPS25H; } virtual bool pressureInit(); virtual bool pressureRead(RTIMU_DATA& data); private: unsigned char m_pressureAddr; // I2C address RTFLOAT m_pressure; // the current pressure RTFLOAT m_temperature; // the current temperature bool m_pressureValid; bool m_temperatureValid; }; #endif // _RTPRESSURELPS25H_H_ rtimulib-7.2.1/RTIMULib/IMUDrivers/RTPressureMS5611.cpp000066400000000000000000000132671254201074400222370ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTPressureMS5611.h" RTPressureMS5611::RTPressureMS5611(RTIMUSettings *settings) : RTPressure(settings) { m_validReadings = false; } RTPressureMS5611::~RTPressureMS5611() { } bool RTPressureMS5611::pressureInit() { unsigned char cmd = MS5611_CMD_PROM + 2; unsigned char data[2]; m_pressureAddr = m_settings->m_I2CPressureAddress; // get calibration data for (int i = 0; i < 6; i++) { if (!m_settings->HALRead(m_pressureAddr, cmd, 2, data, "Failed to read MS5611 calibration data")) return false; m_calData[i] = (((uint16_t)data[0]) << 8) + (uint16_t)data[1]; // printf("Cal index: %d, data: %d\n", i, m_calData[i]); cmd += 2; } m_state = MS5611_STATE_IDLE; return true; } bool RTPressureMS5611::pressureRead(RTIMU_DATA& data) { data.pressureValid = false; data.temperatureValid = false; data.temperature = 0; data.pressure = 0; if (m_state == MS5611_STATE_IDLE) { // start pressure conversion if (!m_settings->HALWrite(m_pressureAddr, MS5611_CMD_CONV_D1, 0, 0, "Failed to start MS5611 pressure conversion")) { return false; } else { m_state = MS5611_STATE_PRESSURE; m_timer = RTMath::currentUSecsSinceEpoch(); } } pressureBackground(); if (m_validReadings) { data.pressureValid = true; data.temperatureValid = true; data.temperature = m_temperature; data.pressure = m_pressure; } return true; } void RTPressureMS5611::pressureBackground() { uint8_t data[3]; switch (m_state) { case MS5611_STATE_IDLE: break; case MS5611_STATE_PRESSURE: if ((RTMath::currentUSecsSinceEpoch() - m_timer) < 10000) break; // not time yet if (!m_settings->HALRead(m_pressureAddr, MS5611_CMD_ADC, 3, data, "Failed to read MS5611 pressure")) { break; } m_D1 = (((uint32_t)data[0]) << 16) + (((uint32_t)data[1]) << 8) + (uint32_t)data[2]; // start temperature conversion if (!m_settings->HALWrite(m_pressureAddr, MS5611_CMD_CONV_D2, 0, 0, "Failed to start MS5611 temperature conversion")) { break; } else { m_state = MS5611_STATE_TEMPERATURE; m_timer = RTMath::currentUSecsSinceEpoch(); } break; case MS5611_STATE_TEMPERATURE: if ((RTMath::currentUSecsSinceEpoch() - m_timer) < 10000) break; // not time yet if (!m_settings->HALRead(m_pressureAddr, MS5611_CMD_ADC, 3, data, "Failed to read MS5611 temperature")) { break; } m_D2 = (((uint32_t)data[0]) << 16) + (((uint32_t)data[1]) << 8) + (uint32_t)data[2]; // call this function for testing only // should give T = 2007 (20.07C) and pressure 100009 (1000.09hPa) // setTestData(); // now calculate the real values int64_t deltaT = (int32_t)m_D2 - (((int32_t)m_calData[4]) << 8); int32_t temperature = 2000 + ((deltaT * (int64_t)m_calData[5]) >> 23); // note - this needs to be divided by 100 int64_t offset = ((int64_t)m_calData[1] << 16) + (((int64_t)m_calData[3] * deltaT) >> 7); int64_t sens = ((int64_t)m_calData[0] << 15) + (((int64_t)m_calData[2] * deltaT) >> 8); // do second order temperature compensation if (temperature < 2000) { int64_t T2 = (deltaT * deltaT) >> 31; int64_t offset2 = 5 * ((temperature - 2000) * (temperature - 2000)) / 2; int64_t sens2 = offset2 / 2; if (temperature < -1500) { offset2 += 7 * (temperature + 1500) * (temperature + 1500); sens2 += 11 * ((temperature + 1500) * (temperature + 1500)) / 2; } temperature -= T2; offset -= offset2; sens -=sens2; } m_pressure = (RTFLOAT)(((((int64_t)m_D1 * sens) >> 21) - offset) >> 15) / (RTFLOAT)100.0; m_temperature = (RTFLOAT)temperature/(RTFLOAT)100; // printf("Temp: %f, pressure: %f\n", m_temperature, m_pressure); m_validReadings = true; m_state = MS5611_STATE_IDLE; break; } } void RTPressureMS5611::setTestData() { m_calData[0] = 40127; m_calData[1] = 36924; m_calData[2] = 23317; m_calData[3] = 23282; m_calData[4] = 33464; m_calData[5] = 28312; m_D1 = 9085466; m_D2 = 8569150; } rtimulib-7.2.1/RTIMULib/IMUDrivers/RTPressureMS5611.h000066400000000000000000000046131254201074400216770ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTPRESSUREMS5611_H_ #define _RTPRESSUREMS5611_H_ #include "RTPressure.h" // State definitions #define MS5611_STATE_IDLE 0 #define MS5611_STATE_TEMPERATURE 1 #define MS5611_STATE_PRESSURE 2 class RTIMUSettings; class RTPressureMS5611 : public RTPressure { public: RTPressureMS5611(RTIMUSettings *settings); ~RTPressureMS5611(); virtual const char *pressureName() { return "MS5611"; } virtual int pressureType() { return RTPRESSURE_TYPE_MS5611; } virtual bool pressureInit(); virtual bool pressureRead(RTIMU_DATA& data); private: void pressureBackground(); void setTestData(); unsigned char m_pressureAddr; // I2C address RTFLOAT m_pressure; // the current pressure RTFLOAT m_temperature; // the current temperature int m_state; uint16_t m_calData[6]; // calibration data uint32_t m_D1; uint32_t m_D2; uint64_t m_timer; // used to time coversions bool m_validReadings; }; #endif // _RTPRESSUREMS5611_H_ rtimulib-7.2.1/RTIMULib/IMUDrivers/RTPressureMS5637.cpp000066400000000000000000000134601254201074400222420ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTPressureMS5637.h" RTPressureMS5637::RTPressureMS5637(RTIMUSettings *settings) : RTPressure(settings) { m_validReadings = false; } RTPressureMS5637::~RTPressureMS5637() { } bool RTPressureMS5637::pressureInit() { unsigned char cmd = MS5611_CMD_PROM + 2; unsigned char data[2]; m_pressureAddr = m_settings->m_I2CPressureAddress; // get calibration data for (int i = 0; i < 6; i++) { if (!m_settings->HALRead(m_pressureAddr, cmd, 2, data, "Failed to read MS5611 calibration data")) return false; m_calData[i] = (((uint16_t)data[0]) << 8) | ((uint16_t)data[1]); // printf("Cal index: %d, data: %d\n", i, m_calData[i]); cmd += 2; } m_state = MS5637_STATE_IDLE; return true; } bool RTPressureMS5637::pressureRead(RTIMU_DATA& data) { data.pressureValid = false; data.temperatureValid = false; data.temperature = 0; data.pressure = 0; if (m_state == MS5637_STATE_IDLE) { // start pressure conversion if (!m_settings->HALWrite(m_pressureAddr, MS5611_CMD_CONV_D1, 0, 0, "Failed to start MS5611 pressure conversion")) { return false; } else { m_state = MS5637_STATE_PRESSURE; m_timer = RTMath::currentUSecsSinceEpoch(); } } pressureBackground(); if (m_validReadings) { data.pressureValid = true; data.temperatureValid = true; data.temperature = m_temperature; data.pressure = m_pressure; } return true; } void RTPressureMS5637::pressureBackground() { uint8_t data[3]; switch (m_state) { case MS5637_STATE_IDLE: break; case MS5637_STATE_PRESSURE: if ((RTMath::currentUSecsSinceEpoch() - m_timer) < 10000) break; // not time yet if (!m_settings->HALRead(m_pressureAddr, MS5611_CMD_ADC, 3, data, "Failed to read MS5611 pressure")) { break; } m_D1 = (((uint32_t)data[0]) << 16) | (((uint32_t)data[1]) << 8) | ((uint32_t)data[2]); // start temperature conversion if (!m_settings->HALWrite(m_pressureAddr, MS5611_CMD_CONV_D2, 0, 0, "Failed to start MS5611 temperature conversion")) { break; } else { m_state = MS5637_STATE_TEMPERATURE; m_timer = RTMath::currentUSecsSinceEpoch(); } break; case MS5637_STATE_TEMPERATURE: if ((RTMath::currentUSecsSinceEpoch() - m_timer) < 10000) break; // not time yet if (!m_settings->HALRead(m_pressureAddr, MS5611_CMD_ADC, 3, data, "Failed to read MS5611 temperature")) { break; } m_D2 = (((uint32_t)data[0]) << 16) | (((uint32_t)data[1]) << 8) | ((uint32_t)data[2]); // call this function for testing only // should give T = 2000 (20.00C) and pressure 110002 (1100.02hPa) // setTestData(); // now calculate the real values int64_t deltaT = (int32_t)m_D2 - (((int32_t)m_calData[4]) << 8); int32_t temperature = 2000 + ((deltaT * (int64_t)m_calData[5]) >> 23); // note - this still needs to be divided by 100 int64_t offset = (((int64_t)m_calData[1]) << 17) + ((m_calData[3] * deltaT) >> 6); int64_t sens = (((int64_t)m_calData[0]) << 16) + ((m_calData[2] * deltaT) >> 7); // do second order temperature compensation if (temperature < 2000) { int64_t T2 = (3 * (deltaT * deltaT)) >> 33; int64_t offset2 = 61 * ((temperature - 2000) * (temperature - 2000)) / 16; int64_t sens2 = 29 * ((temperature - 2000) * (temperature - 2000)) / 16; if (temperature < -1500) { offset2 += 17 * (temperature + 1500) * (temperature + 1500); sens2 += 9 * ((temperature + 1500) * (temperature + 1500)); } temperature -= T2; offset -= offset2; sens -=sens2; } else { temperature -= (5 * (deltaT * deltaT)) >> 38; } m_pressure = (RTFLOAT)(((((int64_t)m_D1 * sens) >> 21) - offset) >> 15) / (RTFLOAT)100.0; m_temperature = (RTFLOAT)temperature/(RTFLOAT)100; // printf("Temp: %f, pressure: %f\n", m_temperature, m_pressure); m_validReadings = true; m_state = MS5637_STATE_IDLE; break; } } void RTPressureMS5637::setTestData() { m_calData[0] = 46372; m_calData[1] = 43981; m_calData[2] = 29059; m_calData[3] = 27842; m_calData[4] = 31553; m_calData[5] = 28165; m_D1 = 6465444; m_D2 = 8077636; } rtimulib-7.2.1/RTIMULib/IMUDrivers/RTPressureMS5637.h000066400000000000000000000046131254201074400217070ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTPRESSUREMS5637_H_ #define _RTPRESSUREMS5637_H_ #include "RTPressure.h" // State definitions #define MS5637_STATE_IDLE 0 #define MS5637_STATE_TEMPERATURE 1 #define MS5637_STATE_PRESSURE 2 class RTIMUSettings; class RTPressureMS5637 : public RTPressure { public: RTPressureMS5637(RTIMUSettings *settings); ~RTPressureMS5637(); virtual const char *pressureName() { return "MS5637"; } virtual int pressureType() { return RTPRESSURE_TYPE_MS5611; } virtual bool pressureInit(); virtual bool pressureRead(RTIMU_DATA& data); private: void pressureBackground(); void setTestData(); unsigned char m_pressureAddr; // I2C address RTFLOAT m_pressure; // the current pressure RTFLOAT m_temperature; // the current temperature int m_state; uint16_t m_calData[6]; // calibration data uint32_t m_D1; uint32_t m_D2; uint64_t m_timer; // used to time coversions bool m_validReadings; }; #endif // _RTPRESSUREMS5637_H_ rtimulib-7.2.1/RTIMULib/RTFusion.cpp000066400000000000000000000104331254201074400171140ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTFusion.h" #include "RTIMUHal.h" // The slerp power valule controls the influence of the measured state to correct the predicted state // 0 = measured state ignored (just gyros), 1 = measured state overrides predicted state. // In between 0 and 1 mixes the two conditions #define RTQF_SLERP_POWER (RTFLOAT)0.02; const char *RTFusion::m_fusionNameMap[] = { "NULL", "Kalman STATE4", "RTQF"}; RTFusion::RTFusion() { m_debug = false; m_firstTime = true; m_enableGyro = true; m_enableAccel = true; m_enableCompass = true; m_gravity.setScalar(0); m_gravity.setX(0); m_gravity.setY(0); m_gravity.setZ(1); m_slerpPower = RTQF_SLERP_POWER; } RTFusion::~RTFusion() { } void RTFusion::calculatePose(const RTVector3& accel, const RTVector3& mag, float magDeclination) { RTQuaternion m; RTQuaternion q; if (m_enableAccel) { accel.accelToEuler(m_measuredPose); } else { m_measuredPose = m_fusionPose; m_measuredPose.setZ(0); } if (m_enableCompass && m_compassValid) { q.fromEuler(m_measuredPose); m.setScalar(0); m.setX(mag.x()); m.setY(mag.y()); m.setZ(mag.z()); m = q * m * q.conjugate(); m_measuredPose.setZ(-atan2(m.y(), m.x()) - magDeclination); } else { m_measuredPose.setZ(m_fusionPose.z()); } m_measuredQPose.fromEuler(m_measuredPose); // check for quaternion aliasing. If the quaternion has the wrong sign // the kalman filter will be very unhappy. int maxIndex = -1; RTFLOAT maxVal = -1000; for (int i = 0; i < 4; i++) { if (fabs(m_measuredQPose.data(i)) > maxVal) { maxVal = fabs(m_measuredQPose.data(i)); maxIndex = i; } } // if the biggest component has a different sign in the measured and kalman poses, // change the sign of the measured pose to match. if (((m_measuredQPose.data(maxIndex) < 0) && (m_fusionQPose.data(maxIndex) > 0)) || ((m_measuredQPose.data(maxIndex) > 0) && (m_fusionQPose.data(maxIndex) < 0))) { m_measuredQPose.setScalar(-m_measuredQPose.scalar()); m_measuredQPose.setX(-m_measuredQPose.x()); m_measuredQPose.setY(-m_measuredQPose.y()); m_measuredQPose.setZ(-m_measuredQPose.z()); m_measuredQPose.toEuler(m_measuredPose); } } RTVector3 RTFusion::getAccelResiduals() { RTQuaternion rotatedGravity; RTQuaternion fusedConjugate; RTQuaternion qTemp; RTVector3 residuals; // do gravity rotation and subtraction // create the conjugate of the pose fusedConjugate = m_fusionQPose.conjugate(); // now do the rotation - takes two steps with qTemp as the intermediate variable qTemp = m_gravity * m_fusionQPose; rotatedGravity = fusedConjugate * qTemp; // now adjust the measured accel and change the signs to make sense residuals.setX(-(m_accel.x() - rotatedGravity.x())); residuals.setY(-(m_accel.y() - rotatedGravity.y())); residuals.setZ(-(m_accel.z() - rotatedGravity.z())); return residuals; } rtimulib-7.2.1/RTIMULib/RTFusion.h000066400000000000000000000111661254201074400165650ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTFUSION_H #define _RTFUSION_H #include "RTIMULibDefs.h" class RTIMUSettings; class RTFusion { public: RTFusion(); virtual ~RTFusion(); // fusionType returns the type code of the fusion algorithm virtual int fusionType() { return RTFUSION_TYPE_NULL; } // the following function can be called to set the SLERP power void setSlerpPower(RTFLOAT power) { m_slerpPower = power; } // reset() resets the fusion state but keeps any setting changes (such as enables) virtual void reset() {} // newIMUData() should be called for subsequent updates // the fusion fields are updated with the results virtual void newIMUData(RTIMU_DATA& /* data */, const RTIMUSettings * /* settings */) {} // This static function returns performs the type to name mapping static const char *fusionName(int fusionType) { return m_fusionNameMap[fusionType]; } // the following three functions control the influence of the gyro, accel and compass sensors void setGyroEnable(bool enable) { m_enableGyro = enable;} void setAccelEnable(bool enable) { m_enableAccel = enable; } void setCompassEnable(bool enable) { m_enableCompass = enable;} inline const RTVector3& getMeasuredPose() {return m_measuredPose;} inline const RTQuaternion& getMeasuredQPose() {return m_measuredQPose;} // getAccelResiduals() gets the residual after subtracting gravity RTVector3 getAccelResiduals(); void setDebugEnable(bool enable) { m_debug = enable; } protected: void calculatePose(const RTVector3& accel, const RTVector3& mag, float magDeclination); // generates pose from accels and mag RTVector3 m_gyro; // current gyro sample RTVector3 m_accel; // current accel sample RTVector3 m_compass; // current compass sample RTQuaternion m_measuredQPose; // quaternion form of pose from measurement RTVector3 m_measuredPose; // vector form of pose from measurement RTQuaternion m_fusionQPose; // quaternion form of pose from fusion RTVector3 m_fusionPose; // vector form of pose from fusion RTQuaternion m_gravity; // the gravity vector as a quaternion RTFLOAT m_slerpPower; // a value 0 to 1 that controls measured state influence RTQuaternion m_rotationDelta; // amount by which measured state differs from predicted RTQuaternion m_rotationPower; // delta raised to the appopriate power RTVector3 m_rotationUnitVector; // the vector part of the rotation delta bool m_debug; bool m_enableGyro; // enables gyro as input bool m_enableAccel; // enables accel as input bool m_enableCompass; // enables compass a input bool m_compassValid; // true if compass data valid bool m_firstTime; // if first time after reset uint64_t m_lastFusionTime; // for delta time calculation static const char *m_fusionNameMap[]; // the fusion name array }; #endif // _RTFUSION_H rtimulib-7.2.1/RTIMULib/RTFusionKalman4.cpp000066400000000000000000000151021254201074400203220ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTFusionKalman4.h" #include "RTIMUSettings.h" // The QVALUE affects the gyro response. #define KALMAN_QVALUE 0.001f // The RVALUE controls the influence of the accels and compass. // The bigger the value, the more sluggish the response. #define KALMAN_RVALUE 0.0005f #define KALMAN_QUATERNION_LENGTH 4 #define KALMAN_STATE_LENGTH 4 // just the quaternion for the moment RTFusionKalman4::RTFusionKalman4() { reset(); } RTFusionKalman4::~RTFusionKalman4() { } void RTFusionKalman4::reset() { m_firstTime = true; m_fusionPose = RTVector3(); m_fusionQPose.fromEuler(m_fusionPose); m_gyro = RTVector3(); m_accel = RTVector3(); m_compass = RTVector3(); m_measuredPose = RTVector3(); m_measuredQPose.fromEuler(m_measuredPose); m_Rk.fill(0); m_Q.fill(0); // initialize process noise covariance matrix for (int i = 0; i < KALMAN_STATE_LENGTH; i++) for (int j = 0; j < KALMAN_STATE_LENGTH; j++) m_Q.setVal(i, i, KALMAN_QVALUE); // initialize observation noise covariance matrix for (int i = 0; i < KALMAN_STATE_LENGTH; i++) for (int j = 0; j < KALMAN_STATE_LENGTH; j++) m_Rk.setVal(i, i, KALMAN_RVALUE); } void RTFusionKalman4::predict() { RTMatrix4x4 mat; RTQuaternion tQuat; RTFLOAT x2, y2, z2; // compute the state transition matrix x2 = m_gyro.x() / (RTFLOAT)2.0; y2 = m_gyro.y() / (RTFLOAT)2.0; z2 = m_gyro.z() / (RTFLOAT)2.0; m_Fk.setVal(0, 1, -x2); m_Fk.setVal(0, 2, -y2); m_Fk.setVal(0, 3, -z2); m_Fk.setVal(1, 0, x2); m_Fk.setVal(1, 2, z2); m_Fk.setVal(1, 3, -y2); m_Fk.setVal(2, 0, y2); m_Fk.setVal(2, 1, -z2); m_Fk.setVal(2, 3, x2); m_Fk.setVal(3, 0, z2); m_Fk.setVal(3, 1, y2); m_Fk.setVal(3, 2, -x2); m_FkTranspose = m_Fk.transposed(); // Predict new state estimate Xkk_1 = Fk * Xk_1k_1 tQuat = m_Fk * m_stateQ; tQuat *= m_timeDelta; m_stateQ += tQuat; // m_stateQ.normalize(); // Compute PDot = Fk * Pk_1k_1 + Pk_1k_1 * FkTranspose (note Pkk == Pk_1k_1 at this stage) m_PDot = m_Fk * m_Pkk; mat = m_Pkk * m_FkTranspose; m_PDot += mat; // add in Q to get the new prediction m_Pkk_1 = m_PDot + m_Q; // multiply by deltaTime (variable name is now misleading though) m_Pkk_1 *= m_timeDelta; } void RTFusionKalman4::update() { RTQuaternion delta; RTMatrix4x4 Sk, SkInverse; if (m_enableCompass || m_enableAccel) { m_stateQError = m_measuredQPose - m_stateQ; } else { m_stateQError = RTQuaternion(); } // Compute residual covariance Sk = Hk * Pkk_1 * HkTranspose + Rk // Note: since Hk is the identity matrix, this has been simplified Sk = m_Pkk_1 + m_Rk; // Compute Kalman gain Kk = Pkk_1 * HkTranspose * SkInverse // Note: again, the HkTranspose part is omitted SkInverse = Sk.inverted(); m_Kk = m_Pkk_1 * SkInverse; if (m_debug) HAL_INFO(RTMath::display("Gain", m_Kk)); // make new state estimate delta = m_Kk * m_stateQError; m_stateQ += delta; m_stateQ.normalize(); // produce new estimate covariance Pkk = (I - Kk * Hk) * Pkk_1 // Note: since Hk is the identity matrix, it is omitted m_Pkk.setToIdentity(); m_Pkk -= m_Kk; m_Pkk = m_Pkk * m_Pkk_1; if (m_debug) HAL_INFO(RTMath::display("Cov", m_Pkk)); } void RTFusionKalman4::newIMUData(RTIMU_DATA& data, const RTIMUSettings *settings) { if (m_enableGyro) m_gyro = data.gyro; else m_gyro = RTVector3(); m_accel = data.accel; m_compass = data.compass; m_compassValid = data.compassValid; if (m_firstTime) { m_lastFusionTime = data.timestamp; calculatePose(m_accel, m_compass, settings->m_compassAdjDeclination); m_Fk.fill(0); // init covariance matrix to something m_Pkk.fill(0); for (int i = 0; i < 4; i++) for (int j = 0; j < 4; j++) m_Pkk.setVal(i,j, 0.5); // initialize the observation model Hk // Note: since the model is the state vector, this is an identity matrix so it won't be used // initialize the poses m_stateQ.fromEuler(m_measuredPose); m_fusionQPose = m_stateQ; m_fusionPose = m_measuredPose; m_firstTime = false; } else { m_timeDelta = (RTFLOAT)(data.timestamp - m_lastFusionTime) / (RTFLOAT)1000000; m_lastFusionTime = data.timestamp; if (m_timeDelta <= 0) return; if (m_debug) { HAL_INFO("\n------\n"); HAL_INFO1("IMU update delta time: %f\n", m_timeDelta); } calculatePose(data.accel, data.compass, settings->m_compassAdjDeclination); predict(); update(); m_stateQ.toEuler(m_fusionPose); m_fusionQPose = m_stateQ; if (m_debug) { HAL_INFO(RTMath::displayRadians("Measured pose", m_measuredPose)); HAL_INFO(RTMath::displayRadians("Kalman pose", m_fusionPose)); HAL_INFO(RTMath::displayRadians("Measured quat", m_measuredPose)); HAL_INFO(RTMath::display("Kalman quat", m_stateQ)); HAL_INFO(RTMath::display("Error quat", m_stateQError)); } } data.fusionPoseValid = true; data.fusionQPoseValid = true; data.fusionPose = m_fusionPose; data.fusionQPose = m_fusionQPose; } rtimulib-7.2.1/RTIMULib/RTFusionKalman4.h000066400000000000000000000066131254201074400177760ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTFUSIONKALMAN4_H #define _RTFUSIONKALMAN4_H #include "RTFusion.h" class RTFusionKalman4 : public RTFusion { public: RTFusionKalman4(); ~RTFusionKalman4(); // fusionType returns the type code of the fusion algorithm virtual int fusionType() { return RTFUSION_TYPE_KALMANSTATE4; } // reset() resets the kalman state but keeps any setting changes (such as enables) void reset(); // newIMUData() should be called for subsequent updates // deltaTime is in units of seconds void newIMUData(RTIMU_DATA& data, const RTIMUSettings *settings); // the following two functions can be called to customize the covariance matrices void setQMatrix(RTMatrix4x4 Q) { m_Q = Q; reset();} void setRkMatrix(RTMatrix4x4 Rk) { m_Rk = Rk; reset();} private: void predict(); void update(); RTVector3 m_gyro; // unbiased gyro data RTFLOAT m_timeDelta; // time between predictions RTQuaternion m_stateQ; // quaternion state vector RTQuaternion m_stateQError; // difference between stateQ and measuredQ RTMatrix4x4 m_Kk; // the Kalman gain matrix RTMatrix4x4 m_Pkk_1; // the predicted estimated covariance matrix RTMatrix4x4 m_Pkk; // the updated estimated covariance matrix RTMatrix4x4 m_PDot; // the derivative of the covariance matrix RTMatrix4x4 m_Q; // process noise covariance RTMatrix4x4 m_Fk; // the state transition matrix RTMatrix4x4 m_FkTranspose; // the state transition matrix transposed RTMatrix4x4 m_Rk; // the measurement noise covariance // Note: SInce Hk ends up being the identity matrix, these are omitted // RTMatrix4x4 m_Hk; // map from state to measurement // RTMatrix4x4> m_HkTranspose; // transpose of map }; #endif // _RTFUSIONKALMAN4_H rtimulib-7.2.1/RTIMULib/RTFusionRTQF.cpp000066400000000000000000000121221254201074400176060ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTFusionRTQF.h" #include "RTIMUSettings.h" RTFusionRTQF::RTFusionRTQF() { reset(); } RTFusionRTQF::~RTFusionRTQF() { } void RTFusionRTQF::reset() { m_firstTime = true; m_fusionPose = RTVector3(); m_fusionQPose.fromEuler(m_fusionPose); m_gyro = RTVector3(); m_accel = RTVector3(); m_compass = RTVector3(); m_measuredPose = RTVector3(); m_measuredQPose.fromEuler(m_measuredPose); m_sampleNumber = 0; } void RTFusionRTQF::predict() { RTFLOAT x2, y2, z2; RTFLOAT qs, qx, qy,qz; if (!m_enableGyro) return; qs = m_stateQ.scalar(); qx = m_stateQ.x(); qy = m_stateQ.y(); qz = m_stateQ.z(); x2 = m_gyro.x() / (RTFLOAT)2.0; y2 = m_gyro.y() / (RTFLOAT)2.0; z2 = m_gyro.z() / (RTFLOAT)2.0; // Predict new state m_stateQ.setScalar(qs + (-x2 * qx - y2 * qy - z2 * qz) * m_timeDelta); m_stateQ.setX(qx + (x2 * qs + z2 * qy - y2 * qz) * m_timeDelta); m_stateQ.setY(qy + (y2 * qs - z2 * qx + x2 * qz) * m_timeDelta); m_stateQ.setZ(qz + (z2 * qs + y2 * qx - x2 * qy) * m_timeDelta); m_stateQ.normalize(); } void RTFusionRTQF::update() { if (m_enableCompass || m_enableAccel) { // calculate rotation delta m_rotationDelta = m_stateQ.conjugate() * m_measuredQPose; m_rotationDelta.normalize(); // take it to the power (0 to 1) to give the desired amount of correction RTFLOAT theta = acos(m_rotationDelta.scalar()); RTFLOAT sinPowerTheta = sin(theta * m_slerpPower); RTFLOAT cosPowerTheta = cos(theta * m_slerpPower); m_rotationUnitVector.setX(m_rotationDelta.x()); m_rotationUnitVector.setY(m_rotationDelta.y()); m_rotationUnitVector.setZ(m_rotationDelta.z()); m_rotationUnitVector.normalize(); m_rotationPower.setScalar(cosPowerTheta); m_rotationPower.setX(sinPowerTheta * m_rotationUnitVector.x()); m_rotationPower.setY(sinPowerTheta * m_rotationUnitVector.y()); m_rotationPower.setZ(sinPowerTheta * m_rotationUnitVector.z()); m_rotationPower.normalize(); // multiple this by predicted value to get result m_stateQ *= m_rotationPower; m_stateQ.normalize(); } } void RTFusionRTQF::newIMUData(RTIMU_DATA& data, const RTIMUSettings *settings) { if (m_debug) { HAL_INFO("\n------\n"); HAL_INFO2("IMU update delta time: %f, sample %d\n", m_timeDelta, m_sampleNumber++); } m_sampleNumber++; if (m_enableGyro) m_gyro = data.gyro; else m_gyro = RTVector3(); m_accel = data.accel; m_compass = data.compass; m_compassValid = data.compassValid; if (m_firstTime) { m_lastFusionTime = data.timestamp; calculatePose(m_accel, m_compass, settings->m_compassAdjDeclination); // initialize the poses m_stateQ.fromEuler(m_measuredPose); m_fusionQPose = m_stateQ; m_fusionPose = m_measuredPose; m_firstTime = false; } else { m_timeDelta = (RTFLOAT)(data.timestamp - m_lastFusionTime) / (RTFLOAT)1000000; m_lastFusionTime = data.timestamp; if (m_timeDelta <= 0) return; calculatePose(data.accel, data.compass, settings->m_compassAdjDeclination); predict(); update(); m_stateQ.toEuler(m_fusionPose); m_fusionQPose = m_stateQ; if (m_debug) { HAL_INFO(RTMath::displayRadians("Measured pose", m_measuredPose)); HAL_INFO(RTMath::displayRadians("RTQF pose", m_fusionPose)); HAL_INFO(RTMath::displayRadians("Measured quat", m_measuredPose)); HAL_INFO(RTMath::display("RTQF quat", m_stateQ)); HAL_INFO(RTMath::display("Error quat", m_stateQError)); } } data.fusionPoseValid = true; data.fusionQPoseValid = true; data.fusionPose = m_fusionPose; data.fusionQPose = m_fusionQPose; } rtimulib-7.2.1/RTIMULib/RTFusionRTQF.h000066400000000000000000000042631254201074400172620ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTFUSIONRTQF_H #define _RTFUSIONRTQF_H #include "RTFusion.h" class RTFusionRTQF : public RTFusion { public: RTFusionRTQF(); ~RTFusionRTQF(); // fusionType returns the type code of the fusion algorithm virtual int fusionType() { return RTFUSION_TYPE_RTQF; } // reset() resets the state but keeps any setting changes (such as enables) void reset(); // newIMUData() should be called for subsequent updates // deltaTime is in units of seconds void newIMUData(RTIMU_DATA& data, const RTIMUSettings *settings); private: void predict(); void update(); RTVector3 m_gyro; // unbiased gyro data RTFLOAT m_timeDelta; // time between predictions RTQuaternion m_stateQ; // quaternion state vector RTQuaternion m_stateQError; // difference between stateQ and measuredQ int m_sampleNumber; }; #endif // _RTFUSIONRTQF_H rtimulib-7.2.1/RTIMULib/RTIMUAccelCal.cpp000066400000000000000000000064141254201074400176570ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTIMUAccelCal.h" // ACCEL_ALPHA control the smoothing - the lower it is, the smoother it is #define ACCEL_ALPHA 0.1f RTIMUAccelCal::RTIMUAccelCal(RTIMUSettings *settings) { m_settings = settings; for (int i = 0; i < 3; i++) m_accelCalEnable[i] = false; } RTIMUAccelCal::~RTIMUAccelCal() { } void RTIMUAccelCal::accelCalInit() { if (m_settings->m_accelCalValid) { m_accelMin = m_settings->m_accelCalMin; m_accelMax = m_settings->m_accelCalMax; } else { m_accelMin = RTVector3(RTIMUCALDEFS_DEFAULT_MIN, RTIMUCALDEFS_DEFAULT_MIN, RTIMUCALDEFS_DEFAULT_MIN); m_accelMax = RTVector3(RTIMUCALDEFS_DEFAULT_MAX, RTIMUCALDEFS_DEFAULT_MAX, RTIMUCALDEFS_DEFAULT_MAX); } } void RTIMUAccelCal::accelCalReset() { for (int i = 0; i < 3; i++) { if (m_accelCalEnable[i]) { m_accelMin.setData(i, RTIMUCALDEFS_DEFAULT_MIN); m_accelMax.setData(i, RTIMUCALDEFS_DEFAULT_MAX); } } } void RTIMUAccelCal::accelCalEnable(int axis, bool enable) { m_accelCalEnable[axis] = enable; } void RTIMUAccelCal::newAccelCalData(const RTVector3& data) { for (int i = 0; i < 3; i++) { if (m_accelCalEnable[i]) { m_averageValue.setData(i, (data.data(i) * ACCEL_ALPHA + m_averageValue.data(i) * (1.0 - ACCEL_ALPHA))); if (m_accelMin.data(i) > m_averageValue.data(i)) m_accelMin.setData(i, m_averageValue.data(i)); if (m_accelMax.data(i) < m_averageValue.data(i)) m_accelMax.setData(i, m_averageValue.data(i)); } } } bool RTIMUAccelCal::accelCalValid() { bool valid = true; for (int i = 0; i < 3; i++) { if (m_accelMax.data(i) < m_accelMin.data(i)) valid = false; } return valid; } bool RTIMUAccelCal::accelCalSave() { if (!accelCalValid()) return false; m_settings->m_accelCalValid = true; m_settings->m_accelCalMin = m_accelMin; m_settings->m_accelCalMax = m_accelMax; m_settings->saveSettings(); return true; } rtimulib-7.2.1/RTIMULib/RTIMUAccelCal.h000066400000000000000000000057171254201074400173310ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTIMUACCELCAL_H #define _RTIMUACCELCAL_H #include "RTIMUCalDefs.h" #include "RTIMULib.h" // RTIMUAccelCal is a helper class for performing accelerometer calibration class RTIMUAccelCal { public: RTIMUAccelCal(RTIMUSettings *settings); virtual ~RTIMUAccelCal(); // This should be called at the start of the calibration process // Loads previous values if available void accelCalInit(); // This should be called to clear enabled axes for a new run void accelCalReset(); // accelCalEnable() controls which axes are active - largely so that each can be done separately void accelCalEnable(int axis, bool enable); // newAccalCalData() adds a new sample for processing but only the axes enabled previously void newAccelCalData(const RTVector3& data); // adds a new accel sample // accelCalValid() checks if all values are reasonable. Should be called before saving bool accelCalValid(); // accelCalSave() should be called at the end of the process to save the cal data // to the settings file. Returns false if invalid data bool accelCalSave(); // saves the accel cal data for specified axes // these vars used during the calibration process bool m_accelCalValid; // true if the mag min/max data valid RTVector3 m_accelMin; // the min values RTVector3 m_accelMax; // the max values RTVector3 m_averageValue; // averaged value actually used bool m_accelCalEnable[3]; // the enable flags RTIMUSettings *m_settings; }; #endif // _RTIMUACCELCAL_H rtimulib-7.2.1/RTIMULib/RTIMUCalDefs.h000066400000000000000000000056501254201074400171770ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef RTIMUCALDEFS_H #define RTIMUCALDEFS_H #define RTIMUCALDEFS_DEFAULT_MIN 1000 // a large min #define RTIMUCALDEFS_DEFAULT_MAX -1000 // a small max #define RTIMUCALDEFS_MAX_MAG_SAMPLES 20000 // max saved mag records #define RTIMUCALDEFS_OCTANT_MIN_SAMPLES 200 // must have at least this in each octant #define RTIMUCALDEFS_ELLIPSOID_MIN_SPACING 0.1f // min distnace between ellipsoid samples to be recorded // Octant defs #define RTIMUCALDEFS_OCTANT_COUNT 8 // there are 8 octants of course #define RTIMUCALDEFS_OCTANT_NNN 0 // x, y, z all negative #define RTIMUCALDEFS_OCTANT_PNN 1 // x positive - y, z neagtive #define RTIMUCALDEFS_OCTANT_NPN 2 // y positive - x, z negative #define RTIMUCALDEFS_OCTANT_PPN 3 // x, y positive - z negative #define RTIMUCALDEFS_OCTANT_NNP 4 // z positive - x, y negative #define RTIMUCALDEFS_OCTANT_PNP 5 // x, z positive - y negative #define RTIMUCALDEFS_OCTANT_NPP 6 // y, z positive - x negative #define RTIMUCALDEFS_OCTANT_PPP 7 // x, y, z all positive // File name for Octave processing #define RTIMUCALDEFS_MAG_RAW_FILE "magRaw.dta" // the raw sample file - input to ellispoid fit code #define RTIMUCALDEFS_MAG_CORR_FILE "magCorr.dta" // the output from the ellipsoid fit code #define RTIMUCALDEFS_OCTAVE_CODE "RTEllipsoidFit.m" #define RTIMUCALDEFS_OCTAVE_COMMAND "octave RTEllipsoidFit.m" #endif // RTIMUCALDEFS_H rtimulib-7.2.1/RTIMULib/RTIMUHal.cpp000066400000000000000000000246401254201074400167350ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. // The MPU-9250 and SPI driver code is based on code generously supplied by // staslock@gmail.com (www.clickdrive.io) #include "IMUDrivers/RTIMU.h" #if !defined(WIN32) && !defined(__APPLE__) #include RTIMUHal::RTIMUHal() { m_I2CBus = 255; m_currentSlave = 255; m_I2C = -1; m_SPI = -1; m_SPISpeed = 500000; } RTIMUHal::~RTIMUHal() { HALClose(); } bool RTIMUHal::HALOpen() { char buf[32]; unsigned char SPIMode = SPI_MODE_0; unsigned char SPIBits = 8; uint32_t SPISpeed = m_SPISpeed; if (m_busIsI2C) { if (m_I2C >= 0) return true; if (m_I2CBus == 255) { HAL_ERROR("No I2C bus has been set\n"); return false; } sprintf(buf, "/dev/i2c-%d", m_I2CBus); m_I2C = open(buf, O_RDWR); if (m_I2C < 0) { HAL_ERROR1("Failed to open I2C bus %d\n", m_I2CBus); m_I2C = -1; return false; } } else { if (m_SPIBus == 255) { HAL_ERROR("No SPI bus has been set\n"); return false; } sprintf(buf, "/dev/spidev%d.%d", m_SPIBus, m_SPISelect); m_SPI = open(buf, O_RDWR); if (m_SPI < 0) { HAL_ERROR2("Failed to open SPI bus %d, select %d\n", m_SPIBus, m_SPISelect); m_SPI = -1; return false; } if (ioctl(m_SPI, SPI_IOC_WR_MODE, &SPIMode) < 0) { HAL_ERROR1("Failed to set WR SPI_MODE0 on bus %d", m_SPIBus); close(m_SPIBus); return false; } if (ioctl(m_SPI, SPI_IOC_RD_MODE, &SPIMode) < 0) { HAL_ERROR1("Failed to set RD SPI_MODE0 on bus %d", m_SPIBus); close(m_SPIBus); return false; } if (ioctl(m_SPI, SPI_IOC_WR_BITS_PER_WORD, &SPIBits) < 0) { HAL_ERROR1("Failed to set WR 8 bit mode on bus %d", m_SPIBus); close(m_SPIBus); return false; } if (ioctl(m_SPI, SPI_IOC_RD_BITS_PER_WORD, &SPIBits) < 0) { HAL_ERROR1("Failed to set RD 8 bit mode on bus %d", m_SPIBus); close(m_SPIBus); return false; } if (ioctl(m_SPI, SPI_IOC_WR_MAX_SPEED_HZ, &SPISpeed) < 0) { HAL_ERROR2("Failed to set WR %dHz on bus %d", SPISpeed, m_SPIBus); close(m_SPIBus); return false; } if (ioctl(m_SPI, SPI_IOC_RD_MAX_SPEED_HZ, &SPISpeed) < 0) { HAL_ERROR2("Failed to set RD %dHz on bus %d", SPISpeed, m_SPIBus); close(m_SPIBus); return false; } } return true; } void RTIMUHal::HALClose() { I2CClose(); SPIClose(); } void RTIMUHal::I2CClose() { if (m_I2C >= 0) { close(m_I2C); m_I2C = -1; m_currentSlave = 255; } } void RTIMUHal::SPIClose() { if (m_SPI >= 0) { close(m_SPI); m_SPI = -1; } } bool RTIMUHal::HALWrite(unsigned char slaveAddr, unsigned char regAddr, unsigned char const data, const char *errorMsg) { return HALWrite(slaveAddr, regAddr, 1, &data, errorMsg); } bool RTIMUHal::HALWrite(unsigned char slaveAddr, unsigned char regAddr, unsigned char length, unsigned char const *data, const char *errorMsg) { int result; unsigned char txBuff[MAX_WRITE_LEN + 1]; char *ifType; if (m_busIsI2C) { if (!I2CSelectSlave(slaveAddr, errorMsg)) return false; ifType = (char *)"I2C"; } else { ifType = (char *)"SPI"; } if (length == 0) { result = ifWrite(®Addr, 1); if (result < 0) { if (strlen(errorMsg) > 0) HAL_ERROR2("%s write of regAddr failed - %s\n", ifType, errorMsg); return false; } else if (result != 1) { if (strlen(errorMsg) > 0) HAL_ERROR2("%s write of regAddr failed (nothing written) - %s\n", ifType, errorMsg); return false; } } else { txBuff[0] = regAddr; memcpy(txBuff + 1, data, length); result = ifWrite(txBuff, length + 1); if (result < 0) { if (strlen(errorMsg) > 0) HAL_ERROR3("%s data write of %d bytes failed - %s\n", ifType, length, errorMsg); return false; } else if (result < (int)length) { if (strlen(errorMsg) > 0) HAL_ERROR4("%s data write of %d bytes failed, only %d written - %s\n", ifType, length, result, errorMsg); return false; } } return true; } bool RTIMUHal::ifWrite(unsigned char *data, unsigned char length) { struct spi_ioc_transfer wrIOC; if (m_busIsI2C) { return write(m_I2C, data, length); } else { memset(&wrIOC, 0, sizeof(wrIOC)); wrIOC.tx_buf = (unsigned long) data; wrIOC.rx_buf = 0; wrIOC.len = length; return ioctl(m_SPI, SPI_IOC_MESSAGE(1), &wrIOC); } } bool RTIMUHal::HALRead(unsigned char slaveAddr, unsigned char regAddr, unsigned char length, unsigned char *data, const char *errorMsg) { int tries, result, total; unsigned char rxBuff[MAX_READ_LEN + 1]; struct spi_ioc_transfer rdIOC; if (m_busIsI2C) { if (!HALWrite(slaveAddr, regAddr, 0, NULL, errorMsg)) return false; total = 0; tries = 0; while ((total < length) && (tries < 5)) { result = read(m_I2C, data + total, length - total); if (result < 0) { if (strlen(errorMsg) > 0) HAL_ERROR3("I2C read error from %d, %d - %s\n", slaveAddr, regAddr, errorMsg); return false; } total += result; if (total == length) break; delayMs(10); tries++; } if (total < length) { if (strlen(errorMsg) > 0) HAL_ERROR3("I2C read from %d, %d failed - %s\n", slaveAddr, regAddr, errorMsg); return false; } } else { rxBuff[0] = regAddr | 0x80; memcpy(rxBuff + 1, data, length); memset(&rdIOC, 0, sizeof(rdIOC)); rdIOC.tx_buf = (unsigned long) rxBuff; rdIOC.rx_buf = (unsigned long) rxBuff; rdIOC.len = length + 1; if (ioctl(m_SPI, SPI_IOC_MESSAGE(1), &rdIOC) < 0) { if (strlen(errorMsg) > 0) HAL_ERROR2("SPI read error from %d - %s\n", regAddr, errorMsg); return false; } memcpy(data, rxBuff + 1, length); } return true; } bool RTIMUHal::HALRead(unsigned char slaveAddr, unsigned char length, unsigned char *data, const char *errorMsg) { int tries, result, total; unsigned char rxBuff[MAX_READ_LEN + 1]; struct spi_ioc_transfer rdIOC; if (m_busIsI2C) { if (!I2CSelectSlave(slaveAddr, errorMsg)) return false; total = 0; tries = 0; while ((total < length) && (tries < 5)) { result = read(m_I2C, data + total, length - total); if (result < 0) { if (strlen(errorMsg) > 0) HAL_ERROR2("I2C read error from %d - %s\n", slaveAddr, errorMsg); return false; } total += result; if (total == length) break; delayMs(10); tries++; } if (total < length) { if (strlen(errorMsg) > 0) HAL_ERROR2("I2C read from %d failed - %s\n", slaveAddr, errorMsg); return false; } } else { memset(&rdIOC, 0, sizeof(rdIOC)); rdIOC.tx_buf = 0; rdIOC.rx_buf = (unsigned long) rxBuff; rdIOC.len = length; if (ioctl(m_SPI, SPI_IOC_MESSAGE(1), &rdIOC) < 0) { if (strlen(errorMsg) > 0) HAL_ERROR1("SPI read error from - %s\n", errorMsg); return false; } memcpy(data, rxBuff, length); } return true; } bool RTIMUHal::I2CSelectSlave(unsigned char slaveAddr, const char *errorMsg) { if (m_currentSlave == slaveAddr) return true; if (!HALOpen()) { HAL_ERROR1("Failed to open I2C port - %s\n", errorMsg); return false; } if (ioctl(m_I2C, I2C_SLAVE, slaveAddr) < 0) { HAL_ERROR2("I2C slave select %d failed - %s\n", slaveAddr, errorMsg); return false; } m_currentSlave = slaveAddr; return true; } void RTIMUHal::delayMs(int milliSeconds) { usleep(1000 * milliSeconds); } #else // just dummy routines for Windows RTIMUHal::RTIMUHal() { } RTIMUHal::~RTIMUHal() { } bool RTIMUHal::HALOpen() { return true; } void RTIMUHal::HALClose() { } void RTIMUHal::I2CClose() { } bool RTIMUHal::HALWrite(unsigned char , unsigned char , unsigned char const , const char *) { return true; } bool RTIMUHal::HALWrite(unsigned char , unsigned char , unsigned char , unsigned char const *, const char *) { return true; } bool RTIMUHal::HALRead(unsigned char , unsigned char , unsigned char , unsigned char *, const char *) { return true; } bool RTIMUHal::I2CSelectSlave(unsigned char , const char *) { return true; } void RTIMUHal::delayMs(int milliSeconds) { } #endif rtimulib-7.2.1/RTIMULib/RTIMUHal.h000066400000000000000000000104711254201074400163770ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. // The MPU-9250 and SPI driver code is based on code generously supplied by // staslock@gmail.com (www.clickdrive.io) #ifndef _RTIMUHAL_H #define _RTIMUHAL_H #include #include #include #include #include #include #ifndef HAL_QUIET #define HAL_INFO(m) { printf("%s", m); fflush(stdout); } #define HAL_INFO1(m, x) { printf(m, x); fflush(stdout); } #define HAL_INFO2(m, x, y) { printf(m, x, y); fflush(stdout); } #define HAL_INFO3(m, x, y, z) { printf(m, x, y, z); fflush(stdout); } #define HAL_INFO4(m, x, y, z, a) { printf(m, x, y, z, a); fflush(stdout); } #define HAL_INFO5(m, x, y, z, a, b) { printf(m, x, y, z, a, b); fflush(stdout); } #define HAL_ERROR(m) fprintf(stderr, m); #define HAL_ERROR1(m, x) fprintf(stderr, m, x); #define HAL_ERROR2(m, x, y) fprintf(stderr, m, x, y); #define HAL_ERROR3(m, x, y, z) fprintf(stderr, m, x, y, z); #define HAL_ERROR4(m, x, y, z, a) fprintf(stderr, m, x, y, z, a); #else #define HAL_INFO(m) #define HAL_INFO1(m, x) #define HAL_INFO2(m, x, y) #define HAL_INFO3(m, x, y, z) #define HAL_INFO4(m, x, y, z, a) #define HAL_INFO5(m, x, y, z, a, b) #define HAL_ERROR(m) #define HAL_ERROR1(m, x) #define HAL_ERROR2(m, x, y) #define HAL_ERROR3(m, x, y, z) #define HAL_ERROR4(m, x, y, z, a) #endif #if !defined(WIN32) && !defined(__APPLE__) #include #include #endif #if !defined(WIN32) #include #include #endif #define MAX_WRITE_LEN 255 #define MAX_READ_LEN 255 class RTIMUHal { public: RTIMUHal(); virtual ~RTIMUHal(); bool m_busIsI2C; // true if I2C bus in use, false if SPI in use unsigned char m_I2CBus; // I2C bus of the imu (eg 1 for Raspberry Pi usually) unsigned char m_SPIBus; // SPI bus of the imu (eg 0 for Raspberry Pi usually) unsigned char m_SPISelect; // SPI select line - defaults to CE0 unsigned int m_SPISpeed; // speed of interface bool HALOpen(); void HALClose(); bool HALRead(unsigned char slaveAddr, unsigned char regAddr, unsigned char length, unsigned char *data, const char *errorMsg); // normal read with register select bool HALRead(unsigned char slaveAddr, unsigned char length, unsigned char *data, const char *errorMsg); // read without register select bool HALWrite(unsigned char slaveAddr, unsigned char regAddr, unsigned char length, unsigned char const *data, const char *errorMsg); bool HALWrite(unsigned char slaveAddr, unsigned char regAddr, unsigned char const data, const char *errorMsg); void delayMs(int milliSeconds); protected: void I2CClose(); bool I2CSelectSlave(unsigned char slaveAddr, const char *errorMsg); void SPIClose(); bool ifWrite(unsigned char *data, unsigned char length); private: int m_I2C; unsigned char m_currentSlave; int m_SPI; }; #endif // _RTIMUHAL_H rtimulib-7.2.1/RTIMULib/RTIMULIB LICENSE000066400000000000000000000025361254201074400171210ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. // The MPU-9250 and SPI driver code is based on code generously supplied by // staslock@gmail.com (www.clickdrive.io) rtimulib-7.2.1/RTIMULib/RTIMULib.h000066400000000000000000000036041254201074400164010ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTIMULIB_H #define _RTIMULIB_H #include "RTIMULibDefs.h" #include "RTMath.h" #include "RTFusion.h" #include "RTFusionKalman4.h" #include "RTIMUHal.h" #include "IMUDrivers/RTIMU.h" #include "IMUDrivers/RTIMUNull.h" #include "IMUDrivers/RTIMUMPU9150.h" #include "IMUDrivers/RTIMUGD20HM303D.h" #include "IMUDrivers/RTIMUGD20M303DLHC.h" #include "IMUDrivers/RTIMULSM9DS0.h" #include "IMUDrivers/RTPressure.h" #include "IMUDrivers/RTPressureBMP180.h" #include "IMUDrivers/RTPressureLPS25H.h" #include "IMUDrivers/RTPressureMS5611.h" #include "IMUDrivers/RTHumidity.h" #include "IMUDrivers/RTHumidityHTS221.h" #include "RTIMUSettings.h" #endif // _RTIMULIB_H rtimulib-7.2.1/RTIMULib/RTIMULib.pri000066400000000000000000000066301254201074400167460ustar00rootroot00000000000000#//////////////////////////////////////////////////////////////////////////// #// #// This file is part of RTIMULib #// #// Copyright (c) 2014-2015, richards-tech, LLC #// #// Permission 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. INCLUDEPATH += $$PWD DEPENDPATH += $$PWD HEADERS += $$PWD/RTIMULib.h \ $$PWD/RTIMULibDefs.h \ $$PWD/RTMath.h \ $$PWD/RTIMUHal.h \ $$PWD/RTFusion.h \ $$PWD/RTFusionKalman4.h \ $$PWD/RTFusionRTQF.h \ $$PWD/RTIMUSettings.h \ $$PWD/RTIMUMagCal.h \ $$PWD/RTIMUAccelCal.h \ $$PWD/RTIMUCalDefs.h \ $$PWD/IMUDrivers/RTIMU.h \ $$PWD/IMUDrivers/RTIMUDefs.h \ $$PWD/IMUDrivers/RTIMUMPU9150.h \ $$PWD/IMUDrivers/RTIMUMPU9250.h \ $$PWD/IMUDrivers/RTIMUGD20HM303D.h \ $$PWD/IMUDrivers/RTIMUGD20M303DLHC.h \ $$PWD/IMUDrivers/RTIMUGD20HM303DLHC.h \ $$PWD/IMUDrivers/RTIMULSM9DS0.h \ $$PWD/IMUDrivers/RTIMULSM9DS1.h \ $$PWD/IMUDrivers/RTIMUBMX055.h \ $$PWD/IMUDrivers/RTIMUBNO055.h \ $$PWD/IMUDrivers/RTIMUNull.h \ $$PWD/IMUDrivers/RTPressure.h \ $$PWD/IMUDrivers/RTPressureDefs.h \ $$PWD/IMUDrivers/RTPressureBMP180.h \ $$PWD/IMUDrivers/RTPressureLPS25H.h \ $$PWD/IMUDrivers/RTPressureMS5611.h \ $$PWD/IMUDrivers/RTPressureMS5637.h \ $$PWD/IMUDrivers/RTHumidity.h \ $$PWD/IMUDrivers/RTHumidityDefs.h \ $$PWD/IMUDrivers/RTHumidityHTS221.h \ $$PWD/IMUDrivers/RTHumidityHTU21D.h \ SOURCES += $$PWD/RTMath.cpp \ $$PWD/RTIMUHal.cpp \ $$PWD/RTFusion.cpp \ $$PWD/RTFusionKalman4.cpp \ $$PWD/RTFusionRTQF.cpp \ $$PWD/RTIMUSettings.cpp \ $$PWD/RTIMUMagCal.cpp \ $$PWD/RTIMUAccelCal.cpp \ $$PWD/IMUDrivers/RTIMU.cpp \ $$PWD/IMUDrivers/RTIMUMPU9150.cpp \ $$PWD/IMUDrivers/RTIMUMPU9250.cpp \ $$PWD/IMUDrivers/RTIMUGD20HM303D.cpp \ $$PWD/IMUDrivers/RTIMUGD20M303DLHC.cpp \ $$PWD/IMUDrivers/RTIMUGD20HM303DLHC.cpp \ $$PWD/IMUDrivers/RTIMULSM9DS0.cpp \ $$PWD/IMUDrivers/RTIMULSM9DS1.cpp \ $$PWD/IMUDrivers/RTIMUBMX055.cpp \ $$PWD/IMUDrivers/RTIMUBNO055.cpp \ $$PWD/IMUDrivers/RTIMUNull.cpp \ $$PWD/IMUDrivers/RTPressure.cpp \ $$PWD/IMUDrivers/RTPressureBMP180.cpp \ $$PWD/IMUDrivers/RTPressureLPS25H.cpp \ $$PWD/IMUDrivers/RTPressureMS5611.cpp \ $$PWD/IMUDrivers/RTPressureMS5637.cpp \ $$PWD/IMUDrivers/RTHumidity.cpp \ $$PWD/IMUDrivers/RTHumidityHTS221.cpp \ $$PWD/IMUDrivers/RTHumidityHTU21D.cpp \ rtimulib-7.2.1/RTIMULib/RTIMULibDefs.h000066400000000000000000000046701254201074400172070ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. // The MPU-9250 and SPI driver code is based on code generously supplied by // staslock@gmail.com (www.clickdrive.io) #ifndef _RTIMULIBDEFS_H #define _RTIMULIBDEFS_H #include "RTMath.h" #include "IMUDrivers/RTIMUDefs.h" // these defines describe the various fusion filter options #define RTFUSION_TYPE_NULL 0 // just a dummy to keep things happy if not needed #define RTFUSION_TYPE_KALMANSTATE4 1 // kalman state is the quaternion pose #define RTFUSION_TYPE_RTQF 2 // RT quaternion fusion #define RTFUSION_TYPE_COUNT 3 // number of fusion algorithm types // This is a convenience structure that can be used to pass IMU data around typedef struct { uint64_t timestamp; bool fusionPoseValid; RTVector3 fusionPose; bool fusionQPoseValid; RTQuaternion fusionQPose; bool gyroValid; RTVector3 gyro; bool accelValid; RTVector3 accel; bool compassValid; RTVector3 compass; bool pressureValid; RTFLOAT pressure; bool temperatureValid; RTFLOAT temperature; bool humidityValid; RTFLOAT humidity; } RTIMU_DATA; #endif // _RTIMULIBDEFS_H rtimulib-7.2.1/RTIMULib/RTIMUMagCal.cpp000066400000000000000000000173111254201074400173520ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTIMUMagCal.h" RTIMUMagCal::RTIMUMagCal(RTIMUSettings *settings) { m_settings = settings; } RTIMUMagCal::~RTIMUMagCal() { } void RTIMUMagCal::magCalInit() { magCalReset(); } void RTIMUMagCal::magCalReset() { m_magMin = RTVector3(RTIMUCALDEFS_DEFAULT_MIN, RTIMUCALDEFS_DEFAULT_MIN, RTIMUCALDEFS_DEFAULT_MIN); m_magMax = RTVector3(RTIMUCALDEFS_DEFAULT_MAX, RTIMUCALDEFS_DEFAULT_MAX, RTIMUCALDEFS_DEFAULT_MAX); m_magCalCount = 0; for (int i = 0; i < RTIMUCALDEFS_OCTANT_COUNT; i++) m_octantCounts[i] = 0; m_magCalInIndex = m_magCalOutIndex = 0; // throw away first few samples so we don't see any old calibrated samples m_startCount = 100; } void RTIMUMagCal::newMinMaxData(const RTVector3& data) { if (m_startCount > 0) { m_startCount--; return; } for (int i = 0; i < 3; i++) { if (m_magMin.data(i) > data.data(i)) { m_magMin.setData(i, data.data(i)); } if (m_magMax.data(i) < data.data(i)) { m_magMax.setData(i, data.data(i)); } } } bool RTIMUMagCal::magCalValid() { bool valid = true; for (int i = 0; i < 3; i++) { if (m_magMax.data(i) < m_magMin.data(i)) valid = false; } return valid; } void RTIMUMagCal::magCalSaveMinMax() { m_settings->m_compassCalValid = true; m_settings->m_compassCalMin = m_magMin; m_settings->m_compassCalMax = m_magMax; m_settings->m_compassCalEllipsoidValid = false; m_settings->saveSettings(); // need to invalidate ellipsoid data in order to use new min/max data m_magCalCount = 0; for (int i = 0; i < RTIMUCALDEFS_OCTANT_COUNT; i++) m_octantCounts[i] = 0; m_magCalInIndex = m_magCalOutIndex = 0; // and set up for min/max calibration setMinMaxCal(); } void RTIMUMagCal::newEllipsoidData(const RTVector3& data) { RTVector3 calData; // do min/max calibration first for (int i = 0; i < 3; i++) calData.setData(i, (data.data(i) - m_minMaxOffset.data(i)) * m_minMaxScale.data(i)); // now see if it's already there - we want them all unique and slightly separate (using a fuzzy compare) for (int index = m_magCalOutIndex, i = 0; i < m_magCalCount; i++) { if ((abs(calData.x() - m_magCalSamples[index].x()) < RTIMUCALDEFS_ELLIPSOID_MIN_SPACING) && (abs(calData.y() - m_magCalSamples[index].y()) < RTIMUCALDEFS_ELLIPSOID_MIN_SPACING) && (abs(calData.z() - m_magCalSamples[index].z()) < RTIMUCALDEFS_ELLIPSOID_MIN_SPACING)) { return; // too close to another sample } if (++index == RTIMUCALDEFS_MAX_MAG_SAMPLES) index = 0; } m_octantCounts[findOctant(calData)]++; m_magCalSamples[m_magCalInIndex++] = calData; if (m_magCalInIndex == RTIMUCALDEFS_MAX_MAG_SAMPLES) m_magCalInIndex = 0; if (++m_magCalCount == RTIMUCALDEFS_MAX_MAG_SAMPLES) { // buffer is full - pull oldest removeMagCalData(); } } bool RTIMUMagCal::magCalEllipsoidValid() { bool valid = true; for (int i = 0; i < RTIMUCALDEFS_OCTANT_COUNT; i++) { if (m_octantCounts[i] < RTIMUCALDEFS_OCTANT_MIN_SAMPLES) valid = false; } return valid; } RTVector3 RTIMUMagCal::removeMagCalData() { RTVector3 ret; if (m_magCalCount == 0) return ret; ret = m_magCalSamples[m_magCalOutIndex++]; if (m_magCalOutIndex == RTIMUCALDEFS_MAX_MAG_SAMPLES) m_magCalOutIndex = 0; m_magCalCount--; m_octantCounts[findOctant(ret)]--; return ret; } bool RTIMUMagCal::magCalSaveRaw(const char *ellipsoidFitPath) { FILE *file; char *rawFile; if (ellipsoidFitPath != NULL) { // need to deal with ellipsoid fit processing rawFile = (char *)malloc(strlen(RTIMUCALDEFS_MAG_RAW_FILE) + strlen(ellipsoidFitPath) + 2); sprintf(rawFile, "%s/%s", ellipsoidFitPath, RTIMUCALDEFS_MAG_RAW_FILE); if ((file = fopen(rawFile, "w")) == NULL) { HAL_ERROR("Failed to open ellipsoid fit raw data file\n"); return false; } while (m_magCalCount > 0) { RTVector3 sample = removeMagCalData(); fprintf(file, "%f %f %f\n", sample.x(), sample.y(), sample.z()); } fclose(file); } return true; } bool RTIMUMagCal::magCalSaveCorr(const char *ellipsoidFitPath) { FILE *file; char *corrFile; float a[3]; float b[9]; if (ellipsoidFitPath != NULL) { corrFile = (char *)malloc(strlen(RTIMUCALDEFS_MAG_CORR_FILE) + strlen(ellipsoidFitPath) + 2); sprintf(corrFile, "%s/%s", ellipsoidFitPath, RTIMUCALDEFS_MAG_CORR_FILE); if ((file = fopen(corrFile, "r")) == NULL) { HAL_ERROR("Failed to open ellipsoid fit correction data file\n"); return false; } if (fscanf(file, "%f %f %f %f %f %f %f %f %f %f %f %f", a + 0, a + 1, a + 2, b + 0, b + 1, b + 2, b + 3, b + 4, b + 5, b + 6, b + 7, b + 8) != 12) { HAL_ERROR("Ellipsoid corrcetion file didn't have 12 floats\n"); fclose(file); return false; } fclose(file); m_settings->m_compassCalEllipsoidValid = true; m_settings->m_compassCalEllipsoidOffset = RTVector3(a[0], a[1], a[2]); memcpy(m_settings->m_compassCalEllipsoidCorr, b, 9 * sizeof(float)); m_settings->saveSettings(); return true; } return false; } void RTIMUMagCal::magCalOctantCounts(int *counts) { memcpy(counts, m_octantCounts, RTIMUCALDEFS_OCTANT_COUNT * sizeof(int)); } int RTIMUMagCal::findOctant(const RTVector3& data) { int val = 0; if (data.x() >= 0) val = 1; if (data.y() >= 0) val |= 2; if (data.z() >= 0) val |= 4; return val; } void RTIMUMagCal::setMinMaxCal() { float maxDelta = -1; float delta; // find biggest range for (int i = 0; i < 3; i++) { if ((m_magMax.data(i) - m_magMin.data(i)) > maxDelta) maxDelta = m_magMax.data(i) - m_magMin.data(i); } if (maxDelta < 0) { HAL_ERROR("Error in min/max calibration data\n"); return; } maxDelta /= 2.0f; // this is the max +/- range for (int i = 0; i < 3; i++) { delta = (m_magMax.data(i) -m_magMin.data(i)) / 2.0f; m_minMaxScale.setData(i, maxDelta / delta); // makes everything the same range m_minMaxOffset.setData(i, (m_magMax.data(i) + m_magMin.data(i)) / 2.0f); } } rtimulib-7.2.1/RTIMULib/RTIMUMagCal.h000066400000000000000000000100231254201074400170100ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTIMUMAGCAL_H #define _RTIMUMAGCAL_H #include "RTIMUCalDefs.h" #include "RTIMULib.h" class RTIMUMagCal { public: RTIMUMagCal(RTIMUSettings *settings); virtual ~RTIMUMagCal(); void magCalInit(); // inits everything void magCalReset(); // clears everything // newMinMaxData() is used to submit a new sample for min/max processing void newMinMaxData(const RTVector3& data); // newEllipsoidData is used to save data to the ellipsoid sample array void newEllipsoidData(const RTVector3& data); // magCalValid() determines if the min/max data is basically valid bool magCalValid(); // magCalEllipsoidValid() determines if enough samples have been collected for a valid ellipsoid fit bool magCalEllipsoidValid(); // magCalSaveMinMax() saves the current min/max values to settings void magCalSaveMinMax(); // magCalSaveRaw saves the ellipsoid fit data and then // saves data to the .ini file. // // Returns true if everything worked correctly. bool magCalSaveRaw(const char *ellipsoidFitPath); // magCalSaveCorr loads the correction data from the ellipsoid fit program and saves it in the // .ini bool magCalSaveCorr(const char *ellipsoidFitPath); // magCalSaveEllipsoid retrieves the ellipsoid fit calibration data // and saves it in the .ini file. void magCalOctantCounts(int *counts); // returns a count for each of the 8 octants // these vars used during the calibration process RTVector3 m_magMin; // the min values RTVector3 m_magMax; // the max values RTIMUSettings *m_settings; private: RTVector3 removeMagCalData(); // takes an entry out of the buffer int findOctant(const RTVector3& data); // works out which octant the data is in void setMinMaxCal(); // get ready for the ellipsoid mode int m_startCount; // need to throw way first few samples RTVector3 m_magCalSamples[RTIMUCALDEFS_MAX_MAG_SAMPLES];// the saved samples for ellipsoid fit int m_magCalInIndex; // current in index into the data int m_magCalOutIndex; // current out index into the data int m_magCalCount; // how many samples in the buffer RTVector3 m_minMaxOffset; // the min/max calibration offset RTVector3 m_minMaxScale; // the min/max scale int m_octantCounts[RTIMUCALDEFS_OCTANT_COUNT]; // counts in each octant }; #endif // _RTIMUMAGCAL_H rtimulib-7.2.1/RTIMULib/RTIMUSettings.cpp000066400000000000000000001760151254201074400200350ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. // The MPU-9250 and SPI driver code is based on code generously supplied by // staslock@gmail.com (www.clickdrive.io) #include "RTIMUSettings.h" #include "IMUDrivers/RTIMUMPU9150.h" #include "IMUDrivers/RTIMUMPU9250.h" #include "IMUDrivers/RTIMUGD20HM303D.h" #include "IMUDrivers/RTIMUGD20M303DLHC.h" #include "IMUDrivers/RTIMUGD20HM303DLHC.h" #include "IMUDrivers/RTIMULSM9DS0.h" #include "IMUDrivers/RTIMULSM9DS1.h" #include "IMUDrivers/RTIMUBMX055.h" #include "IMUDrivers/RTPressureBMP180.h" #include "IMUDrivers/RTPressureLPS25H.h" #include "IMUDrivers/RTHumidityHTS221.h" #include "IMUDrivers/RTHumidityHTU21D.h" #define RATE_TIMER_INTERVAL 2 RTIMUSettings::RTIMUSettings(const char *productType) { if ((strlen(productType) > 200) || (strlen(productType) == 0)) { HAL_ERROR("Product name too long or null - using default\n"); strcpy(m_filename, "RTIMULib.ini"); } else { sprintf(m_filename, "%s.ini", productType); } loadSettings(); } RTIMUSettings::RTIMUSettings(const char *settingsDirectory, const char *productType) { if (((strlen(productType) + strlen(settingsDirectory)) > 200) || (strlen(productType) == 0)) { HAL_ERROR("Product name too long or null - using default\n"); strcpy(m_filename, "RTIMULib.ini"); } else { sprintf(m_filename, "%s/%s.ini", settingsDirectory, productType); } loadSettings(); } bool RTIMUSettings::discoverIMU(int& imuType, bool& busIsI2C, unsigned char& slaveAddress) { unsigned char result; unsigned char altResult; // auto detect on I2C bus m_busIsI2C = true; if (HALOpen()) { if (HALRead(MPU9150_ADDRESS0, MPU9150_WHO_AM_I, 1, &result, "")) { if (result == MPU9250_ID) { imuType = RTIMU_TYPE_MPU9250; slaveAddress = MPU9250_ADDRESS0; busIsI2C = true; HAL_INFO("Detected MPU9250 at standard address\n"); return true; } else if (result == MPU9150_ID) { imuType = RTIMU_TYPE_MPU9150; slaveAddress = MPU9150_ADDRESS0; busIsI2C = true; HAL_INFO("Detected MPU9150 at standard address\n"); return true; } } if (HALRead(MPU9150_ADDRESS1, MPU9150_WHO_AM_I, 1, &result, "")) { if (result == MPU9250_ID) { imuType = RTIMU_TYPE_MPU9250; slaveAddress = MPU9250_ADDRESS1; busIsI2C = true; HAL_INFO("Detected MPU9250 at option address\n"); return true; } else if (result == MPU9150_ID) { imuType = RTIMU_TYPE_MPU9150; slaveAddress = MPU9150_ADDRESS1; busIsI2C = true; HAL_INFO("Detected MPU9150 at option address\n"); return true; } } if (HALRead(L3GD20H_ADDRESS0, L3GD20H_WHO_AM_I, 1, &result, "")) { if (result == L3GD20H_ID) { if (HALRead(LSM303D_ADDRESS0, LSM303D_WHO_AM_I, 1, &altResult, "")) { if (altResult == LSM303D_ID) { imuType = RTIMU_TYPE_GD20HM303D; slaveAddress = L3GD20H_ADDRESS0; busIsI2C = true; HAL_INFO("Detected L3GD20H/LSM303D at standard/standard address\n"); return true; } } if (HALRead(LSM303D_ADDRESS1, LSM303D_WHO_AM_I, 1, &altResult, "")) { if (altResult == LSM303D_ID) { imuType = RTIMU_TYPE_GD20HM303D; slaveAddress = L3GD20H_ADDRESS0; busIsI2C = true; HAL_INFO("Detected L3GD20H/LSM303D at standard/option address\n"); return true; } } if (HALRead(LSM303DLHC_ACCEL_ADDRESS, LSM303DLHC_STATUS_A, 1, &altResult, "")) { imuType = RTIMU_TYPE_GD20HM303DLHC; slaveAddress = L3GD20H_ADDRESS0; busIsI2C = true; HAL_INFO("Detected L3GD20H/LSM303DLHC at standard/standard address\n"); return true; } } else if (result == LSM9DS0_GYRO_ID) { if (HALRead(LSM9DS0_ACCELMAG_ADDRESS0, LSM9DS0_WHO_AM_I, 1, &altResult, "")) { if (altResult == LSM9DS0_ACCELMAG_ID) { imuType = RTIMU_TYPE_LSM9DS0; slaveAddress = LSM9DS0_GYRO_ADDRESS0; busIsI2C = true; HAL_INFO("Detected LSM9DS0 at standard/standard address\n"); return true; } } if (HALRead(LSM9DS0_ACCELMAG_ADDRESS1, LSM9DS0_WHO_AM_I, 1, &altResult, "")) { if (altResult == LSM9DS0_ACCELMAG_ID) { imuType = RTIMU_TYPE_LSM9DS0; slaveAddress = LSM9DS0_GYRO_ADDRESS0; busIsI2C = true; HAL_INFO("Detected LSM9DS0 at standard/option address\n"); return true; } } } else if (result == LSM9DS1_ID) { if (HALRead(LSM9DS1_MAG_ADDRESS0, LSM9DS1_MAG_WHO_AM_I, 1, &altResult, "")) { if (altResult == LSM9DS1_MAG_ID) { imuType = RTIMU_TYPE_LSM9DS1; slaveAddress = LSM9DS1_ADDRESS0; busIsI2C = true; HAL_INFO("Detected LSM9DS1 at standard/standard address\n"); return true; } } if (HALRead(LSM9DS1_MAG_ADDRESS1, LSM9DS1_MAG_WHO_AM_I, 1, &altResult, "")) { if (altResult == LSM9DS1_MAG_ID) { imuType = RTIMU_TYPE_LSM9DS1; slaveAddress = LSM9DS1_ADDRESS0; busIsI2C = true; HAL_INFO("Detected LSM9DS1 at standard/option 1 address\n"); return true; } } if (HALRead(LSM9DS1_MAG_ADDRESS2, LSM9DS1_MAG_WHO_AM_I, 1, &altResult, "")) { if (altResult == LSM9DS1_MAG_ID) { imuType = RTIMU_TYPE_LSM9DS1; slaveAddress = LSM9DS1_ADDRESS0; busIsI2C = true; HAL_INFO("Detected LSM9DS1 at standard/option 2 address\n"); return true; } } if (HALRead(LSM9DS1_MAG_ADDRESS3, LSM9DS1_MAG_WHO_AM_I, 1, &altResult, "")) { if (altResult == LSM9DS1_MAG_ID) { imuType = RTIMU_TYPE_LSM9DS1; slaveAddress = LSM9DS1_ADDRESS0; busIsI2C = true; HAL_INFO("Detected LSM9DS1 at standard/option 3 address\n"); return true; } } } } if (HALRead(L3GD20H_ADDRESS1, L3GD20H_WHO_AM_I, 1, &result, "")) { if (result == L3GD20H_ID) { if (HALRead(LSM303D_ADDRESS1, LSM303D_WHO_AM_I, 1, &altResult, "")) { if (altResult == LSM303D_ID) { imuType = RTIMU_TYPE_GD20HM303D; slaveAddress = L3GD20H_ADDRESS1; busIsI2C = true; HAL_INFO("Detected L3GD20H/LSM303D at option/option address\n"); return true; } } if (HALRead(LSM303D_ADDRESS0, LSM303D_WHO_AM_I, 1, &altResult, "")) { if (altResult == LSM303D_ID) { imuType = RTIMU_TYPE_GD20HM303D; slaveAddress = L3GD20H_ADDRESS1; busIsI2C = true; HAL_INFO("Detected L3GD20H/LSM303D at option/standard address\n"); return true; } } if (HALRead(LSM303DLHC_ACCEL_ADDRESS, LSM303DLHC_STATUS_A, 1, &altResult, "")) { imuType = RTIMU_TYPE_GD20HM303DLHC; slaveAddress = L3GD20H_ADDRESS1; busIsI2C = true; HAL_INFO("Detected L3GD20H/LSM303DLHC at option/standard address\n"); return true; } } else if (result == LSM9DS0_GYRO_ID) { if (HALRead(LSM9DS0_ACCELMAG_ADDRESS1, LSM9DS0_WHO_AM_I, 1, &altResult, "")) { if (altResult == LSM9DS0_ACCELMAG_ID) { imuType = RTIMU_TYPE_LSM9DS0; slaveAddress = LSM9DS0_GYRO_ADDRESS1; busIsI2C = true; HAL_INFO("Detected LSM9DS0 at option/option address\n"); return true; } } if (HALRead(LSM9DS0_ACCELMAG_ADDRESS0, LSM9DS0_WHO_AM_I, 1, &altResult, "")) { if (altResult == LSM9DS0_ACCELMAG_ID) { imuType = RTIMU_TYPE_LSM9DS0; slaveAddress = LSM9DS0_GYRO_ADDRESS1; busIsI2C = true; HAL_INFO("Detected LSM9DS0 at option/standard address\n"); return true; } } } else if (result == LSM9DS1_ID) { if (HALRead(LSM9DS1_MAG_ADDRESS0, LSM9DS1_MAG_WHO_AM_I, 1, &altResult, "")) { if (altResult == LSM9DS1_MAG_ID) { imuType = RTIMU_TYPE_LSM9DS1; slaveAddress = LSM9DS1_ADDRESS1; busIsI2C = true; HAL_INFO("Detected LSM9DS1 at option/standard address\n"); return true; } } if (HALRead(LSM9DS1_MAG_ADDRESS1, LSM9DS1_MAG_WHO_AM_I, 1, &altResult, "")) { if (altResult == LSM9DS1_MAG_ID) { imuType = RTIMU_TYPE_LSM9DS1; slaveAddress = LSM9DS1_ADDRESS1; busIsI2C = true; HAL_INFO("Detected LSM9DS1 at option/option 1 address\n"); return true; } } if (HALRead(LSM9DS1_MAG_ADDRESS2, LSM9DS1_MAG_WHO_AM_I, 1, &altResult, "")) { if (altResult == LSM9DS1_MAG_ID) { imuType = RTIMU_TYPE_LSM9DS1; slaveAddress = LSM9DS1_ADDRESS1; busIsI2C = true; HAL_INFO("Detected LSM9DS1 at option/option 2 address\n"); return true; } } if (HALRead(LSM9DS1_MAG_ADDRESS3, LSM9DS1_MAG_WHO_AM_I, 1, &altResult, "")) { if (altResult == LSM9DS1_MAG_ID) { imuType = RTIMU_TYPE_LSM9DS1; slaveAddress = LSM9DS1_ADDRESS1; busIsI2C = true; HAL_INFO("Detected LSM9DS1 at option/option 3 address\n"); return true; } } } } if (HALRead(L3GD20_ADDRESS0, L3GD20_WHO_AM_I, 1, &result, "")) { if (result == L3GD20_ID) { imuType = RTIMU_TYPE_GD20M303DLHC; slaveAddress = L3GD20_ADDRESS0; busIsI2C = true; HAL_INFO("Detected L3GD20 at standard address\n"); return true; } } if (HALRead(L3GD20_ADDRESS1, L3GD20_WHO_AM_I, 1, &result, "")) { if (result == L3GD20_ID) { imuType = RTIMU_TYPE_GD20M303DLHC; slaveAddress = L3GD20_ADDRESS1; busIsI2C = true; HAL_INFO("Detected L3GD20 at option address\n"); return true; } } if (HALRead(BMX055_GYRO_ADDRESS0, BMX055_GYRO_WHO_AM_I, 1, &result, "")) { if (result == BMX055_GYRO_ID) { imuType = RTIMU_TYPE_BMX055; slaveAddress = BMX055_GYRO_ADDRESS0; busIsI2C = true; HAL_INFO("Detected BMX055 at standard address\n"); return true; } } if (HALRead(BMX055_GYRO_ADDRESS1, BMX055_GYRO_WHO_AM_I, 1, &result, "")) { if (result == BMX055_GYRO_ID) { imuType = RTIMU_TYPE_BMX055; slaveAddress = BMX055_GYRO_ADDRESS1; busIsI2C = true; HAL_INFO("Detected BMX055 at option address\n"); return true; } } if (HALRead(BNO055_ADDRESS0, BNO055_WHO_AM_I, 1, &result, "")) { if (result == BNO055_ID) { imuType = RTIMU_TYPE_BNO055; slaveAddress = BNO055_ADDRESS0; busIsI2C = true; HAL_INFO("Detected BNO055 at standard address\n"); return true; } } if (HALRead(BNO055_ADDRESS1, BNO055_WHO_AM_I, 1, &result, "")) { if (result == BNO055_ID) { imuType = RTIMU_TYPE_BNO055; slaveAddress = BNO055_ADDRESS1; busIsI2C = true; HAL_INFO("Detected BNO055 at option address\n"); return true; } } HALClose(); } // nothing found on I2C bus - try SPI instead m_busIsI2C = false; m_SPIBus = 0; m_SPISelect = 0; if (HALOpen()) { if (HALRead(MPU9250_ADDRESS0, MPU9250_WHO_AM_I, 1, &result, "")) { if (result == MPU9250_ID) { imuType = RTIMU_TYPE_MPU9250; slaveAddress = MPU9250_ADDRESS0; busIsI2C = false; HAL_INFO("Detected MPU9250 on SPI bus 0, select 0\n"); return true; } } HALClose(); } m_SPISelect = 1; if (HALOpen()) { if (HALRead(MPU9250_ADDRESS0, MPU9250_WHO_AM_I, 1, &result, "")) { if (result == MPU9250_ID) { imuType = RTIMU_TYPE_MPU9250; slaveAddress = MPU9250_ADDRESS0; busIsI2C = false; HAL_INFO("Detected MPU9250 on SPI bus 0, select 1\n"); return true; } } HALClose(); } HAL_ERROR("No IMU detected\n"); return false; } bool RTIMUSettings::discoverPressure(int& pressureType, unsigned char& pressureAddress) { unsigned char result; // auto detect on current bus if (HALOpen()) { if (HALRead(BMP180_ADDRESS, BMP180_REG_ID, 1, &result, "")) { if (result == BMP180_ID) { pressureType = RTPRESSURE_TYPE_BMP180; pressureAddress = BMP180_ADDRESS; HAL_INFO("Detected BMP180\n"); return true; } } if (HALRead(LPS25H_ADDRESS0, LPS25H_REG_ID, 1, &result, "")) { if (result == LPS25H_ID) { pressureType = RTPRESSURE_TYPE_LPS25H; pressureAddress = LPS25H_ADDRESS0; HAL_INFO("Detected LPS25H at standard address\n"); return true; } } if (HALRead(LPS25H_ADDRESS1, LPS25H_REG_ID, 1, &result, "")) { if (result == LPS25H_ID) { pressureType = RTPRESSURE_TYPE_LPS25H; pressureAddress = LPS25H_ADDRESS1; HAL_INFO("Detected LPS25H at option address\n"); return true; } } // check for MS5611 (which unfortunately has no ID reg) if (HALRead(MS5611_ADDRESS0, 0, 1, &result, "")) { pressureType = RTPRESSURE_TYPE_MS5611; pressureAddress = MS5611_ADDRESS0; HAL_INFO("Detected MS5611 at standard address\n"); return true; } if (HALRead(MS5611_ADDRESS1, 0, 1, &result, "")) { pressureType = RTPRESSURE_TYPE_MS5611; pressureAddress = MS5611_ADDRESS1; HAL_INFO("Detected MS5611 at option address\n"); return true; } } HAL_ERROR("No pressure sensor detected\n"); return false; } bool RTIMUSettings::discoverHumidity(int& humidityType, unsigned char& humidityAddress) { unsigned char result; // auto detect on current bus if (HALOpen()) { if (HALRead(HTS221_ADDRESS, HTS221_REG_ID, 1, &result, "")) { if (result == HTS221_ID) { humidityType = RTHUMIDITY_TYPE_HTS221; humidityAddress = HTS221_ADDRESS; HAL_INFO("Detected HTS221 at standard address\n"); return true; } } if (HALRead(HTU21D_ADDRESS, HTU21D_READ_USER_REG, 1, &result, "")) { humidityType = RTHUMIDITY_TYPE_HTU21D; humidityAddress = HTU21D_ADDRESS; HAL_INFO("Detected HTU21D at standard address\n"); return true; } } HAL_ERROR("No humidity sensor detected\n"); return false; } void RTIMUSettings::setDefaults() { // preset general defaults m_imuType = RTIMU_TYPE_AUTODISCOVER; m_I2CSlaveAddress = 0; m_busIsI2C = true; m_I2CBus = 1; m_SPIBus = 0; m_SPISelect = 0; m_SPISpeed = 500000; m_fusionType = RTFUSION_TYPE_RTQF; m_axisRotation = RTIMU_XNORTH_YEAST; m_pressureType = RTPRESSURE_TYPE_AUTODISCOVER; m_I2CPressureAddress = 0; m_humidityType = RTHUMIDITY_TYPE_AUTODISCOVER; m_I2CHumidityAddress = 0; m_compassCalValid = false; m_compassCalEllipsoidValid = false; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { m_compassCalEllipsoidCorr[i][j] = 0; } } m_compassCalEllipsoidCorr[0][0] = 1; m_compassCalEllipsoidCorr[1][1] = 1; m_compassCalEllipsoidCorr[2][2] = 1; m_compassAdjDeclination = 0; m_accelCalValid = false; m_gyroBiasValid = false; // MPU9150 defaults m_MPU9150GyroAccelSampleRate = 50; m_MPU9150CompassSampleRate = 25; m_MPU9150GyroAccelLpf = MPU9150_LPF_20; m_MPU9150GyroFsr = MPU9150_GYROFSR_1000; m_MPU9150AccelFsr = MPU9150_ACCELFSR_8; // MPU9250 defaults m_MPU9250GyroAccelSampleRate = 80; m_MPU9250CompassSampleRate = 40; m_MPU9250GyroLpf = MPU9250_GYRO_LPF_41; m_MPU9250AccelLpf = MPU9250_ACCEL_LPF_41; m_MPU9250GyroFsr = MPU9250_GYROFSR_1000; m_MPU9250AccelFsr = MPU9250_ACCELFSR_8; // GD20HM303D defaults m_GD20HM303DGyroSampleRate = L3GD20H_SAMPLERATE_50; m_GD20HM303DGyroBW = L3GD20H_BANDWIDTH_1; m_GD20HM303DGyroHpf = L3GD20H_HPF_4; m_GD20HM303DGyroFsr = L3GD20H_FSR_500; m_GD20HM303DAccelSampleRate = LSM303D_ACCEL_SAMPLERATE_50; m_GD20HM303DAccelFsr = LSM303D_ACCEL_FSR_8; m_GD20HM303DAccelLpf = LSM303D_ACCEL_LPF_50; m_GD20HM303DCompassSampleRate = LSM303D_COMPASS_SAMPLERATE_50; m_GD20HM303DCompassFsr = LSM303D_COMPASS_FSR_2; // GD20M303DLHC defaults m_GD20M303DLHCGyroSampleRate = L3GD20_SAMPLERATE_95; m_GD20M303DLHCGyroBW = L3GD20_BANDWIDTH_1; m_GD20M303DLHCGyroHpf = L3GD20_HPF_4; m_GD20M303DLHCGyroFsr = L3GD20H_FSR_500; m_GD20M303DLHCAccelSampleRate = LSM303DLHC_ACCEL_SAMPLERATE_50; m_GD20M303DLHCAccelFsr = LSM303DLHC_ACCEL_FSR_8; m_GD20M303DLHCCompassSampleRate = LSM303DLHC_COMPASS_SAMPLERATE_30; m_GD20M303DLHCCompassFsr = LSM303DLHC_COMPASS_FSR_1_3; // GD20HM303DLHC defaults m_GD20HM303DLHCGyroSampleRate = L3GD20H_SAMPLERATE_50; m_GD20HM303DLHCGyroBW = L3GD20H_BANDWIDTH_1; m_GD20HM303DLHCGyroHpf = L3GD20H_HPF_4; m_GD20HM303DLHCGyroFsr = L3GD20H_FSR_500; m_GD20HM303DLHCAccelSampleRate = LSM303DLHC_ACCEL_SAMPLERATE_50; m_GD20HM303DLHCAccelFsr = LSM303DLHC_ACCEL_FSR_8; m_GD20HM303DLHCCompassSampleRate = LSM303DLHC_COMPASS_SAMPLERATE_30; m_GD20HM303DLHCCompassFsr = LSM303DLHC_COMPASS_FSR_1_3; // LSM9DS0 defaults m_LSM9DS0GyroSampleRate = LSM9DS0_GYRO_SAMPLERATE_95; m_LSM9DS0GyroBW = LSM9DS0_GYRO_BANDWIDTH_1; m_LSM9DS0GyroHpf = LSM9DS0_GYRO_HPF_4; m_LSM9DS0GyroFsr = LSM9DS0_GYRO_FSR_500; m_LSM9DS0AccelSampleRate = LSM9DS0_ACCEL_SAMPLERATE_50; m_LSM9DS0AccelFsr = LSM9DS0_ACCEL_FSR_8; m_LSM9DS0AccelLpf = LSM9DS0_ACCEL_LPF_50; m_LSM9DS0CompassSampleRate = LSM9DS0_COMPASS_SAMPLERATE_50; m_LSM9DS0CompassFsr = LSM9DS0_COMPASS_FSR_2; // LSM9DS1 defaults m_LSM9DS1GyroSampleRate = LSM9DS1_GYRO_SAMPLERATE_119; m_LSM9DS1GyroBW = LSM9DS1_GYRO_BANDWIDTH_1; m_LSM9DS1GyroHpf = LSM9DS1_GYRO_HPF_4; m_LSM9DS1GyroFsr = LSM9DS1_GYRO_FSR_500; m_LSM9DS1AccelSampleRate = LSM9DS1_ACCEL_SAMPLERATE_119; m_LSM9DS1AccelFsr = LSM9DS1_ACCEL_FSR_8; m_LSM9DS1AccelLpf = LSM9DS1_ACCEL_LPF_50; m_LSM9DS1CompassSampleRate = LSM9DS1_COMPASS_SAMPLERATE_20; m_LSM9DS1CompassFsr = LSM9DS1_COMPASS_FSR_4; // BMX055 defaults m_BMX055GyroSampleRate = BMX055_GYRO_SAMPLERATE_100_32; m_BMX055GyroFsr = BMX055_GYRO_FSR_500; m_BMX055AccelSampleRate = BMX055_ACCEL_SAMPLERATE_125; m_BMX055AccelFsr = BMX055_ACCEL_FSR_8; m_BMX055MagPreset = BMX055_MAG_REGULAR; } bool RTIMUSettings::loadSettings() { setDefaults(); char buf[200]; char key[200]; char val[200]; RTFLOAT ftemp; // check to see if settings file exists if (!(m_fd = fopen(m_filename, "r"))) { HAL_INFO("Settings file not found. Using defaults and creating settings file\n"); return saveSettings(); } while (fgets(buf, 200, m_fd)) { if ((buf[0] == '#') || (buf[0] == ' ') || (buf[0] == '\n')) // just a comment continue; if (sscanf(buf, "%[^=]=%s", key, val) != 2) { HAL_ERROR1("Bad line in settings file: %s\n", buf); fclose(m_fd); return false; } // now decode keys // general config if (strcmp(key, RTIMULIB_IMU_TYPE) == 0) { m_imuType = atoi(val); } else if (strcmp(key, RTIMULIB_FUSION_TYPE) == 0) { m_fusionType = atoi(val); } else if (strcmp(key, RTIMULIB_BUS_IS_I2C) == 0) { m_busIsI2C = strcmp(val, "true") == 0; } else if (strcmp(key, RTIMULIB_I2C_BUS) == 0) { m_I2CBus = atoi(val); } else if (strcmp(key, RTIMULIB_SPI_BUS) == 0) { m_SPIBus = atoi(val); } else if (strcmp(key, RTIMULIB_SPI_SELECT) == 0) { m_SPISelect = atoi(val); } else if (strcmp(key, RTIMULIB_SPI_SPEED) == 0) { m_SPISpeed = atoi(val); } else if (strcmp(key, RTIMULIB_I2C_SLAVEADDRESS) == 0) { m_I2CSlaveAddress = atoi(val); } else if (strcmp(key, RTIMULIB_AXIS_ROTATION) == 0) { m_axisRotation = atoi(val); } else if (strcmp(key, RTIMULIB_PRESSURE_TYPE) == 0) { m_pressureType = atoi(val); } else if (strcmp(key, RTIMULIB_I2C_PRESSUREADDRESS) == 0) { m_I2CPressureAddress = atoi(val); } else if (strcmp(key, RTIMULIB_HUMIDITY_TYPE) == 0) { m_humidityType = atoi(val); } else if (strcmp(key, RTIMULIB_I2C_HUMIDITYADDRESS) == 0) { m_I2CHumidityAddress = atoi(val); // compass calibration and adjustment } else if (strcmp(key, RTIMULIB_COMPASSCAL_VALID) == 0) { m_compassCalValid = strcmp(val, "true") == 0; } else if (strcmp(key, RTIMULIB_COMPASSCAL_MINX) == 0) { sscanf(val, "%f", &ftemp); m_compassCalMin.setX(ftemp); } else if (strcmp(key, RTIMULIB_COMPASSCAL_MINY) == 0) { sscanf(val, "%f", &ftemp); m_compassCalMin.setY(ftemp); } else if (strcmp(key, RTIMULIB_COMPASSCAL_MINZ) == 0) { sscanf(val, "%f", &ftemp); m_compassCalMin.setZ(ftemp); } else if (strcmp(key, RTIMULIB_COMPASSCAL_MAXX) == 0) { sscanf(val, "%f", &ftemp); m_compassCalMax.setX(ftemp); } else if (strcmp(key, RTIMULIB_COMPASSCAL_MAXY) == 0) { sscanf(val, "%f", &ftemp); m_compassCalMax.setY(ftemp); } else if (strcmp(key, RTIMULIB_COMPASSCAL_MAXZ) == 0) { sscanf(val, "%f", &ftemp); m_compassCalMax.setZ(ftemp); } else if (strcmp(key, RTIMULIB_COMPASSADJ_DECLINATION) == 0) { sscanf(val, "%f", &ftemp); m_compassAdjDeclination = ftemp; // compass ellipsoid calibration } else if (strcmp(key, RTIMULIB_COMPASSCAL_ELLIPSOID_VALID) == 0) { m_compassCalEllipsoidValid = strcmp(val, "true") == 0; } else if (strcmp(key, RTIMULIB_COMPASSCAL_OFFSET_X) == 0) { sscanf(val, "%f", &ftemp); m_compassCalEllipsoidOffset.setX(ftemp); } else if (strcmp(key, RTIMULIB_COMPASSCAL_OFFSET_Y) == 0) { sscanf(val, "%f", &ftemp); m_compassCalEllipsoidOffset.setY(ftemp); } else if (strcmp(key, RTIMULIB_COMPASSCAL_OFFSET_Z) == 0) { sscanf(val, "%f", &ftemp); m_compassCalEllipsoidOffset.setZ(ftemp); } else if (strcmp(key, RTIMULIB_COMPASSCAL_CORR11) == 0) { sscanf(val, "%f", &ftemp); m_compassCalEllipsoidCorr[0][0] = ftemp; } else if (strcmp(key, RTIMULIB_COMPASSCAL_CORR12) == 0) { sscanf(val, "%f", &ftemp); m_compassCalEllipsoidCorr[0][1] = ftemp; } else if (strcmp(key, RTIMULIB_COMPASSCAL_CORR13) == 0) { sscanf(val, "%f", &ftemp); m_compassCalEllipsoidCorr[0][2] = ftemp; } else if (strcmp(key, RTIMULIB_COMPASSCAL_CORR21) == 0) { sscanf(val, "%f", &ftemp); m_compassCalEllipsoidCorr[1][0] = ftemp; } else if (strcmp(key, RTIMULIB_COMPASSCAL_CORR22) == 0) { sscanf(val, "%f", &ftemp); m_compassCalEllipsoidCorr[1][1] = ftemp; } else if (strcmp(key, RTIMULIB_COMPASSCAL_CORR23) == 0) { sscanf(val, "%f", &ftemp); m_compassCalEllipsoidCorr[1][2] = ftemp; } else if (strcmp(key, RTIMULIB_COMPASSCAL_CORR31) == 0) { sscanf(val, "%f", &ftemp); m_compassCalEllipsoidCorr[2][0] = ftemp; } else if (strcmp(key, RTIMULIB_COMPASSCAL_CORR32) == 0) { sscanf(val, "%f", &ftemp); m_compassCalEllipsoidCorr[2][1] = ftemp; } else if (strcmp(key, RTIMULIB_COMPASSCAL_CORR33) == 0) { sscanf(val, "%f", &ftemp); m_compassCalEllipsoidCorr[2][2] = ftemp; // accel calibration } else if (strcmp(key, RTIMULIB_ACCELCAL_VALID) == 0) { m_accelCalValid = strcmp(val, "true") == 0; } else if (strcmp(key, RTIMULIB_ACCELCAL_MINX) == 0) { sscanf(val, "%f", &ftemp); m_accelCalMin.setX(ftemp); } else if (strcmp(key, RTIMULIB_ACCELCAL_MINY) == 0) { sscanf(val, "%f", &ftemp); m_accelCalMin.setY(ftemp); } else if (strcmp(key, RTIMULIB_ACCELCAL_MINZ) == 0) { sscanf(val, "%f", &ftemp); m_accelCalMin.setZ(ftemp); } else if (strcmp(key, RTIMULIB_ACCELCAL_MAXX) == 0) { sscanf(val, "%f", &ftemp); m_accelCalMax.setX(ftemp); } else if (strcmp(key, RTIMULIB_ACCELCAL_MAXY) == 0) { sscanf(val, "%f", &ftemp); m_accelCalMax.setY(ftemp); } else if (strcmp(key, RTIMULIB_ACCELCAL_MAXZ) == 0) { sscanf(val, "%f", &ftemp); m_accelCalMax.setZ(ftemp); // gyro bias } else if (strcmp(key, RTIMULIB_GYRO_BIAS_VALID) == 0) { m_gyroBiasValid = strcmp(val, "true") == 0; } else if (strcmp(key, RTIMULIB_GYRO_BIAS_X) == 0) { sscanf(val, "%f", &ftemp); m_gyroBias.setX(ftemp); } else if (strcmp(key, RTIMULIB_GYRO_BIAS_Y) == 0) { sscanf(val, "%f", &ftemp); m_gyroBias.setY(ftemp); } else if (strcmp(key, RTIMULIB_GYRO_BIAS_Z) == 0) { sscanf(val, "%f", &ftemp); m_gyroBias.setZ(ftemp); // MPU9150 settings } else if (strcmp(key, RTIMULIB_MPU9150_GYROACCEL_SAMPLERATE) == 0) { m_MPU9150GyroAccelSampleRate = atoi(val); } else if (strcmp(key, RTIMULIB_MPU9150_COMPASS_SAMPLERATE) == 0) { m_MPU9150CompassSampleRate = atoi(val); } else if (strcmp(key, RTIMULIB_MPU9150_GYROACCEL_LPF) == 0) { m_MPU9150GyroAccelLpf = atoi(val); } else if (strcmp(key, RTIMULIB_MPU9150_GYRO_FSR) == 0) { m_MPU9150GyroFsr = atoi(val); } else if (strcmp(key, RTIMULIB_MPU9150_ACCEL_FSR) == 0) { m_MPU9150AccelFsr = atoi(val); // MPU9250 settings } else if (strcmp(key, RTIMULIB_MPU9250_GYROACCEL_SAMPLERATE) == 0) { m_MPU9250GyroAccelSampleRate = atoi(val); } else if (strcmp(key, RTIMULIB_MPU9250_COMPASS_SAMPLERATE) == 0) { m_MPU9250CompassSampleRate = atoi(val); } else if (strcmp(key, RTIMULIB_MPU9250_GYRO_LPF) == 0) { m_MPU9250GyroLpf = atoi(val); } else if (strcmp(key, RTIMULIB_MPU9250_ACCEL_LPF) == 0) { m_MPU9250AccelLpf = atoi(val); } else if (strcmp(key, RTIMULIB_MPU9250_GYRO_FSR) == 0) { m_MPU9250GyroFsr = atoi(val); } else if (strcmp(key, RTIMULIB_MPU9250_ACCEL_FSR) == 0) { m_MPU9250AccelFsr = atoi(val); // GD20HM303D settings } else if (strcmp(key, RTIMULIB_GD20HM303D_GYRO_SAMPLERATE) == 0) { m_GD20HM303DGyroSampleRate = atoi(val); } else if (strcmp(key, RTIMULIB_GD20HM303D_GYRO_FSR) == 0) { m_GD20HM303DGyroFsr = atoi(val); } else if (strcmp(key, RTIMULIB_GD20HM303D_GYRO_HPF) == 0) { m_GD20HM303DGyroHpf = atoi(val); } else if (strcmp(key, RTIMULIB_GD20HM303D_GYRO_BW) == 0) { m_GD20HM303DGyroBW = atoi(val); } else if (strcmp(key, RTIMULIB_GD20HM303D_ACCEL_SAMPLERATE) == 0) { m_GD20HM303DAccelSampleRate = atoi(val); } else if (strcmp(key, RTIMULIB_GD20HM303D_ACCEL_FSR) == 0) { m_GD20HM303DAccelFsr = atoi(val); } else if (strcmp(key, RTIMULIB_GD20HM303D_ACCEL_LPF) == 0) { m_GD20HM303DAccelLpf = atoi(val); } else if (strcmp(key, RTIMULIB_GD20HM303D_COMPASS_SAMPLERATE) == 0) { m_GD20HM303DCompassSampleRate = atoi(val); } else if (strcmp(key, RTIMULIB_GD20HM303D_COMPASS_FSR) == 0) { m_GD20HM303DCompassFsr = atoi(val); // GD20M303DLHC settings } else if (strcmp(key, RTIMULIB_GD20M303DLHC_GYRO_SAMPLERATE) == 0) { m_GD20M303DLHCGyroSampleRate = atoi(val); } else if (strcmp(key, RTIMULIB_GD20M303DLHC_GYRO_FSR) == 0) { m_GD20M303DLHCGyroFsr = atoi(val); } else if (strcmp(key, RTIMULIB_GD20M303DLHC_GYRO_HPF) == 0) { m_GD20M303DLHCGyroHpf = atoi(val); } else if (strcmp(key, RTIMULIB_GD20M303DLHC_GYRO_BW) == 0) { m_GD20M303DLHCGyroBW = atoi(val); } else if (strcmp(key, RTIMULIB_GD20M303DLHC_ACCEL_SAMPLERATE) == 0) { m_GD20M303DLHCAccelSampleRate = atoi(val); } else if (strcmp(key, RTIMULIB_GD20M303DLHC_ACCEL_FSR) == 0) { m_GD20M303DLHCAccelFsr = atoi(val); } else if (strcmp(key, RTIMULIB_GD20M303DLHC_COMPASS_SAMPLERATE) == 0) { m_GD20M303DLHCCompassSampleRate = atoi(val); } else if (strcmp(key, RTIMULIB_GD20M303DLHC_COMPASS_FSR) == 0) { m_GD20M303DLHCCompassFsr = atoi(val); // GD20HM303DLHC settings } else if (strcmp(key, RTIMULIB_GD20HM303DLHC_GYRO_SAMPLERATE) == 0) { m_GD20HM303DLHCGyroSampleRate = atoi(val); } else if (strcmp(key, RTIMULIB_GD20HM303DLHC_GYRO_FSR) == 0) { m_GD20HM303DLHCGyroFsr = atoi(val); } else if (strcmp(key, RTIMULIB_GD20HM303DLHC_GYRO_HPF) == 0) { m_GD20HM303DLHCGyroHpf = atoi(val); } else if (strcmp(key, RTIMULIB_GD20HM303DLHC_GYRO_BW) == 0) { m_GD20HM303DLHCGyroBW = atoi(val); } else if (strcmp(key, RTIMULIB_GD20HM303DLHC_ACCEL_SAMPLERATE) == 0) { m_GD20HM303DLHCAccelSampleRate = atoi(val); } else if (strcmp(key, RTIMULIB_GD20HM303DLHC_ACCEL_FSR) == 0) { m_GD20HM303DLHCAccelFsr = atoi(val); } else if (strcmp(key, RTIMULIB_GD20HM303DLHC_COMPASS_SAMPLERATE) == 0) { m_GD20HM303DLHCCompassSampleRate = atoi(val); } else if (strcmp(key, RTIMULIB_GD20HM303DLHC_COMPASS_FSR) == 0) { m_GD20HM303DLHCCompassFsr = atoi(val); // LSM9DS0 settings } else if (strcmp(key, RTIMULIB_LSM9DS0_GYRO_SAMPLERATE) == 0) { m_LSM9DS0GyroSampleRate = atoi(val); } else if (strcmp(key, RTIMULIB_LSM9DS0_GYRO_FSR) == 0) { m_LSM9DS0GyroFsr = atoi(val); } else if (strcmp(key, RTIMULIB_LSM9DS0_GYRO_HPF) == 0) { m_LSM9DS0GyroHpf = atoi(val); } else if (strcmp(key, RTIMULIB_LSM9DS0_GYRO_BW) == 0) { m_LSM9DS0GyroBW = atoi(val); } else if (strcmp(key, RTIMULIB_LSM9DS0_ACCEL_SAMPLERATE) == 0) { m_LSM9DS0AccelSampleRate = atoi(val); } else if (strcmp(key, RTIMULIB_LSM9DS0_ACCEL_FSR) == 0) { m_LSM9DS0AccelFsr = atoi(val); } else if (strcmp(key, RTIMULIB_LSM9DS0_ACCEL_LPF) == 0) { m_LSM9DS0AccelLpf = atoi(val); } else if (strcmp(key, RTIMULIB_LSM9DS0_COMPASS_SAMPLERATE) == 0) { m_LSM9DS0CompassSampleRate = atoi(val); } else if (strcmp(key, RTIMULIB_LSM9DS0_COMPASS_FSR) == 0) { m_LSM9DS0CompassFsr = atoi(val); // LSM9DS1 settings } else if (strcmp(key, RTIMULIB_LSM9DS1_GYRO_SAMPLERATE) == 0) { m_LSM9DS1GyroSampleRate = atoi(val); } else if (strcmp(key, RTIMULIB_LSM9DS1_GYRO_FSR) == 0) { m_LSM9DS1GyroFsr = atoi(val); } else if (strcmp(key, RTIMULIB_LSM9DS1_GYRO_HPF) == 0) { m_LSM9DS1GyroHpf = atoi(val); } else if (strcmp(key, RTIMULIB_LSM9DS1_GYRO_BW) == 0) { m_LSM9DS1GyroBW = atoi(val); } else if (strcmp(key, RTIMULIB_LSM9DS1_ACCEL_SAMPLERATE) == 0) { m_LSM9DS1AccelSampleRate = atoi(val); } else if (strcmp(key, RTIMULIB_LSM9DS1_ACCEL_FSR) == 0) { m_LSM9DS1AccelFsr = atoi(val); } else if (strcmp(key, RTIMULIB_LSM9DS1_ACCEL_LPF) == 0) { m_LSM9DS1AccelLpf = atoi(val); } else if (strcmp(key, RTIMULIB_LSM9DS1_COMPASS_SAMPLERATE) == 0) { m_LSM9DS1CompassSampleRate = atoi(val); } else if (strcmp(key, RTIMULIB_LSM9DS1_COMPASS_FSR) == 0) { m_LSM9DS1CompassFsr = atoi(val); // BMX055 settings } else if (strcmp(key, RTIMULIB_BMX055_GYRO_SAMPLERATE) == 0) { m_BMX055GyroSampleRate = atoi(val); } else if (strcmp(key, RTIMULIB_BMX055_GYRO_FSR) == 0) { m_BMX055GyroFsr = atoi(val); } else if (strcmp(key, RTIMULIB_BMX055_ACCEL_SAMPLERATE) == 0) { m_BMX055AccelSampleRate = atoi(val); } else if (strcmp(key, RTIMULIB_BMX055_ACCEL_FSR) == 0) { m_BMX055AccelFsr = atoi(val); } else if (strcmp(key, RTIMULIB_BMX055_MAG_PRESET) == 0) { m_BMX055MagPreset = atoi(val); // Handle unrecognized key } else { HAL_ERROR1("Unrecognized key in settings file: %s\n", buf); } } HAL_INFO1("Settings file %s loaded\n", m_filename); fclose(m_fd); return saveSettings(); // make sure settings file is correct and complete } bool RTIMUSettings::saveSettings() { if (!(m_fd = fopen(m_filename, "w"))) { HAL_ERROR("Failed to open settings file for save"); return false; } // General settings setComment("#####################################################################"); setComment(""); setComment("RTIMULib settings file"); setBlank(); setComment("General settings"); setComment(""); setBlank(); setComment("IMU type - "); setComment(" 0 = Auto discover"); setComment(" 1 = Null (used when data is provided from a remote IMU"); setComment(" 2 = InvenSense MPU-9150"); setComment(" 3 = STM L3GD20H + LSM303D"); setComment(" 4 = STM L3GD20 + LSM303DLHC"); setComment(" 5 = STM LSM9DS0"); setComment(" 6 = STM LSM9DS1"); setComment(" 7 = InvenSense MPU-9250"); setComment(" 8 = STM L3GD20H + LSM303DLHC"); setComment(" 9 = Bosch BMX055"); setComment(" 10 = Bosch BNX055"); setValue(RTIMULIB_IMU_TYPE, m_imuType); setBlank(); setComment(""); setComment("Fusion type type - "); setComment(" 0 - Null. Use if only sensor data required without fusion"); setComment(" 1 - Kalman STATE4"); setComment(" 2 - RTQF"); setValue(RTIMULIB_FUSION_TYPE, m_fusionType); setBlank(); setComment(""); setComment("Is bus I2C: 'true' for I2C, 'false' for SPI"); setValue(RTIMULIB_BUS_IS_I2C, m_busIsI2C); setBlank(); setComment(""); setComment("I2C Bus (between 0 and 7) "); setValue(RTIMULIB_I2C_BUS, m_I2CBus); setBlank(); setComment(""); setComment("SPI Bus (between 0 and 7) "); setValue(RTIMULIB_SPI_BUS, m_SPIBus); setBlank(); setComment(""); setComment("SPI select (between 0 and 1) "); setValue(RTIMULIB_SPI_SELECT, m_SPISelect); setBlank(); setComment(""); setComment("SPI Speed in Hz"); setValue(RTIMULIB_SPI_SPEED, (int)m_SPISpeed); setBlank(); setComment(""); setComment("I2C slave address (filled in automatically by auto discover) "); setValue(RTIMULIB_I2C_SLAVEADDRESS, m_I2CSlaveAddress); setBlank(); setComment(""); setComment("IMU axis rotation - see RTIMU.h for details"); setValue(RTIMULIB_AXIS_ROTATION, m_axisRotation); setBlank(); setComment("Pressure sensor type - "); setComment(" 0 = Auto discover"); setComment(" 1 = Null (no hardware or don't use)"); setComment(" 2 = BMP180"); setComment(" 3 = LPS25H"); setComment(" 4 = MS5611"); setComment(" 5 = MS5637"); setValue(RTIMULIB_PRESSURE_TYPE, m_pressureType); setBlank(); setComment(""); setComment("I2C pressure sensor address (filled in automatically by auto discover) "); setValue(RTIMULIB_I2C_PRESSUREADDRESS, m_I2CPressureAddress); setBlank(); setComment("Humidity sensor type - "); setComment(" 0 = Auto discover"); setComment(" 1 = Null (no hardware or don't use)"); setComment(" 2 = HTS221"); setComment(" 3 = HTU21D"); setValue(RTIMULIB_HUMIDITY_TYPE, m_humidityType); setBlank(); setComment(""); setComment("I2C humidity sensor address (filled in automatically by auto discover) "); setValue(RTIMULIB_I2C_HUMIDITYADDRESS, m_I2CHumidityAddress); // Compass settings setBlank(); setComment("#####################################################################"); setComment(""); setBlank(); setComment("Compass calibration settings"); setValue(RTIMULIB_COMPASSCAL_VALID, m_compassCalValid); setValue(RTIMULIB_COMPASSCAL_MINX, m_compassCalMin.x()); setValue(RTIMULIB_COMPASSCAL_MINY, m_compassCalMin.y()); setValue(RTIMULIB_COMPASSCAL_MINZ, m_compassCalMin.z()); setValue(RTIMULIB_COMPASSCAL_MAXX, m_compassCalMax.x()); setValue(RTIMULIB_COMPASSCAL_MAXY, m_compassCalMax.y()); setValue(RTIMULIB_COMPASSCAL_MAXZ, m_compassCalMax.z()); setBlank(); setComment("#####################################################################"); setComment(""); setBlank(); setComment("Compass adjustment settings"); setComment("Compass declination is in radians and is subtracted from calculated heading"); setValue(RTIMULIB_COMPASSADJ_DECLINATION, m_compassAdjDeclination); // Compass ellipsoid calibration settings setBlank(); setComment("#####################################################################"); setComment(""); setBlank(); setComment("Compass ellipsoid calibration"); setValue(RTIMULIB_COMPASSCAL_ELLIPSOID_VALID, m_compassCalEllipsoidValid); setValue(RTIMULIB_COMPASSCAL_OFFSET_X, m_compassCalEllipsoidOffset.x()); setValue(RTIMULIB_COMPASSCAL_OFFSET_Y, m_compassCalEllipsoidOffset.y()); setValue(RTIMULIB_COMPASSCAL_OFFSET_Z, m_compassCalEllipsoidOffset.z()); setValue(RTIMULIB_COMPASSCAL_CORR11, m_compassCalEllipsoidCorr[0][0]); setValue(RTIMULIB_COMPASSCAL_CORR12, m_compassCalEllipsoidCorr[0][1]); setValue(RTIMULIB_COMPASSCAL_CORR13, m_compassCalEllipsoidCorr[0][2]); setValue(RTIMULIB_COMPASSCAL_CORR21, m_compassCalEllipsoidCorr[1][0]); setValue(RTIMULIB_COMPASSCAL_CORR22, m_compassCalEllipsoidCorr[1][1]); setValue(RTIMULIB_COMPASSCAL_CORR23, m_compassCalEllipsoidCorr[1][2]); setValue(RTIMULIB_COMPASSCAL_CORR31, m_compassCalEllipsoidCorr[2][0]); setValue(RTIMULIB_COMPASSCAL_CORR32, m_compassCalEllipsoidCorr[2][1]); setValue(RTIMULIB_COMPASSCAL_CORR33, m_compassCalEllipsoidCorr[2][2]); // Accel calibration settings setBlank(); setComment("#####################################################################"); setComment(""); setBlank(); setComment("Accel calibration"); setValue(RTIMULIB_ACCELCAL_VALID, m_accelCalValid); setValue(RTIMULIB_ACCELCAL_MINX, m_accelCalMin.x()); setValue(RTIMULIB_ACCELCAL_MINY, m_accelCalMin.y()); setValue(RTIMULIB_ACCELCAL_MINZ, m_accelCalMin.z()); setValue(RTIMULIB_ACCELCAL_MAXX, m_accelCalMax.x()); setValue(RTIMULIB_ACCELCAL_MAXY, m_accelCalMax.y()); setValue(RTIMULIB_ACCELCAL_MAXZ, m_accelCalMax.z()); // Gyro bias settings setBlank(); setComment("#####################################################################"); setComment(""); setBlank(); setComment("Saved gyro bias data"); setValue(RTIMULIB_GYRO_BIAS_VALID, m_gyroBiasValid); setValue(RTIMULIB_GYRO_BIAS_X, m_gyroBias.x()); setValue(RTIMULIB_GYRO_BIAS_Y, m_gyroBias.y()); setValue(RTIMULIB_GYRO_BIAS_Z, m_gyroBias.z()); // MPU-9150 settings setBlank(); setComment("#####################################################################"); setComment(""); setComment("MPU-9150 settings"); setComment(""); setBlank(); setComment("Gyro sample rate (between 5Hz and 1000Hz) "); setValue(RTIMULIB_MPU9150_GYROACCEL_SAMPLERATE, m_MPU9150GyroAccelSampleRate); setBlank(); setComment(""); setComment("Compass sample rate (between 1Hz and 100Hz) "); setValue(RTIMULIB_MPU9150_COMPASS_SAMPLERATE, m_MPU9150CompassSampleRate); setBlank(); setComment(""); setComment("Gyro/accel low pass filter - "); setComment(" 0 - gyro: 256Hz, accel: 260Hz"); setComment(" 1 - gyro: 188Hz, accel: 184Hz"); setComment(" 2 - gyro: 98Hz, accel: 98Hz"); setComment(" 3 - gyro: 42Hz, accel: 44Hz"); setComment(" 4 - gyro: 20Hz, accel: 21Hz"); setComment(" 5 - gyro: 10Hz, accel: 10Hz"); setComment(" 6 - gyro: 5Hz, accel: 5Hz"); setValue(RTIMULIB_MPU9150_GYROACCEL_LPF, m_MPU9150GyroAccelLpf); setBlank(); setComment(""); setComment("Gyro full scale range - "); setComment(" 0 - +/- 250 degress per second"); setComment(" 8 - +/- 500 degress per second"); setComment(" 16 - +/- 1000 degress per second"); setComment(" 24 - +/- 2000 degress per second"); setValue(RTIMULIB_MPU9150_GYRO_FSR, m_MPU9150GyroFsr); setBlank(); setComment(""); setComment("Accel full scale range - "); setComment(" 0 - +/- 2g"); setComment(" 8 - +/- 4g"); setComment(" 16 - +/- 8g"); setComment(" 24 - +/- 16g"); setValue(RTIMULIB_MPU9150_ACCEL_FSR, m_MPU9150AccelFsr); // MPU-9250 settings setBlank(); setComment("#####################################################################"); setComment(""); setComment("MPU-9250 settings"); setComment(""); setBlank(); setComment("Gyro sample rate (between 5Hz and 1000Hz plus 8000Hz and 32000Hz) "); setValue(RTIMULIB_MPU9250_GYROACCEL_SAMPLERATE, m_MPU9250GyroAccelSampleRate); setBlank(); setComment(""); setComment("Compass sample rate (between 1Hz and 100Hz) "); setValue(RTIMULIB_MPU9250_COMPASS_SAMPLERATE, m_MPU9250CompassSampleRate); setBlank(); setComment(""); setComment("Gyro low pass filter - "); setComment(" 0x11 - 8800Hz, 0.64mS delay"); setComment(" 0x10 - 3600Hz, 0.11mS delay"); setComment(" 0x00 - 250Hz, 0.97mS delay"); setComment(" 0x01 - 184Hz, 2.9mS delay"); setComment(" 0x02 - 92Hz, 3.9mS delay"); setComment(" 0x03 - 41Hz, 5.9mS delay"); setComment(" 0x04 - 20Hz, 9.9mS delay"); setComment(" 0x05 - 10Hz, 17.85mS delay"); setComment(" 0x06 - 5Hz, 33.48mS delay"); setValue(RTIMULIB_MPU9250_GYRO_LPF, m_MPU9250GyroLpf); setBlank(); setComment(""); setComment("Accel low pass filter - "); setComment(" 0x08 - 1130Hz, 0.75mS delay"); setComment(" 0x00 - 460Hz, 1.94mS delay"); setComment(" 0x01 - 184Hz, 5.80mS delay"); setComment(" 0x02 - 92Hz, 7.80mS delay"); setComment(" 0x03 - 41Hz, 11.80mS delay"); setComment(" 0x04 - 20Hz, 19.80mS delay"); setComment(" 0x05 - 10Hz, 35.70mS delay"); setComment(" 0x06 - 5Hz, 66.96mS delay"); setValue(RTIMULIB_MPU9250_ACCEL_LPF, m_MPU9250AccelLpf); setBlank(); setComment(""); setComment("Gyro full scale range - "); setComment(" 0 - +/- 250 degress per second"); setComment(" 8 - +/- 500 degress per second"); setComment(" 16 - +/- 1000 degress per second"); setComment(" 24 - +/- 2000 degress per second"); setValue(RTIMULIB_MPU9250_GYRO_FSR, m_MPU9250GyroFsr); setBlank(); setComment(""); setComment("Accel full scale range - "); setComment(" 0 - +/- 2g"); setComment(" 8 - +/- 4g"); setComment(" 16 - +/- 8g"); setComment(" 24 - +/- 16g"); setValue(RTIMULIB_MPU9250_ACCEL_FSR, m_MPU9250AccelFsr); // GD20HM303D settings setBlank(); setComment("#####################################################################"); setComment(""); setComment("L3GD20H + LSM303D settings"); setBlank(); setComment(""); setComment("Gyro sample rate - "); setComment(" 0 = 12.5Hz "); setComment(" 1 = 25Hz "); setComment(" 2 = 50Hz "); setComment(" 3 = 100Hz "); setComment(" 4 = 200Hz "); setComment(" 5 = 400Hz "); setComment(" 6 = 800Hz "); setValue(RTIMULIB_GD20HM303D_GYRO_SAMPLERATE, m_GD20HM303DGyroSampleRate); setBlank(); setComment(""); setComment("Gyro full scale range - "); setComment(" 0 = 245 degrees per second "); setComment(" 1 = 500 degrees per second "); setComment(" 2 = 2000 degrees per second "); setValue(RTIMULIB_GD20HM303D_GYRO_FSR, m_GD20HM303DGyroFsr); setBlank(); setComment(""); setComment("Gyro high pass filter - "); setComment(" 0 - 9 but see the L3GD20H manual for details"); setValue(RTIMULIB_GD20HM303D_GYRO_HPF, m_GD20HM303DGyroHpf); setBlank(); setComment(""); setComment("Gyro bandwidth - "); setComment(" 0 - 3 but see the L3GD20H manual for details"); setValue(RTIMULIB_GD20HM303D_GYRO_BW, m_GD20HM303DGyroBW); setBlank(); setComment("Accel sample rate - "); setComment(" 1 = 3.125Hz "); setComment(" 2 = 6.25Hz "); setComment(" 3 = 12.5Hz "); setComment(" 4 = 25Hz "); setComment(" 5 = 50Hz "); setComment(" 6 = 100Hz "); setComment(" 7 = 200Hz "); setComment(" 8 = 400Hz "); setComment(" 9 = 800Hz "); setComment(" 10 = 1600Hz "); setValue(RTIMULIB_GD20HM303D_ACCEL_SAMPLERATE, m_GD20HM303DAccelSampleRate); setBlank(); setComment(""); setComment("Accel full scale range - "); setComment(" 0 = +/- 2g "); setComment(" 1 = +/- 4g "); setComment(" 2 = +/- 6g "); setComment(" 3 = +/- 8g "); setComment(" 4 = +/- 16g "); setValue(RTIMULIB_GD20HM303D_ACCEL_FSR, m_GD20HM303DAccelFsr); setBlank(); setComment(""); setComment("Accel low pass filter - "); setComment(" 0 = 773Hz"); setComment(" 1 = 194Hz"); setComment(" 2 = 362Hz"); setComment(" 3 = 50Hz"); setValue(RTIMULIB_GD20HM303D_ACCEL_LPF, m_GD20HM303DAccelLpf); setBlank(); setComment(""); setComment("Compass sample rate - "); setComment(" 0 = 3.125Hz "); setComment(" 1 = 6.25Hz "); setComment(" 2 = 12.5Hz "); setComment(" 3 = 25Hz "); setComment(" 4 = 50Hz "); setComment(" 5 = 100Hz "); setValue(RTIMULIB_GD20HM303D_COMPASS_SAMPLERATE, m_GD20HM303DCompassSampleRate); setBlank(); setComment(""); setComment("Compass full scale range - "); setComment(" 0 = +/- 200 uT "); setComment(" 1 = +/- 400 uT "); setComment(" 2 = +/- 800 uT "); setComment(" 3 = +/- 1200 uT "); setValue(RTIMULIB_GD20HM303D_COMPASS_FSR, m_GD20HM303DCompassFsr); // GD20M303DLHC settings setBlank(); setComment("#####################################################################"); setComment(""); setComment("L3GD20 + LSM303DLHC settings"); setComment(""); setBlank(); setComment("Gyro sample rate - "); setComment(" 0 = 95z "); setComment(" 1 = 190Hz "); setComment(" 2 = 380Hz "); setComment(" 3 = 760Hz "); setValue(RTIMULIB_GD20M303DLHC_GYRO_SAMPLERATE, m_GD20M303DLHCGyroSampleRate); setBlank(); setComment(""); setComment("Gyro full scale range - "); setComment(" 0 = 250 degrees per second "); setComment(" 1 = 500 degrees per second "); setComment(" 2 = 2000 degrees per second "); setValue(RTIMULIB_GD20M303DLHC_GYRO_FSR, m_GD20M303DLHCGyroFsr); setBlank(); setComment(""); setComment("Gyro high pass filter - "); setComment(" 0 - 9 but see the L3GD20 manual for details"); setValue(RTIMULIB_GD20M303DLHC_GYRO_HPF, m_GD20M303DLHCGyroHpf); setBlank(); setComment(""); setComment("Gyro bandwidth - "); setComment(" 0 - 3 but see the L3GD20 manual for details"); setValue(RTIMULIB_GD20M303DLHC_GYRO_BW, m_GD20M303DLHCGyroBW); setBlank(); setComment("Accel sample rate - "); setComment(" 1 = 1Hz "); setComment(" 2 = 10Hz "); setComment(" 3 = 25Hz "); setComment(" 4 = 50Hz "); setComment(" 5 = 100Hz "); setComment(" 6 = 200Hz "); setComment(" 7 = 400Hz "); setValue(RTIMULIB_GD20M303DLHC_ACCEL_SAMPLERATE, m_GD20M303DLHCAccelSampleRate); setBlank(); setComment(""); setComment("Accel full scale range - "); setComment(" 0 = +/- 2g "); setComment(" 1 = +/- 4g "); setComment(" 2 = +/- 8g "); setComment(" 3 = +/- 16g "); setValue(RTIMULIB_GD20M303DLHC_ACCEL_FSR, m_GD20M303DLHCAccelFsr); setBlank(); setComment(""); setComment("Compass sample rate - "); setComment(" 0 = 0.75Hz "); setComment(" 1 = 1.5Hz "); setComment(" 2 = 3Hz "); setComment(" 3 = 7.5Hz "); setComment(" 4 = 15Hz "); setComment(" 5 = 30Hz "); setComment(" 6 = 75Hz "); setComment(" 7 = 220Hz "); setValue(RTIMULIB_GD20M303DLHC_COMPASS_SAMPLERATE, m_GD20M303DLHCCompassSampleRate); setBlank(); setComment(""); setComment("Compass full scale range - "); setComment(" 1 = +/- 130 uT "); setComment(" 2 = +/- 190 uT "); setComment(" 3 = +/- 250 uT "); setComment(" 4 = +/- 400 uT "); setComment(" 5 = +/- 470 uT "); setComment(" 6 = +/- 560 uT "); setComment(" 7 = +/- 810 uT "); setValue(RTIMULIB_GD20M303DLHC_COMPASS_FSR, m_GD20M303DLHCCompassFsr); // GD20HM303DLHC settings setBlank(); setComment("#####################################################################"); setComment(""); setComment("L3GD20H + LSM303DLHC settings"); setComment(""); setBlank(); setComment(""); setComment("Gyro sample rate - "); setComment(" 0 = 12.5Hz "); setComment(" 1 = 25Hz "); setComment(" 2 = 50Hz "); setComment(" 3 = 100Hz "); setComment(" 4 = 200Hz "); setComment(" 5 = 400Hz "); setComment(" 6 = 800Hz "); setValue(RTIMULIB_GD20HM303DLHC_GYRO_SAMPLERATE, m_GD20HM303DLHCGyroSampleRate); setBlank(); setComment(""); setComment("Gyro full scale range - "); setComment(" 0 = 245 degrees per second "); setComment(" 1 = 500 degrees per second "); setComment(" 2 = 2000 degrees per second "); setValue(RTIMULIB_GD20HM303DLHC_GYRO_FSR, m_GD20HM303DLHCGyroFsr); setBlank(); setComment(""); setComment("Gyro high pass filter - "); setComment(" 0 - 9 but see the L3GD20H manual for details"); setValue(RTIMULIB_GD20HM303DLHC_GYRO_HPF, m_GD20HM303DLHCGyroHpf); setBlank(); setComment(""); setComment("Gyro bandwidth - "); setComment(" 0 - 3 but see the L3GD20H manual for details"); setValue(RTIMULIB_GD20HM303DLHC_GYRO_BW, m_GD20HM303DLHCGyroBW); setBlank(); setComment("Accel sample rate - "); setComment(" 1 = 1Hz "); setComment(" 2 = 10Hz "); setComment(" 3 = 25Hz "); setComment(" 4 = 50Hz "); setComment(" 5 = 100Hz "); setComment(" 6 = 200Hz "); setComment(" 7 = 400Hz "); setValue(RTIMULIB_GD20HM303DLHC_ACCEL_SAMPLERATE, m_GD20HM303DLHCAccelSampleRate); setBlank(); setComment(""); setComment("Accel full scale range - "); setComment(" 0 = +/- 2g "); setComment(" 1 = +/- 4g "); setComment(" 2 = +/- 8g "); setComment(" 3 = +/- 16g "); setValue(RTIMULIB_GD20HM303DLHC_ACCEL_FSR, m_GD20HM303DLHCAccelFsr); setBlank(); setComment(""); setComment("Compass sample rate - "); setComment(" 0 = 0.75Hz "); setComment(" 1 = 1.5Hz "); setComment(" 2 = 3Hz "); setComment(" 3 = 7.5Hz "); setComment(" 4 = 15Hz "); setComment(" 5 = 30Hz "); setComment(" 6 = 75Hz "); setComment(" 7 = 220Hz "); setValue(RTIMULIB_GD20HM303DLHC_COMPASS_SAMPLERATE, m_GD20HM303DLHCCompassSampleRate); setBlank(); setComment(""); setComment("Compass full scale range - "); setComment(" 1 = +/- 130 uT "); setComment(" 2 = +/- 190 uT "); setComment(" 3 = +/- 250 uT "); setComment(" 4 = +/- 400 uT "); setComment(" 5 = +/- 470 uT "); setComment(" 6 = +/- 560 uT "); setComment(" 7 = +/- 810 uT "); setValue(RTIMULIB_GD20HM303DLHC_COMPASS_FSR, m_GD20HM303DLHCCompassFsr); // LSM9DS0 settings setBlank(); setComment("#####################################################################"); setComment(""); setComment("LSM9DS0 settings"); setComment(""); setBlank(); setComment("Gyro sample rate - "); setComment(" 0 = 95z "); setComment(" 1 = 190Hz "); setComment(" 2 = 380Hz "); setComment(" 3 = 760Hz "); setValue(RTIMULIB_LSM9DS0_GYRO_SAMPLERATE, m_LSM9DS0GyroSampleRate); setBlank(); setComment(""); setComment("Gyro full scale range - "); setComment(" 0 = 250 degrees per second "); setComment(" 1 = 500 degrees per second "); setComment(" 2 = 2000 degrees per second "); setValue(RTIMULIB_LSM9DS0_GYRO_FSR, m_LSM9DS0GyroFsr); setBlank(); setComment(""); setComment("Gyro high pass filter - "); setComment(" 0 - 9 but see the LSM9DS0 manual for details"); setValue(RTIMULIB_LSM9DS0_GYRO_HPF, m_LSM9DS0GyroHpf); setBlank(); setComment(""); setComment("Gyro bandwidth - "); setComment(" 0 - 3 but see the LSM9DS0 manual for details"); setValue(RTIMULIB_LSM9DS0_GYRO_BW, m_LSM9DS0GyroBW); setBlank(); setComment("Accel sample rate - "); setComment(" 1 = 3.125Hz "); setComment(" 2 = 6.25Hz "); setComment(" 3 = 12.5Hz "); setComment(" 4 = 25Hz "); setComment(" 5 = 50Hz "); setComment(" 6 = 100Hz "); setComment(" 7 = 200Hz "); setComment(" 8 = 400Hz "); setComment(" 9 = 800Hz "); setComment(" 10 = 1600Hz "); setValue(RTIMULIB_LSM9DS0_ACCEL_SAMPLERATE, m_LSM9DS0AccelSampleRate); setBlank(); setComment(""); setComment("Accel full scale range - "); setComment(" 0 = +/- 2g "); setComment(" 1 = +/- 4g "); setComment(" 2 = +/- 6g "); setComment(" 3 = +/- 8g "); setComment(" 4 = +/- 16g "); setValue(RTIMULIB_LSM9DS0_ACCEL_FSR, m_LSM9DS0AccelFsr); setBlank(); setComment(""); setComment("Accel low pass filter - "); setComment(" 0 = 773Hz"); setComment(" 1 = 194Hz"); setComment(" 2 = 362Hz"); setComment(" 3 = 50Hz"); setValue(RTIMULIB_LSM9DS0_ACCEL_LPF, m_LSM9DS0AccelLpf); setBlank(); setComment(""); setComment("Compass sample rate - "); setComment(" 0 = 3.125Hz "); setComment(" 1 = 6.25Hz "); setComment(" 2 = 12.5Hz "); setComment(" 3 = 25Hz "); setComment(" 4 = 50Hz "); setComment(" 5 = 100Hz "); setValue(RTIMULIB_LSM9DS0_COMPASS_SAMPLERATE, m_LSM9DS0CompassSampleRate); setBlank(); setComment(""); setComment("Compass full scale range - "); setComment(" 0 = +/- 200 uT "); setComment(" 1 = +/- 400 uT "); setComment(" 2 = +/- 800 uT "); setComment(" 3 = +/- 1200 uT "); setValue(RTIMULIB_LSM9DS0_COMPASS_FSR, m_LSM9DS0CompassFsr); // LSM9DS1 settings setBlank(); setComment("#####################################################################"); setComment(""); setComment("LSM9DS1 settings"); setComment(""); setBlank(); setComment("Gyro sample rate - "); setComment(" 0 = 95Hz "); setComment(" 1 = 190Hz "); setComment(" 2 = 380Hz "); setComment(" 3 = 760Hz "); setValue(RTIMULIB_LSM9DS1_GYRO_SAMPLERATE, m_LSM9DS1GyroSampleRate); setBlank(); setComment(""); setComment("Gyro full scale range - "); setComment(" 0 = 250 degrees per second "); setComment(" 1 = 500 degrees per second "); setComment(" 2 = 2000 degrees per second "); setValue(RTIMULIB_LSM9DS1_GYRO_FSR, m_LSM9DS1GyroFsr); setBlank(); setComment(""); setComment("Gyro high pass filter - "); setComment(" 0 - 9 but see the LSM9DS1 manual for details"); setValue(RTIMULIB_LSM9DS1_GYRO_HPF, m_LSM9DS1GyroHpf); setBlank(); setComment(""); setComment("Gyro bandwidth - "); setComment(" 0 - 3 but see the LSM9DS1 manual for details"); setValue(RTIMULIB_LSM9DS1_GYRO_BW, m_LSM9DS1GyroBW); setBlank(); setComment("Accel sample rate - "); setComment(" 1 = 14.9Hz "); setComment(" 2 = 59.5Hz "); setComment(" 3 = 119Hz "); setComment(" 4 = 238Hz "); setComment(" 5 = 476Hz "); setComment(" 6 = 952Hz "); setValue(RTIMULIB_LSM9DS1_ACCEL_SAMPLERATE, m_LSM9DS1AccelSampleRate); setBlank(); setComment(""); setComment("Accel full scale range - "); setComment(" 0 = +/- 2g "); setComment(" 1 = +/- 16g "); setComment(" 2 = +/- 4g "); setComment(" 3 = +/- 8g "); setValue(RTIMULIB_LSM9DS1_ACCEL_FSR, m_LSM9DS1AccelFsr); setBlank(); setComment(""); setComment("Accel low pass filter - "); setComment(" 0 = 408Hz"); setComment(" 1 = 211Hz"); setComment(" 2 = 105Hz"); setComment(" 3 = 50Hz"); setValue(RTIMULIB_LSM9DS1_ACCEL_LPF, m_LSM9DS1AccelLpf); setBlank(); setComment(""); setComment("Compass sample rate - "); setComment(" 0 = 0.625Hz "); setComment(" 1 = 1.25Hz "); setComment(" 2 = 2.5Hz "); setComment(" 3 = 5Hz "); setComment(" 4 = 10Hz "); setComment(" 5 = 20Hz "); setComment(" 6 = 40Hz "); setComment(" 7 = 80Hz "); setValue(RTIMULIB_LSM9DS1_COMPASS_SAMPLERATE, m_LSM9DS1CompassSampleRate); setBlank(); setComment(""); setComment("Compass full scale range - "); setComment(" 0 = +/- 400 uT "); setComment(" 1 = +/- 800 uT "); setComment(" 2 = +/- 1200 uT "); setComment(" 3 = +/- 1600 uT "); setValue(RTIMULIB_LSM9DS1_COMPASS_FSR, m_LSM9DS1CompassFsr); // BMX055 settings setBlank(); setComment("#####################################################################"); setComment(""); setComment("BMX055 settings"); setComment(""); setBlank(); setComment(""); setComment("Gyro sample rate - "); setComment(" 0 = 2000Hz (532Hz filter)"); setComment(" 1 = 2000Hz (230Hz filter)"); setComment(" 2 = 1000Hz (116Hz filter)"); setComment(" 3 = 400Hz (47Hz filter)"); setComment(" 4 = 200Hz (23Hz filter)"); setComment(" 5 = 100Hz (12Hz filter)"); setComment(" 6 = 200Hz (64Hz filter)"); setComment(" 7 = 100Hz (32Hz filter)"); setValue(RTIMULIB_BMX055_GYRO_SAMPLERATE, m_BMX055GyroSampleRate); setBlank(); setComment(""); setComment("Gyro full scale range - "); setComment(" 0 = 2000 deg/s"); setComment(" 1 = 1000 deg/s"); setComment(" 2 = 500 deg/s"); setComment(" 3 = 250 deg/s"); setComment(" 4 = 125 deg/s"); setValue(RTIMULIB_BMX055_GYRO_FSR, m_BMX055GyroFsr); setBlank(); setComment(""); setComment("Accel sample rate - "); setComment(" 0 = 15.63Hz"); setComment(" 1 = 31.25"); setComment(" 2 = 62.5"); setComment(" 3 = 125"); setComment(" 4 = 250"); setComment(" 5 = 500"); setComment(" 6 = 1000"); setComment(" 7 = 2000"); setValue(RTIMULIB_BMX055_ACCEL_SAMPLERATE, m_BMX055AccelSampleRate); setBlank(); setComment(""); setComment("Accel full scale range - "); setComment(" 0 = +/- 2g"); setComment(" 1 = +/- 4g"); setComment(" 2 = +/- 8g"); setComment(" 3 = +/- 16g"); setValue(RTIMULIB_BMX055_ACCEL_FSR, m_BMX055AccelFsr); setBlank(); setComment(""); setComment("Mag presets - "); setComment(" 0 = Low power"); setComment(" 1 = Regular"); setComment(" 2 = Enhanced"); setComment(" 3 = High accuracy"); setValue(RTIMULIB_BMX055_MAG_PRESET, m_BMX055MagPreset); fclose(m_fd); return true; } void RTIMUSettings::setBlank() { fprintf(m_fd, "\n"); } void RTIMUSettings::setComment(const char *comment) { fprintf(m_fd, "# %s\n", comment); } void RTIMUSettings::setValue(const char *key, const bool val) { fprintf(m_fd, "%s=%s\n", key, val ? "true" : "false"); } void RTIMUSettings::setValue(const char *key, const int val) { fprintf(m_fd, "%s=%d\n", key, val); } void RTIMUSettings::setValue(const char *key, const RTFLOAT val) { fprintf(m_fd, "%s=%f\n", key, val); } rtimulib-7.2.1/RTIMULib/RTIMUSettings.h000066400000000000000000000423541254201074400175000ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. // The MPU-9250 and SPI driver code is based on code generously supplied by // staslock@gmail.com (www.clickdrive.io) #ifndef _RTIMUSETTINGS_H #define _RTIMUSETTINGS_H #include "RTMath.h" #include "RTIMUHal.h" // Settings keys #define RTIMULIB_IMU_TYPE "IMUType" #define RTIMULIB_FUSION_TYPE "FusionType" #define RTIMULIB_BUS_IS_I2C "BusIsI2C" #define RTIMULIB_I2C_SLAVEADDRESS "I2CSlaveAddress" #define RTIMULIB_I2C_BUS "I2CBus" #define RTIMULIB_SPI_BUS "SPIBus" #define RTIMULIB_SPI_SELECT "SPISelect" #define RTIMULIB_SPI_SPEED "SPISpeed" #define RTIMULIB_AXIS_ROTATION "AxisRotation" #define RTIMULIB_PRESSURE_TYPE "PressureType" #define RTIMULIB_I2C_PRESSUREADDRESS "I2CPressureAddress" #define RTIMULIB_HUMIDITY_TYPE "HumidityType" #define RTIMULIB_I2C_HUMIDITYADDRESS "I2CHumidityAddress" // MPU9150 settings keys #define RTIMULIB_MPU9150_GYROACCEL_SAMPLERATE "MPU9150GyroAccelSampleRate" #define RTIMULIB_MPU9150_COMPASS_SAMPLERATE "MPU9150CompassSampleRate" #define RTIMULIB_MPU9150_GYROACCEL_LPF "MPU9150GyroAccelLpf" #define RTIMULIB_MPU9150_GYRO_FSR "MPU9150GyroFSR" #define RTIMULIB_MPU9150_ACCEL_FSR "MPU9150AccelFSR" // MPU9250 settings keys #define RTIMULIB_MPU9250_GYROACCEL_SAMPLERATE "MPU9250GyroAccelSampleRate" #define RTIMULIB_MPU9250_COMPASS_SAMPLERATE "MPU9250CompassSampleRate" #define RTIMULIB_MPU9250_GYRO_LPF "MPU9250GyroLpf" #define RTIMULIB_MPU9250_ACCEL_LPF "MPU9250AccelLpf" #define RTIMULIB_MPU9250_GYRO_FSR "MPU9250GyroFSR" #define RTIMULIB_MPU9250_ACCEL_FSR "MPU9250AccelFSR" // GD20HM303D settings keys #define RTIMULIB_GD20HM303D_GYRO_SAMPLERATE "GD20HM303DGyroSampleRate" #define RTIMULIB_GD20HM303D_GYRO_BW "GD20HM303DGyroBW" #define RTIMULIB_GD20HM303D_GYRO_HPF "GD20HM303DGyroHpf" #define RTIMULIB_GD20HM303D_GYRO_FSR "GD20HM303DGyroFsr" #define RTIMULIB_GD20HM303D_ACCEL_SAMPLERATE "GD20HM303DAccelSampleRate" #define RTIMULIB_GD20HM303D_ACCEL_FSR "GD20HM303DAccelFsr" #define RTIMULIB_GD20HM303D_ACCEL_LPF "GD20HM303DAccelLpf" #define RTIMULIB_GD20HM303D_COMPASS_SAMPLERATE "GD20HM303DCompassSampleRate" #define RTIMULIB_GD20HM303D_COMPASS_FSR "GD20HM303DCompassFsr" // GD20M303DLHC settings keys #define RTIMULIB_GD20M303DLHC_GYRO_SAMPLERATE "GD20M303DLHCGyroSampleRate" #define RTIMULIB_GD20M303DLHC_GYRO_BW "GD20M303DLHCGyroBW" #define RTIMULIB_GD20M303DLHC_GYRO_HPF "GD20M303DLHCGyroHpf" #define RTIMULIB_GD20M303DLHC_GYRO_FSR "GD20M303DLHCGyroFsr" #define RTIMULIB_GD20M303DLHC_ACCEL_SAMPLERATE "GD20M303DLHCAccelSampleRate" #define RTIMULIB_GD20M303DLHC_ACCEL_FSR "GD20M303DLHCAccelFsr" #define RTIMULIB_GD20M303DLHC_COMPASS_SAMPLERATE "GD20M303DLHCCompassSampleRate" #define RTIMULIB_GD20M303DLHC_COMPASS_FSR "GD20M303DLHCCompassFsr" // GD20HM303DLHC settings keys #define RTIMULIB_GD20HM303DLHC_GYRO_SAMPLERATE "GD20HM303DLHCGyroSampleRate" #define RTIMULIB_GD20HM303DLHC_GYRO_BW "GD20HM303DLHCGyroBW" #define RTIMULIB_GD20HM303DLHC_GYRO_HPF "GD20HM303DLHCGyroHpf" #define RTIMULIB_GD20HM303DLHC_GYRO_FSR "GD20HM303DLHCGyroFsr" #define RTIMULIB_GD20HM303DLHC_ACCEL_SAMPLERATE "GD20HM303DLHCAccelSampleRate" #define RTIMULIB_GD20HM303DLHC_ACCEL_FSR "GD20HM303DLHCAccelFsr" #define RTIMULIB_GD20HM303DLHC_COMPASS_SAMPLERATE "GD20HM303DLHCCompassSampleRate" #define RTIMULIB_GD20HM303DLHC_COMPASS_FSR "GD20HM303DLHCCompassFsr" // LSM9DS0 settings keys #define RTIMULIB_LSM9DS0_GYRO_SAMPLERATE "LSM9DS0GyroSampleRate" #define RTIMULIB_LSM9DS0_GYRO_BW "LSM9DS0GyroBW" #define RTIMULIB_LSM9DS0_GYRO_HPF "LSM9DS0GyroHpf" #define RTIMULIB_LSM9DS0_GYRO_FSR "LSM9DS0GyroFsr" #define RTIMULIB_LSM9DS0_ACCEL_SAMPLERATE "LSM9DS0AccelSampleRate" #define RTIMULIB_LSM9DS0_ACCEL_FSR "LSM9DS0AccelFsr" #define RTIMULIB_LSM9DS0_ACCEL_LPF "LSM9DS0AccelLpf" #define RTIMULIB_LSM9DS0_COMPASS_SAMPLERATE "LSM9DS0CompassSampleRate" #define RTIMULIB_LSM9DS0_COMPASS_FSR "LSM9DS0CompassFsr" // LSM9DS1 settings keys #define RTIMULIB_LSM9DS1_GYRO_SAMPLERATE "LSM9DS1GyroSampleRate" #define RTIMULIB_LSM9DS1_GYRO_BW "LSM9DS1GyroBW" #define RTIMULIB_LSM9DS1_GYRO_HPF "LSM9DS1GyroHpf" #define RTIMULIB_LSM9DS1_GYRO_FSR "LSM9DS1GyroFsr" #define RTIMULIB_LSM9DS1_ACCEL_SAMPLERATE "LSM9DS1AccelSampleRate" #define RTIMULIB_LSM9DS1_ACCEL_FSR "LSM9DS1AccelFsr" #define RTIMULIB_LSM9DS1_ACCEL_LPF "LSM9DS1AccelLpf" #define RTIMULIB_LSM9DS1_COMPASS_SAMPLERATE "LSM9DS1CompassSampleRate" #define RTIMULIB_LSM9DS1_COMPASS_FSR "LSM9DS1CompassFsr" // BMX055 settings keys #define RTIMULIB_BMX055_GYRO_SAMPLERATE "BMX055GyroSampleRate" #define RTIMULIB_BMX055_GYRO_FSR "BMX055GyroFsr" #define RTIMULIB_BMX055_ACCEL_SAMPLERATE "BMX055AccelSampleRate" #define RTIMULIB_BMX055_ACCEL_FSR "BMX055AccelFsr" #define RTIMULIB_BMX055_MAG_PRESET "BMX055MagPreset" // Gyro bias keys #define RTIMULIB_GYRO_BIAS_VALID "GyroBiasValid" #define RTIMULIB_GYRO_BIAS_X "GyroBiasX" #define RTIMULIB_GYRO_BIAS_Y "GyroBiasY" #define RTIMULIB_GYRO_BIAS_Z "GyroBiasZ" // Compass calibration and adjustment settings keys #define RTIMULIB_COMPASSCAL_VALID "CompassCalValid" #define RTIMULIB_COMPASSCAL_MINX "CompassCalMinX" #define RTIMULIB_COMPASSCAL_MAXX "CompassCalMaxX" #define RTIMULIB_COMPASSCAL_MINY "CompassCalMinY" #define RTIMULIB_COMPASSCAL_MAXY "CompassCalMaxY" #define RTIMULIB_COMPASSCAL_MINZ "CompassCalMinZ" #define RTIMULIB_COMPASSCAL_MAXZ "CompassCalMaxZ" #define RTIMULIB_COMPASSCAL_ELLIPSOID_VALID "compassCalEllipsoidValid" #define RTIMULIB_COMPASSCAL_OFFSET_X "compassCalOffsetX" #define RTIMULIB_COMPASSCAL_OFFSET_Y "compassCalOffsetY" #define RTIMULIB_COMPASSCAL_OFFSET_Z "compassCalOffsetZ" #define RTIMULIB_COMPASSCAL_CORR11 "compassCalCorr11" #define RTIMULIB_COMPASSCAL_CORR12 "compassCalCorr12" #define RTIMULIB_COMPASSCAL_CORR13 "compassCalCorr13" #define RTIMULIB_COMPASSCAL_CORR21 "compassCalCorr21" #define RTIMULIB_COMPASSCAL_CORR22 "compassCalCorr22" #define RTIMULIB_COMPASSCAL_CORR23 "compassCalCorr23" #define RTIMULIB_COMPASSCAL_CORR31 "compassCalCorr31" #define RTIMULIB_COMPASSCAL_CORR32 "compassCalCorr32" #define RTIMULIB_COMPASSCAL_CORR33 "compassCalCorr33" #define RTIMULIB_COMPASSADJ_DECLINATION "compassAdjDeclination" // Accel calibration settings keys #define RTIMULIB_ACCELCAL_VALID "AccelCalValid" #define RTIMULIB_ACCELCAL_MINX "AccelCalMinX" #define RTIMULIB_ACCELCAL_MAXX "AccelCalMaxX" #define RTIMULIB_ACCELCAL_MINY "AccelCalMinY" #define RTIMULIB_ACCELCAL_MAXY "AccelCalMaxY" #define RTIMULIB_ACCELCAL_MINZ "AccelCalMinZ" #define RTIMULIB_ACCELCAL_MAXZ "AccelCalMaxZ" class RTIMUSettings : public RTIMUHal { public: // Standard constructor sets up for ini file in working directory RTIMUSettings(const char *productType = "RTIMULib"); // Alternate constructor allow ini file to be in any directory RTIMUSettings(const char *settingsDirectory, const char *productType); // This function tries to find an IMU. It stops at the first valid one // and returns true or else false bool discoverIMU(int& imuType, bool& busIsI2C, unsigned char& slaveAddress); // This function tries to find a pressure sensor. It stops at the first valid one // and returns true or else false bool discoverPressure(int& pressureType, unsigned char& pressureAddress); // This function tries to find a humidity sensor. It stops at the first valid one // and returns true or else false bool discoverHumidity(int& humidityType, unsigned char& humidityAddress); // This function sets the settings to default values. void setDefaults(); // This function loads the local variables from the settings file or uses defaults virtual bool loadSettings(); // This function saves the local variables to the settings file virtual bool saveSettings(); // These are the local variables int m_imuType; // type code of imu in use int m_fusionType; // fusion algorithm type code unsigned char m_I2CSlaveAddress; // I2C slave address of the imu int m_axisRotation; // axis rotation code int m_pressureType; // type code of pressure sensor in use unsigned char m_I2CPressureAddress; // I2C slave address of the pressure sensor int m_humidityType; // type code of humidity sensor in use unsigned char m_I2CHumidityAddress; // I2C slave address of the humidity sensor bool m_compassCalValid; // true if there is valid compass calibration data RTVector3 m_compassCalMin; // the minimum values RTVector3 m_compassCalMax; // the maximum values bool m_compassCalEllipsoidValid; // true if the ellipsoid calibration data is valid RTVector3 m_compassCalEllipsoidOffset; // the ellipsoid offset float m_compassCalEllipsoidCorr[3][3]; // the correction matrix float m_compassAdjDeclination; // magnetic declination adjustment - subtracted from measured bool m_accelCalValid; // true if there is valid accel calibration data RTVector3 m_accelCalMin; // the minimum values RTVector3 m_accelCalMax; // the maximum values bool m_gyroBiasValid; // true if the recorded gyro bias is valid RTVector3 m_gyroBias; // the recorded gyro bias // IMU-specific vars // MPU9150 int m_MPU9150GyroAccelSampleRate; // the sample rate (samples per second) for gyro and accel int m_MPU9150CompassSampleRate; // same for the compass int m_MPU9150GyroAccelLpf; // low pass filter code for the gyro and accel int m_MPU9150GyroFsr; // FSR code for the gyro int m_MPU9150AccelFsr; // FSR code for the accel // MPU9250 int m_MPU9250GyroAccelSampleRate; // the sample rate (samples per second) for gyro and accel int m_MPU9250CompassSampleRate; // same for the compass int m_MPU9250GyroLpf; // low pass filter code for the gyro int m_MPU9250AccelLpf; // low pass filter code for the accel int m_MPU9250GyroFsr; // FSR code for the gyro int m_MPU9250AccelFsr; // FSR code for the accel // GD20HM303D int m_GD20HM303DGyroSampleRate; // the gyro sample rate int m_GD20HM303DGyroBW; // the gyro bandwidth code int m_GD20HM303DGyroHpf; // the gyro high pass filter cutoff code int m_GD20HM303DGyroFsr; // the gyro full scale range int m_GD20HM303DAccelSampleRate; // the accel sample rate int m_GD20HM303DAccelFsr; // the accel full scale range int m_GD20HM303DAccelLpf; // the accel low pass filter int m_GD20HM303DCompassSampleRate; // the compass sample rate int m_GD20HM303DCompassFsr; // the compass full scale range // GD20M303DLHC int m_GD20M303DLHCGyroSampleRate; // the gyro sample rate int m_GD20M303DLHCGyroBW; // the gyro bandwidth code int m_GD20M303DLHCGyroHpf; // the gyro high pass filter cutoff code int m_GD20M303DLHCGyroFsr; // the gyro full scale range int m_GD20M303DLHCAccelSampleRate; // the accel sample rate int m_GD20M303DLHCAccelFsr; // the accel full scale range int m_GD20M303DLHCCompassSampleRate; // the compass sample rate int m_GD20M303DLHCCompassFsr; // the compass full scale range // GD20HM303DLHC int m_GD20HM303DLHCGyroSampleRate; // the gyro sample rate int m_GD20HM303DLHCGyroBW; // the gyro bandwidth code int m_GD20HM303DLHCGyroHpf; // the gyro high pass filter cutoff code int m_GD20HM303DLHCGyroFsr; // the gyro full scale range int m_GD20HM303DLHCAccelSampleRate; // the accel sample rate int m_GD20HM303DLHCAccelFsr; // the accel full scale range int m_GD20HM303DLHCCompassSampleRate; // the compass sample rate int m_GD20HM303DLHCCompassFsr; // the compass full scale range // LSM9DS0 int m_LSM9DS0GyroSampleRate; // the gyro sample rate int m_LSM9DS0GyroBW; // the gyro bandwidth code int m_LSM9DS0GyroHpf; // the gyro high pass filter cutoff code int m_LSM9DS0GyroFsr; // the gyro full scale range int m_LSM9DS0AccelSampleRate; // the accel sample rate int m_LSM9DS0AccelFsr; // the accel full scale range int m_LSM9DS0AccelLpf; // the accel low pass filter int m_LSM9DS0CompassSampleRate; // the compass sample rate int m_LSM9DS0CompassFsr; // the compass full scale range // LSM9DS1 int m_LSM9DS1GyroSampleRate; // the gyro sample rate int m_LSM9DS1GyroBW; // the gyro bandwidth code int m_LSM9DS1GyroHpf; // the gyro high pass filter cutoff code int m_LSM9DS1GyroFsr; // the gyro full scale range int m_LSM9DS1AccelSampleRate; // the accel sample rate int m_LSM9DS1AccelFsr; // the accel full scale range int m_LSM9DS1AccelLpf; // the accel low pass filter int m_LSM9DS1CompassSampleRate; // the compass sample rate int m_LSM9DS1CompassFsr; // the compass full scale range // BMX055 int m_BMX055GyroSampleRate; // the gyro sample rate int m_BMX055GyroFsr; // the gyro full scale range int m_BMX055AccelSampleRate; // the accel sample rate int m_BMX055AccelFsr; // the accel full scale range int m_BMX055MagPreset; // the mag preset code private: void setBlank(); void setComment(const char *comment); void setValue(const char *key, const bool val); void setValue(const char *key, const int val); void setValue(const char *key, const RTFLOAT val); char m_filename[256]; // the settings file name FILE *m_fd; }; #endif // _RTIMUSETTINGS_H rtimulib-7.2.1/RTIMULib/RTMath.cpp000066400000000000000000000402411254201074400165420ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #include "RTMath.h" #ifdef WIN32 #include #endif // Strings are put here. So the display functions are no re-entrant! char RTMath::m_string[1000]; uint64_t RTMath::currentUSecsSinceEpoch() { #ifdef WIN32 #include return QDateTime::currentMSecsSinceEpoch(); #else struct timeval tv; gettimeofday(&tv, NULL); return (uint64_t)tv.tv_sec * 1000000 + (uint64_t)tv.tv_usec; #endif } const char *RTMath::displayRadians(const char *label, RTVector3& vec) { sprintf(m_string, "%s: x:%f, y:%f, z:%f\n", label, vec.x(), vec.y(), vec.z()); return m_string; } const char *RTMath::displayDegrees(const char *label, RTVector3& vec) { sprintf(m_string, "%s: roll:%f, pitch:%f, yaw:%f", label, vec.x() * RTMATH_RAD_TO_DEGREE, vec.y() * RTMATH_RAD_TO_DEGREE, vec.z() * RTMATH_RAD_TO_DEGREE); return m_string; } const char *RTMath::display(const char *label, RTQuaternion& quat) { sprintf(m_string, "%s: scalar: %f, x:%f, y:%f, z:%f\n", label, quat.scalar(), quat.x(), quat.y(), quat.z()); return m_string; } const char *RTMath::display(const char *label, RTMatrix4x4& mat) { sprintf(m_string, "%s(0): %f %f %f %f\n%s(1): %f %f %f %f\n%s(2): %f %f %f %f\n%s(3): %f %f %f %f\n", label, mat.val(0,0), mat.val(0,1), mat.val(0,2), mat.val(0,3), label, mat.val(1,0), mat.val(1,1), mat.val(1,2), mat.val(1,3), label, mat.val(2,0), mat.val(2,1), mat.val(2,2), mat.val(2,3), label, mat.val(3,0), mat.val(3,1), mat.val(3,2), mat.val(3,3)); return m_string; } // convertPressureToHeight() - the conversion uses the formula: // // h = (T0 / L0) * ((p / P0)**(-(R* * L0) / (g0 * M)) - 1) // // where: // h = height above sea level // T0 = standard temperature at sea level = 288.15 // L0 = standard temperatur elapse rate = -0.0065 // p = measured pressure // P0 = static pressure = 1013.25 (but can be overridden) // g0 = gravitational acceleration = 9.80665 // M = mloecular mass of earth's air = 0.0289644 // R* = universal gas constant = 8.31432 // // Given the constants, this works out to: // // h = 44330.8 * (1 - (p / P0)**0.190263) RTFLOAT RTMath::convertPressureToHeight(RTFLOAT pressure, RTFLOAT staticPressure) { return 44330.8 * (1 - pow(pressure / staticPressure, (RTFLOAT)0.190263)); } RTVector3 RTMath::poseFromAccelMag(const RTVector3& accel, const RTVector3& mag) { RTVector3 result; RTQuaternion m; RTQuaternion q; accel.accelToEuler(result); // q.fromEuler(result); // since result.z() is always 0, this can be optimized a little RTFLOAT cosX2 = cos(result.x() / 2.0f); RTFLOAT sinX2 = sin(result.x() / 2.0f); RTFLOAT cosY2 = cos(result.y() / 2.0f); RTFLOAT sinY2 = sin(result.y() / 2.0f); q.setScalar(cosX2 * cosY2); q.setX(sinX2 * cosY2); q.setY(cosX2 * sinY2); q.setZ(-sinX2 * sinY2); // q.normalize(); m.setScalar(0); m.setX(mag.x()); m.setY(mag.y()); m.setZ(mag.z()); m = q * m * q.conjugate(); result.setZ(-atan2(m.y(), m.x())); return result; } void RTMath::convertToVector(unsigned char *rawData, RTVector3& vec, RTFLOAT scale, bool bigEndian) { if (bigEndian) { vec.setX((RTFLOAT)((int16_t)(((uint16_t)rawData[0] << 8) | (uint16_t)rawData[1])) * scale); vec.setY((RTFLOAT)((int16_t)(((uint16_t)rawData[2] << 8) | (uint16_t)rawData[3])) * scale); vec.setZ((RTFLOAT)((int16_t)(((uint16_t)rawData[4] << 8) | (uint16_t)rawData[5])) * scale); } else { vec.setX((RTFLOAT)((int16_t)(((uint16_t)rawData[1] << 8) | (uint16_t)rawData[0])) * scale); vec.setY((RTFLOAT)((int16_t)(((uint16_t)rawData[3] << 8) | (uint16_t)rawData[2])) * scale); vec.setZ((RTFLOAT)((int16_t)(((uint16_t)rawData[5] << 8) | (uint16_t)rawData[4])) * scale); } } //---------------------------------------------------------- // // The RTVector3 class RTVector3::RTVector3() { zero(); } RTVector3::RTVector3(RTFLOAT x, RTFLOAT y, RTFLOAT z) { m_data[0] = x; m_data[1] = y; m_data[2] = z; } RTVector3& RTVector3::operator =(const RTVector3& vec) { if (this == &vec) return *this; m_data[0] = vec.m_data[0]; m_data[1] = vec.m_data[1]; m_data[2] = vec.m_data[2]; return *this; } const RTVector3& RTVector3::operator +=(RTVector3& vec) { for (int i = 0; i < 3; i++) m_data[i] += vec.m_data[i]; return *this; } const RTVector3& RTVector3::operator -=(RTVector3& vec) { for (int i = 0; i < 3; i++) m_data[i] -= vec.m_data[i]; return *this; } void RTVector3::zero() { for (int i = 0; i < 3; i++) m_data[i] = 0; } RTFLOAT RTVector3::dotProduct(const RTVector3& a, const RTVector3& b) { return a.x() * b.x() + a.y() * b.y() + a.z() * b.z(); } void RTVector3::crossProduct(const RTVector3& a, const RTVector3& b, RTVector3& d) { d.setX(a.y() * b.z() - a.z() * b.y()); d.setY(a.z() * b.x() - a.x() * b.z()); d.setZ(a.x() * b.y() - a.y() * b.x()); } void RTVector3::accelToEuler(RTVector3& rollPitchYaw) const { RTVector3 normAccel = *this; normAccel.normalize(); rollPitchYaw.setX(atan2(normAccel.y(), normAccel.z())); rollPitchYaw.setY(-atan2(normAccel.x(), sqrt(normAccel.y() * normAccel.y() + normAccel.z() * normAccel.z()))); rollPitchYaw.setZ(0); } void RTVector3::accelToQuaternion(RTQuaternion& qPose) const { RTVector3 normAccel = *this; RTVector3 vec; RTVector3 z(0, 0, 1.0); normAccel.normalize(); RTFLOAT angle = acos(RTVector3::dotProduct(z, normAccel)); RTVector3::crossProduct(normAccel, z, vec); vec.normalize(); qPose.fromAngleVector(angle, vec); } void RTVector3::normalize() { RTFLOAT length = sqrt(m_data[0] * m_data[0] + m_data[1] * m_data[1] + m_data[2] * m_data[2]); if (length == 0) return; m_data[0] /= length; m_data[1] /= length; m_data[2] /= length; } RTFLOAT RTVector3::length() { return sqrt(m_data[0] * m_data[0] + m_data[1] * m_data[1] + m_data[2] * m_data[2]); } //---------------------------------------------------------- // // The RTQuaternion class RTQuaternion::RTQuaternion() { zero(); } RTQuaternion::RTQuaternion(RTFLOAT scalar, RTFLOAT x, RTFLOAT y, RTFLOAT z) { m_data[0] = scalar; m_data[1] = x; m_data[2] = y; m_data[3] = z; } RTQuaternion& RTQuaternion::operator =(const RTQuaternion& quat) { if (this == &quat) return *this; m_data[0] = quat.m_data[0]; m_data[1] = quat.m_data[1]; m_data[2] = quat.m_data[2]; m_data[3] = quat.m_data[3]; return *this; } RTQuaternion& RTQuaternion::operator +=(const RTQuaternion& quat) { for (int i = 0; i < 4; i++) m_data[i] += quat.m_data[i]; return *this; } RTQuaternion& RTQuaternion::operator -=(const RTQuaternion& quat) { for (int i = 0; i < 4; i++) m_data[i] -= quat.m_data[i]; return *this; } RTQuaternion& RTQuaternion::operator -=(const RTFLOAT val) { for (int i = 0; i < 4; i++) m_data[i] -= val; return *this; } RTQuaternion& RTQuaternion::operator *=(const RTQuaternion& qb) { RTQuaternion qa; qa = *this; m_data[0] = qa.scalar() * qb.scalar() - qa.x() * qb.x() - qa.y() * qb.y() - qa.z() * qb.z(); m_data[1] = qa.scalar() * qb.x() + qa.x() * qb.scalar() + qa.y() * qb.z() - qa.z() * qb.y(); m_data[2] = qa.scalar() * qb.y() - qa.x() * qb.z() + qa.y() * qb.scalar() + qa.z() * qb.x(); m_data[3] = qa.scalar() * qb.z() + qa.x() * qb.y() - qa.y() * qb.x() + qa.z() * qb.scalar(); return *this; } RTQuaternion& RTQuaternion::operator *=(const RTFLOAT val) { m_data[0] *= val; m_data[1] *= val; m_data[2] *= val; m_data[3] *= val; return *this; } const RTQuaternion RTQuaternion::operator *(const RTQuaternion& qb) const { RTQuaternion result = *this; result *= qb; return result; } const RTQuaternion RTQuaternion::operator *(const RTFLOAT val) const { RTQuaternion result = *this; result *= val; return result; } const RTQuaternion RTQuaternion::operator -(const RTQuaternion& qb) const { RTQuaternion result = *this; result -= qb; return result; } const RTQuaternion RTQuaternion::operator -(const RTFLOAT val) const { RTQuaternion result = *this; result -= val; return result; } void RTQuaternion::zero() { for (int i = 0; i < 4; i++) m_data[i] = 0; } void RTQuaternion::normalize() { RTFLOAT length = sqrt(m_data[0] * m_data[0] + m_data[1] * m_data[1] + m_data[2] * m_data[2] + m_data[3] * m_data[3]); if ((length == 0) || (length == 1)) return; m_data[0] /= length; m_data[1] /= length; m_data[2] /= length; m_data[3] /= length; } void RTQuaternion::toEuler(RTVector3& vec) { vec.setX(atan2(2.0 * (m_data[2] * m_data[3] + m_data[0] * m_data[1]), 1 - 2.0 * (m_data[1] * m_data[1] + m_data[2] * m_data[2]))); vec.setY(asin(2.0 * (m_data[0] * m_data[2] - m_data[1] * m_data[3]))); vec.setZ(atan2(2.0 * (m_data[1] * m_data[2] + m_data[0] * m_data[3]), 1 - 2.0 * (m_data[2] * m_data[2] + m_data[3] * m_data[3]))); } void RTQuaternion::fromEuler(RTVector3& vec) { RTFLOAT cosX2 = cos(vec.x() / 2.0f); RTFLOAT sinX2 = sin(vec.x() / 2.0f); RTFLOAT cosY2 = cos(vec.y() / 2.0f); RTFLOAT sinY2 = sin(vec.y() / 2.0f); RTFLOAT cosZ2 = cos(vec.z() / 2.0f); RTFLOAT sinZ2 = sin(vec.z() / 2.0f); m_data[0] = cosX2 * cosY2 * cosZ2 + sinX2 * sinY2 * sinZ2; m_data[1] = sinX2 * cosY2 * cosZ2 - cosX2 * sinY2 * sinZ2; m_data[2] = cosX2 * sinY2 * cosZ2 + sinX2 * cosY2 * sinZ2; m_data[3] = cosX2 * cosY2 * sinZ2 - sinX2 * sinY2 * cosZ2; normalize(); } RTQuaternion RTQuaternion::conjugate() const { RTQuaternion q; q.setScalar(m_data[0]); q.setX(-m_data[1]); q.setY(-m_data[2]); q.setZ(-m_data[3]); return q; } void RTQuaternion::toAngleVector(RTFLOAT& angle, RTVector3& vec) { RTFLOAT halfTheta; RTFLOAT sinHalfTheta; halfTheta = acos(m_data[0]); sinHalfTheta = sin(halfTheta); if (sinHalfTheta == 0) { vec.setX(1.0); vec.setY(0); vec.setZ(0); } else { vec.setX(m_data[1] / sinHalfTheta); vec.setY(m_data[1] / sinHalfTheta); vec.setZ(m_data[1] / sinHalfTheta); } angle = 2.0 * halfTheta; } void RTQuaternion::fromAngleVector(const RTFLOAT& angle, const RTVector3& vec) { RTFLOAT sinHalfTheta = sin(angle / 2.0); m_data[0] = cos(angle / 2.0); m_data[1] = vec.x() * sinHalfTheta; m_data[2] = vec.y() * sinHalfTheta; m_data[3] = vec.z() * sinHalfTheta; } //---------------------------------------------------------- // // The RTMatrix4x4 class RTMatrix4x4::RTMatrix4x4() { fill(0); } RTMatrix4x4& RTMatrix4x4::operator =(const RTMatrix4x4& mat) { if (this == &mat) return *this; for (int row = 0; row < 4; row++) for (int col = 0; col < 4; col++) m_data[row][col] = mat.m_data[row][col]; return *this; } void RTMatrix4x4::fill(RTFLOAT val) { for (int row = 0; row < 4; row++) for (int col = 0; col < 4; col++) m_data[row][col] = val; } RTMatrix4x4& RTMatrix4x4::operator +=(const RTMatrix4x4& mat) { for (int row = 0; row < 4; row++) for (int col = 0; col < 4; col++) m_data[row][col] += mat.m_data[row][col]; return *this; } RTMatrix4x4& RTMatrix4x4::operator -=(const RTMatrix4x4& mat) { for (int row = 0; row < 4; row++) for (int col = 0; col < 4; col++) m_data[row][col] -= mat.m_data[row][col]; return *this; } RTMatrix4x4& RTMatrix4x4::operator *=(const RTFLOAT val) { for (int row = 0; row < 4; row++) for (int col = 0; col < 4; col++) m_data[row][col] *= val; return *this; } const RTMatrix4x4 RTMatrix4x4::operator +(const RTMatrix4x4& mat) const { RTMatrix4x4 result = *this; result += mat; return result; } const RTMatrix4x4 RTMatrix4x4::operator *(const RTFLOAT val) const { RTMatrix4x4 result = *this; result *= val; return result; } const RTMatrix4x4 RTMatrix4x4::operator *(const RTMatrix4x4& mat) const { RTMatrix4x4 res; for (int row = 0; row < 4; row++) for (int col = 0; col < 4; col++) res.m_data[row][col] = m_data[row][0] * mat.m_data[0][col] + m_data[row][1] * mat.m_data[1][col] + m_data[row][2] * mat.m_data[2][col] + m_data[row][3] * mat.m_data[3][col]; return res; } const RTQuaternion RTMatrix4x4::operator *(const RTQuaternion& q) const { RTQuaternion res; res.setScalar(m_data[0][0] * q.scalar() + m_data[0][1] * q.x() + m_data[0][2] * q.y() + m_data[0][3] * q.z()); res.setX(m_data[1][0] * q.scalar() + m_data[1][1] * q.x() + m_data[1][2] * q.y() + m_data[1][3] * q.z()); res.setY(m_data[2][0] * q.scalar() + m_data[2][1] * q.x() + m_data[2][2] * q.y() + m_data[2][3] * q.z()); res.setZ(m_data[3][0] * q.scalar() + m_data[3][1] * q.x() + m_data[3][2] * q.y() + m_data[3][3] * q.z()); return res; } void RTMatrix4x4::setToIdentity() { fill(0); m_data[0][0] = 1; m_data[1][1] = 1; m_data[2][2] = 1; m_data[3][3] = 1; } RTMatrix4x4 RTMatrix4x4::transposed() { RTMatrix4x4 res; for (int row = 0; row < 4; row++) for (int col = 0; col < 4; col++) res.m_data[col][row] = m_data[row][col]; return res; } // Note: // The matrix inversion code here was strongly influenced by some old code I found // but I have no idea where it came from. Apologies to whoever wrote it originally! // If it's you, please let me know at info@richards-tech.com so I can credit it correctly. RTMatrix4x4 RTMatrix4x4::inverted() { RTMatrix4x4 res; RTFLOAT det = matDet(); if (det == 0) { res.setToIdentity(); return res; } for (int row = 0; row < 4; row++) { for (int col = 0; col < 4; col++) { if ((row + col) & 1) res.m_data[col][row] = -matMinor(row, col) / det; else res.m_data[col][row] = matMinor(row, col) / det; } } return res; } RTFLOAT RTMatrix4x4::matDet() { RTFLOAT det = 0; det += m_data[0][0] * matMinor(0, 0); det -= m_data[0][1] * matMinor(0, 1); det += m_data[0][2] * matMinor(0, 2); det -= m_data[0][3] * matMinor(0, 3); return det; } RTFLOAT RTMatrix4x4::matMinor(const int row, const int col) { static int map[] = {1, 2, 3, 0, 2, 3, 0, 1, 3, 0, 1, 2}; int *rc; int *cc; RTFLOAT res = 0; rc = map + row * 3; cc = map + col * 3; res += m_data[rc[0]][cc[0]] * m_data[rc[1]][cc[1]] * m_data[rc[2]][cc[2]]; res -= m_data[rc[0]][cc[0]] * m_data[rc[1]][cc[2]] * m_data[rc[2]][cc[1]]; res -= m_data[rc[0]][cc[1]] * m_data[rc[1]][cc[0]] * m_data[rc[2]][cc[2]]; res += m_data[rc[0]][cc[1]] * m_data[rc[1]][cc[2]] * m_data[rc[2]][cc[0]]; res += m_data[rc[0]][cc[2]] * m_data[rc[1]][cc[0]] * m_data[rc[2]][cc[1]]; res -= m_data[rc[0]][cc[2]] * m_data[rc[1]][cc[1]] * m_data[rc[2]][cc[0]]; return res; } rtimulib-7.2.1/RTIMULib/RTMath.h000066400000000000000000000153641254201074400162170ustar00rootroot00000000000000//////////////////////////////////////////////////////////////////////////// // // This file is part of RTIMULib // // Copyright (c) 2014-2015, richards-tech, LLC // // Permission 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. #ifndef _RTMATH_H_ #define _RTMATH_H_ #include "RTIMUHal.h" // The fundamental float type #ifdef RTMATH_USE_DOUBLE typedef double RTFLOAT; #else typedef float RTFLOAT; #endif // Useful constants #define RTMATH_PI 3.1415926535 #define RTMATH_DEGREE_TO_RAD (RTMATH_PI / 180.0) #define RTMATH_RAD_TO_DEGREE (180.0 / RTMATH_PI) class RTVector3; class RTMatrix4x4; class RTQuaternion; class RTMath { public: // convenient display routines static const char *displayRadians(const char *label, RTVector3& vec); static const char *displayDegrees(const char *label, RTVector3& vec); static const char *display(const char *label, RTQuaternion& quat); static const char *display(const char *label, RTMatrix4x4& mat); // currentUSecsSinceEpoch() is the source of all timestamps and // is the number of uS since the standard epoch static uint64_t currentUSecsSinceEpoch(); // poseFromAccelMag generates pose Euler angles from measured settings static RTVector3 poseFromAccelMag(const RTVector3& accel, const RTVector3& mag); // Takes signed 16 bit data from a char array and converts it to a vector of scaled RTFLOATs static void convertToVector(unsigned char *rawData, RTVector3& vec, RTFLOAT scale, bool bigEndian); // Takes a pressure in hPa and returns height above sea level in meters static RTFLOAT convertPressureToHeight(RTFLOAT pressure, RTFLOAT staticPressure = 1013.25); private: static char m_string[1000]; // for the display routines }; class RTVector3 { public: RTVector3(); RTVector3(RTFLOAT x, RTFLOAT y, RTFLOAT z); const RTVector3& operator +=(RTVector3& vec); const RTVector3& operator -=(RTVector3& vec); RTVector3& operator =(const RTVector3& vec); RTFLOAT length(); void normalize(); void zero(); const char *display(); const char *displayDegrees(); static float dotProduct(const RTVector3& a, const RTVector3& b); static void crossProduct(const RTVector3& a, const RTVector3& b, RTVector3& d); void accelToEuler(RTVector3& rollPitchYaw) const; void accelToQuaternion(RTQuaternion& qPose) const; inline RTFLOAT x() const { return m_data[0]; } inline RTFLOAT y() const { return m_data[1]; } inline RTFLOAT z() const { return m_data[2]; } inline RTFLOAT data(const int i) const { return m_data[i]; } inline void setX(const RTFLOAT val) { m_data[0] = val; } inline void setY(const RTFLOAT val) { m_data[1] = val; } inline void setZ(const RTFLOAT val) { m_data[2] = val; } inline void setData(const int i, RTFLOAT val) { m_data[i] = val; } inline void fromArray(RTFLOAT *val) { memcpy(m_data, val, 3 * sizeof(RTFLOAT)); } inline void toArray(RTFLOAT *val) const { memcpy(val, m_data, 3 * sizeof(RTFLOAT)); } private: RTFLOAT m_data[3]; }; class RTQuaternion { public: RTQuaternion(); RTQuaternion(RTFLOAT scalar, RTFLOAT x, RTFLOAT y, RTFLOAT z); RTQuaternion& operator +=(const RTQuaternion& quat); RTQuaternion& operator -=(const RTQuaternion& quat); RTQuaternion& operator *=(const RTQuaternion& qb); RTQuaternion& operator *=(const RTFLOAT val); RTQuaternion& operator -=(const RTFLOAT val); RTQuaternion& operator =(const RTQuaternion& quat); const RTQuaternion operator *(const RTQuaternion& qb) const; const RTQuaternion operator *(const RTFLOAT val) const; const RTQuaternion operator -(const RTQuaternion& qb) const; const RTQuaternion operator -(const RTFLOAT val) const; void normalize(); void toEuler(RTVector3& vec); void fromEuler(RTVector3& vec); RTQuaternion conjugate() const; void toAngleVector(RTFLOAT& angle, RTVector3& vec); void fromAngleVector(const RTFLOAT& angle, const RTVector3& vec); void zero(); const char *display(); inline RTFLOAT scalar() const { return m_data[0]; } inline RTFLOAT x() const { return m_data[1]; } inline RTFLOAT y() const { return m_data[2]; } inline RTFLOAT z() const { return m_data[3]; } inline RTFLOAT data(const int i) const { return m_data[i]; } inline void setScalar(const RTFLOAT val) { m_data[0] = val; } inline void setX(const RTFLOAT val) { m_data[1] = val; } inline void setY(const RTFLOAT val) { m_data[2] = val; } inline void setZ(const RTFLOAT val) { m_data[3] = val; } inline void setData(const int i, RTFLOAT val) { m_data[i] = val; } inline void fromArray(RTFLOAT *val) { memcpy(m_data, val, 4 * sizeof(RTFLOAT)); } inline void toArray(RTFLOAT *val) const { memcpy(val, m_data, 4 * sizeof(RTFLOAT)); } private: RTFLOAT m_data[4]; }; class RTMatrix4x4 { public: RTMatrix4x4(); RTMatrix4x4& operator +=(const RTMatrix4x4& mat); RTMatrix4x4& operator -=(const RTMatrix4x4& mat); RTMatrix4x4& operator *=(const RTFLOAT val); RTMatrix4x4& operator =(const RTMatrix4x4& vec); const RTQuaternion operator *(const RTQuaternion& q) const; const RTMatrix4x4 operator *(const RTFLOAT val) const; const RTMatrix4x4 operator *(const RTMatrix4x4& mat) const; const RTMatrix4x4 operator +(const RTMatrix4x4& mat) const; inline RTFLOAT val(int row, int col) const { return m_data[row][col]; } inline void setVal(int row, int col, RTFLOAT val) { m_data[row][col] = val; } void fill(RTFLOAT val); void setToIdentity(); RTMatrix4x4 inverted(); RTMatrix4x4 transposed(); private: RTFLOAT m_data[4][4]; // row, column RTFLOAT matDet(); RTFLOAT matMinor(const int row, const int col); }; #endif /* _RTMATH_H_ */ rtimulib-7.2.1/RTIMULibVersion.txt000066400000000000000000000003001254201074400167760ustar00rootroot00000000000000SET(RTIMULIB_VERSION_MAJOR 7) SET(RTIMULIB_VERSION_MINOR 2) SET(RTIMULIB_VERSION_PATCH 1) SET(RTIMULIB_VERSION ${RTIMULIB_VERSION_MAJOR}.${RTIMULIB_VERSION_MINOR}.${RTIMULIB_VERSION_PATCH})