From 1ff2fed62f27bf19d2150d8a5881a0ba0f6d27dd Mon Sep 17 00:00:00 2001
From: Felix Agner <felix.agner@control.lth.se>
Date: Tue, 1 Nov 2022 11:21:32 +0100
Subject: [PATCH] initial commit. Added code along with a build so that it can
 hopefully be easily installed

---
 LICENSE                                    |  21 +++
 __pycache__/Omnibot.cpython-310.pyc        | Bin 0 -> 3785 bytes
 dist/omnibot_client-0.0.1-py3-none-any.whl | Bin 0 -> 8245 bytes
 dist/omnibot_client-0.0.1.tar.gz           | Bin 0 -> 6745 bytes
 pyproject.toml                             |  22 +++
 src/omnibot_client/ControllerTest.py       | 120 ++++++++++++++++
 src/omnibot_client/Dummybot.py             |  40 ++++++
 src/omnibot_client/Omnibot.py              | 159 +++++++++++++++++++++
 src/omnibot_client/__init__.py             |   0
 test/gitkeep                               |   0
 10 files changed, 362 insertions(+)
 create mode 100644 LICENSE
 create mode 100644 __pycache__/Omnibot.cpython-310.pyc
 create mode 100644 dist/omnibot_client-0.0.1-py3-none-any.whl
 create mode 100644 dist/omnibot_client-0.0.1.tar.gz
 create mode 100644 pyproject.toml
 create mode 100644 src/omnibot_client/ControllerTest.py
 create mode 100644 src/omnibot_client/Dummybot.py
 create mode 100644 src/omnibot_client/Omnibot.py
 create mode 100644 src/omnibot_client/__init__.py
 create mode 100644 test/gitkeep

diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..b0471c3
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 Department of Automatic Control, Lund University
+
+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.
diff --git a/__pycache__/Omnibot.cpython-310.pyc b/__pycache__/Omnibot.cpython-310.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..07df5f4bb8d582d36a2ff6892b6165d2280c37f7
GIT binary patch
literal 3785
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
Vw>WGdPPYTqOvRwc;b0J90s!Uvn8N@7

literal 0
HcmV?d00001

diff --git a/dist/omnibot_client-0.0.1-py3-none-any.whl b/dist/omnibot_client-0.0.1-py3-none-any.whl
new file mode 100644
index 0000000000000000000000000000000000000000..af5823726089324bb369e8e7479102449be92426
GIT binary patch
literal 8245
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

literal 0
HcmV?d00001

diff --git a/dist/omnibot_client-0.0.1.tar.gz b/dist/omnibot_client-0.0.1.tar.gz
new file mode 100644
index 0000000000000000000000000000000000000000..41e7273a4e6402e3f1ea24c2309233b41c8b84db
GIT binary patch
literal 6745
zcmb2|=3rnDG>c>U|JLSj`RyGOe`o&;UvP?lqS=%b%lPR1%WRKM6TWM;ZT^y+S)YG~
zbhM~k5jiNdL}l6f|G&@jICM>!%ddXB<i703Pf49JJZ#3zGY=*xY~_0DqZ+I~LCNP9
z``l)~^{bQDuh!DvbzI9QNt8ut9>?60MQ_^m^Vu)Vv#oThU-<Ta?Y-){AI|STZoYR}
zeQ&e+k3SzCy!^b}enYeTK4<UJL&*wH+yBph%NX4eaKxtIiB?eIp+hC2sXf_ZZa<ah
zW~R%n7WIDOW$;_Tezy<1lS|*{2G!FM1@X^g&xA|reYopa&Lbu1mbs|dg?plpc#W#k
zJL_kETLbsXFznwK()qZ8@%pZo+&LdyWGs227GGSj|2WGeoytQ@TMzg*Rl2e-+vvWu
zS^rs%)zpPP%PubyJGduRAWFil#e5R`r{){SrzRh0leLci*7TLHewE#scU2PizXr5^
zC=pt|s3JySMWbu<qH}krz0h{M8>V2)JoBxyl4c~Uk=cZgb}E;|-z!Ya44$uEGv9He
z$0dst{_XtoFOMj^&WY}qOI!M0vZmkr{^PJ#7N@)u2Lw+|VGz90Ehp}u`r`0s?YlD%
zFJG3kac9@emg8@@P81|&h_~(Dx6bc`K|q^laJ=LG)y=Qn_bUInuzO$L*1W@MsyE{J
zsw>QMVw!ASZj`O(dH%r1QGB`e)yzvvqC?Nx`a0`RT)?tq4~Nd-kfl~9A{0uMoC4C0
zR?INm$ku%3Ow*bbOSsqnopevXa!12gDWQM6!<e!!%y(;@a5jjo>1$cjf{y`GOQ+tt
z<14_L;K8IQaU(Wf=;j@pcNM3^-yG$>_F}(`|Nl!XoCN(RN@?p{K3cGfNuz1E;J*JV
zhu-|-`2Tt`$K&6MHI|n1<M__k%kfF6EDcJpxxd@?3bPsG#84}<<ed>p^J_Pza@It4
z>^S=Fg{JA}OBQX44z)qLH6|}?QnKvw&tFmrOnj~!tgKyfidk1CW5w=5DZkGN3CfdG
zBPYhk_o%;%I%2%^DEF(a%daM^aek=kRyx!5u*1RBBO8oADs@d&Z*XH^a%F!yh4b)4
z3H|IfjV)WJ`S-edc}E##&0G8~#I2mi+>ML9w}NNa?2d@bj|I;NC(TWNQ@+3YfL}uE
z#|y$aYLj{e6hAZA_i%P3zv^kupXS7}%=o?I`@`~wR|ubcm&_*d>!H$(()&jHrp~H5
zo4Q8$^7RtimUaGJ>)r^*Zf$t^d+9#Ii!}v5F88y~RrlY$cm9I!)-|V-3bw6$x8b_N
zbIC<Bm;ZCx^8Ai>2+x|ioc+ym6GR)*HTiEuv7DOh**5ve@wBQl)9ttmk2S`$xbgfD
z3)v^D`C#8W3+sDKeJdrIHLBUJ%z3oojGfW6o;4FTdTivVP`du;+KmHA%O8DQwDe2p
zr8|e}^}ejPdMdb6miNUoCXs{MA3P<}{g|I$F1*QfjGKFEV}P2VcJ|*1$IBmTRDUx!
z*jTn+dhvk@na^t~ORu(_k81N(In!#v8Mbp<yxW^wy8G*oREr9HuHC|4vV*<E!{Pec
zTL0h6CKn&ot6u$3fb*!xN1X#~@2~X)ugRWqH11A{ZUs|*SHfE3h5o(=*tY$1y_(%v
z-YNd8wJ=Uk;6?GqpoiQ%kve)?(hHLeXS?6hF<ub(IaK6;f{5ii1&_ly9pPKmzvXl1
ziff$kV|6kWE%dPp@0{NAT<eWQmpS{|S=XO1r1PdQtn|BDv`<K2qLlpcOB_l33$3da
z@)Kn<StoAzwqxdmB$c_=v2SLvYpzgTBeS)gJz{O>iFC)v@2i?~8_ub@bmbba+>!d$
zjenc3=z-F();rsF?Op!c;I@`e8G8>uH~)gN*T39%3$4EvT|Rf`>}+wKfU9>y_XSuy
zTc;`3u$JYGj>A=f?AP1covVEf6Iib8*Ss6CRN(U3=(W)kwzny|U#Qt(TlIruqmD+(
zjn^58n}mGY3`7-f9WV*6xouN0x!JulPkQeU3BkT2f1Nm_jOH$STxT(N-434Rm)qU_
z_DQ(zar@ZK;<Tf+P~ypJ1s(>&&j~9_E*V^C&=p7&J|h${{phZv|3dg!<u*K&`Ky*Y
z<HAbm<hEaJYB#%6r|dWu&Bl16;);==dxB2Sad(XxmXA&GZdEs*?UU!`<$J8!?hw|-
zv{C%X+bI_=&ArTi@I&5Hsek9BIf{?`vrhbf!2f@a#JO6%oQ%H#+wVIB$Itk0=^4J7
zXZppxM)vVj{=c-X-ZcIHOWXRQ4{Of-{}cVme!l;=8YB1M9Lbv12BGu)4my=~8+a*B
zd92fZ{4C=wh2$!YmLAbx+AOmcu2t6eKgK@y{j@(dY<=NJ1rEQFDN7apaMD6$;f7Rw
zl^<O@jz}D7yZf1A|HYy_OMU0Pk{y2y<$noQ|7sQVx|$vrTYpIO|2CQD>VMMSnyt>;
zys0E=Kkx7S>%Xth_<#OJeNXEA_qYEazUP?xf4}?bwfjEhxok2HSGziIw!P_ZX^Abi
zyYHso)4R!+!L)Iajbgj1%=*yV8hlbV`JSAbau?&=6Car$dAp1;CFRtusipCr;-xc!
zC7PALN!2pn5^V0sVSD457^r)OYu6qL#v7_Tbz6ULv|((myd{?1)ch!O!Up?45Bcr4
z=7=@({1W^;jq6`iX5(M}Mc=0xWma44f9A8&;lqZnFAp<MmthV1z;yBcOeyV2$FF%j
znE#6V4Zr>e|F1{qvEMrF+RSnB*#s5qXa1M#UeEVyf8N|Vd%tYF%zqnMf4Am;(%IL}
zhG@JleeUS-@#j(|`MGY#rU;o89Z<D<Jlp1SI!lSVlI41zS1<U)r|b^eebS8U0z-b}
z9JL9pE$qD4Pwo9ACO`FW*umv}s@r!SezYk_{M&*#JC|IUda5|&`_kA}pXKw|^OEPW
z$r^k<R~N<}7<xHgq4TZZ${9DRE~|96{B)6I{ax@)P*awD#fg2lT0|Ys8m@eK(E5?a
zGVP3sb$e@cw4_e(T*|n6G*4xDWRU3=hTsdcGj(hj@7?q%VfA^$9XR*0gu}rVH_Cr=
zr4?1U{%UQnQqFm{k!Rb3?2DS``5yDW=rR!eo+n`ZJLSWc**urdH7~F{t#B<TYS*3i
zOr!k&Up9#Rvq?}kwv3S}mRxV4<sA7#zs#eN|8?5?2Om6FKjyYE&3ML<zmoe;;i~iu
z5pE&&rMv%ixb?I?<Y0WhE!eP=Cw6+tr--#-nHsiTJ2W}A8DH^{lsg}~C#knL#yNWm
zQ?wxWwu5WJU(7biNftisVv;e*daIW6jLAF-$Hb$O=FRHRN+@hGs?hm2kE=Iu*2#cd
zC!c8E&q|;3N6%!tQQLvyWIpB8?tg2dzX$!;_%$XYP3PQR)?T$6GQ96HfAH-wQ7GAA
zwPTsE(#G;TBE_8E6&t>~v{YSYxgfi?<j5Dxtd+-eeJ{F&eiLb}jS1$u_p0WrsHg9T
zxl>-n-(Oj*F+**|lAO@bjj#8nBot1YmCm&Ac;5%t!e6mVC4#r!zO?WCw>0jhOTD)(
z7R>)Hx=qkrORM{5ezDW_8)5gS?7DZ|r?2PUrLwITcPX#@q57-&$o74%<?mcSiN(BJ
zoMoO}_pI^J?_FCpPVwb+YwTX$v8lR4WA(n1b)tf)_Z1esvF_6NEhKn%m8xT4?T?NX
zc6Wk9<liZ-;=gJxUnd$^`-3g8_DA2MUj^^4vV?sr+j}-#%Jxtxf1u=#Q@3qJ?kgBI
zzkGc(?RUNT#fug3D*L+rl$H91{+!70_T+-XxepT7Z%a_kT=4q;%o44}(`M<dXRnCr
zZ~D}|=1bFuDe+RFyL^9Ny>>RncvY}`%a!N$^UwT$n%P%>@F#!mz0?0~>tde&|5y8|
zeQo@u*b`6OI$nfJJpT8o{?DJx*#CduPyT=NLq&m|+@GlA8n=J#dk^26_lJMJ&ARjT
z`@O4;=WH;3*(tzn*B?4%rqJbO*OS`W+uPr`xY+m^Z0>LLviB8PwsFs%S!YtyHCuY}
zojJ}W=}taVsOP$8QdIK2;H<Wd&vcTzXS8>Ih&AhPHCS1C=E06i-(r@%8z&S`EBIh6
zpqBJ9eTwCjS_YG-vy*xIeb35WkE)!MEO#P2uIor(R`1zV1(WQgwnc{9Y^8Lij)+$r
z$rXzDsqK}&(xkD0gI_=+{I!u}m7|yEyg#K1;g|XxRwg`|P{xqCDv`}&`RTAz(~JLT
zXG8^BaW9G6b!K`;)ikw=x<3BSt061zhF08?WZ_FVaLRRd=E;@DSML~jwea}rbZf9~
zj=Xj2eUe&`b#ceb1dm|%*V+k^y+5`G301Faa>y;niiq&qy7||M!VTwUoEK5mDrV&n
z^|-lVHjBX9C0X$=RnPnTPfRq>+c16lGU3M`c61)#7UAiiS+M7CW?90Y>&#~REavWc
z-OrX|kv4sCcYK?Av+%VS{ST`Tr@n8Sd|f<Wokzj#@8^@%Wre4HJ%4}m{Nc~S(|P|D
zSxWUBf0Oit=YGsQUiJ?;6?HY=zNJ+B>)P8=_gs9zQ|Ak%4puu8EEc$$%Jc}{oPF@c
z57U~r`(@_ws=tqYUSeVCK4-bO^0oBoe=nv~{Q0)VK52Ts`*iWgj6C&B8Ry%dFISdG
z{g75*_wvAs4?Vnfjruu%cGZ`)Ia~hS?(mo6W%6<H>F?bShp$(E^Y!S>lMHhEWEfKa
zPEThx7V&xYV~u^y<4u$Q&HlHV@#`)I9cA`|Jf&~$zGk#L^zqk+_Z3EaERJW&#Lw|}
z`*5{2vQN$Rdimjx2U7oDEa>5}l#Sg}D4U}GWru>wh4(cH8x&SYx+*a=-M`<*&gxWV
z^kP2aI>WMKn$Hf*ty2G3f8c*V+uzCN&F4=2m;1N>`;zZ(KJNQp&!_14_WyqGztgWW
zEN0lS_i(j5{|bRwpG-p*sMKt@)V20U?}1&C2TR>57y2xH5iO>+ys_Zfi5F9S&iy!}
z`u@Mb<iLv7&b(j_|FvNuk%yf&9?eraq|N%5K{!CN!ENI&<KO-Q+`<nQ^?cV9nIseB
z$JQffyp_}XjjV;F$`YRk^KV{_u70d2xLRXD*MF7$c?^YTN<TC9F-*>RtdOgcpc=J*
z`OELM_a7{uaj5Xto37Z+tCJbLj+7S9RGDb^s!Z|U1=$;#tTopfXO_0Ua0(D8PZWAl
zv3uhJZ!N8JyQB?gKdR7rr!zV9!vW=x+<V6aqh9E(zoZdx=YQx1=@8a3V{U8CIkP#Z
zUb^dO(9xRLfAYiS%f|O_8LwNqeD|I2nJmT{b;iouIE_DqZqa>amb5I>ZDm|iW%~`U
z$wDl-iMv<6c{gXuVbMd38&Adm{}q1xbN9aBH_vrA4tgzYNzCJNlIU>#<SFtc_{c5J
zNvZDLda+iQqF&fszIZpD!=lj1w)w%;=uO|G+f=i^bUeG@_xxwX7p6?-yOK^Tb{}Zg
zGZqb6?6NXpxm(4NX>MmuP3$_mf%%xb^|N~2yLZdX^_RVzX1Kj$M{<G|OZD$3ocpv^
zNVjFr<fvl%_$G_3arxShCw8xjsNXp)O5|=>so=pN{-eiO&ip+2u6Bac_LXm!?cOpY
zKz`MEPu=dq?z`MBD<!8oCOla4QDt&gl=hN4$xUqT;wDq{C$N3KyRIl$@uJ!JTYG$*
z9B&-B!?d#Jd<Dyo>oynS&rV@-{;sTJz??TtDP>}hkE8O{RUH3cyPL)fzBF;SIy*6{
za)F#*M&E+a4TpI|D(2paI3Fabl3U$>@=l|!zQCPSiSL&*MZR6V`>$P=fA?|q`Exqd
zch~zbEjM2JE_Rjsjo?`m*l)5YPUDPy?sB|bIO*tVE8Y!dRcg$|-+PW7yB)g0@tu2M
zL#T+yr-+Kop6hqjo(U|meAlnO_I&Y-_XR#%k6yeQqi)?O^={$&-<GYRVqGp79ACr#
zF!xwn*p*y-Bo=@C+M~r%5y$E$b+K$K%6Q6nM?72frCwL$v1M1*pVF8TKZT8P7u%hg
z!ezli9!ur4L)<DhE}nd`OEG5tbJjP8rg8V)IQ4Hh(lA%kcg2fYoEzdca%Da_e{vts
zZQTH$)BVL$|9(6!|8YAHn_7}ayK3KOzHkAJ6Ne?H>$e$J>I5-q9}=DZcdgXj1&5a`
zopAp`%%SNy;#<$K>~OrsqIT}%I`yRiFV^MEsq&NxKC(GPx^d|QiHW%eQd!b>KJ>di
zPHR+nS<?IY`&z!)2Q17hdCFVA6bUmcwwJBBontn4@ASLVbayd+v79Lzb?tG<MhDg7
zbM2Z6wgsFzZ)wUPw0Z9B!?%|e@2a1fkg2q;LbrBCtEQCcOr|}#>91ax+&a=QqkGR4
z*<FgUdzMt6GdsGu`$v*VX6UT22&Yv_jjGw1Z#w5FINacG;PI(eUNm*d8}{?>Wj@4A
zl5^2Fp3<1qcqk>b<JY_bhEI3zCU=$>PCjyFaqE{D{m?lj8|>!Z*f+(Ep|DT#XXftR
z)@4gLBWCbV5#tv1(Kj=Vh%gU5S>M<i^K5bD%Fg?SjjGNq>JrLJi}wkg+qkmqX-Uux
zvFL;uC-(-HuH}9Ic#h?=$DIaGKS!J>Q!~(9s`P+omUDuTzgBFAzn$ohs-nCazxLQB
z$%e>@mB(cnQoq#9H{(3OJ6k~K?&+x_;!Rr@91;0o%lo{BRpy~#WXy!9jr%QH4H&{$
zQs-M#pWHi}^{&>EkSS}Axva7{Rq6InNrN{p*h7O;wl{d5A;ZqrlIY0iT+`2=)}1!(
zYTko6?%tD5Xp}?+c_igKXCFQ={SJF<iJnt&>y-{!9kW%3yV4c7FI*Q*yY{4OcJ<|F
z&lRpQ{VhH8@wtJ)M)udu*<bFe6wO$-bBk=m*Xt3TvdM*u)w<_jKk@WZ%KJozCoH$-
zd<^P3U~X@~I7#8uC(GMw#jBfx4r&>^d>AV8<o=}fhQHI_T*y26d!Ofna|Nr?uI=}j
z>m<$|)2F+D+49LRr;?_|i7P%EoeZD;RCfiJds5Cc!S(qZf`_cUJWl+u=3?lQS{fmo
zTi_rj)Fk-pBlDBzs~@ghx2E?Gn@!oJ3=TsJ_J<1(`fX~RBh2e9=%Ux6T+tp|VZLS3
zuUnG^F09#Mny20R&DGWWPG7~8No<O`Q}1uIX56sLF@ekezGuw)9fw_=-z;T(p`X)!
zc52D>S8}fyeC4*=oKW$b{$#0RiY>p(H)~M?xxcpk2RF`HF2FVMbUm-poTEvX8~vY1
zdOd6T;?Niu@L<wYHNT0>6KCDL{or>S=cCfC=O!PLp31f_QSxKLL@)Vz$AyQ_y;-Lh
z|23`K|IU>y-porMuAbQ<XJGays>9{^w3bb;jy~9_*q9kKL+paSu;eY~%_)wFAspwE
z_qe&e(PcZQ%M%!xX#JqIplIu%MkYDEAe|`-g?VgRIxbosN&9$9o~I$*TjHkrU)Iek
zpQn0eD+H?X`x!ZjK1l33cFy$mk%;YqnnhYOl^1K|ZwzU7O4xYxcP7)-!nW_9IJl(@
zK73HIG`OO6fw6YUZ{K6n8;&vAPik+w{Mp1|)f=v*2j;D^xVS0dL9V;wRWIMj(tQ#q
z(;gqKi?~*CasQ6|_ORJMSi@%d9F^S_FQ9*NTH2N^-1Qu_UDod-H!fIvd|TNuHbFr-
zaSLI_?weX|E2ET_csz1aw2Eo5h@DfJXzsMef`yg4^50{H3UQwA@jT0(+`Zs0X>k6I
zPKfsOBEb!f{4@66dulYXqHWT|rU~uteWf?r<|j=2RP}_xZkAJoSHzMRH77dW&T-B=
zaKfD7%ByvfmkXujgdh5+U3_o+q{x*oXibvd<_Hb0winwSi{?l3omnX?snO3h-={B~
z)p>5xKh^XVMiZ0dR*UVwvhn@fC$Ukkev72)4%i1>@^TW8+1D}M=F+tTg2_Gtfm2?l
zueh7xpCEX7r{E(!)`vM~lRKkRVqXb8vzJsB`r;>&6THFW8QWpWmnU12T=qU+W+SE3
z&v=0OR_DwXdCrSd{4M?7sc~A&&zskO$!%R?QgFtCvnxamIExPaykX7U@!`Q9f&W5_
zBriXGpP*e7EWvI6^-PrOYS9y_s{#t=DvArf*v9`%%g~PRjm>_=WfSuc#5YDRK9j#Q
zZ9xx{jj&vU3d>EeQ>;%uO-%_}Yo;i#$;UO}Z045FR!8%i4!<)KSm+b}GgpVpG-s>4
z<deuXW*^RFSmoGGKczM$!O-aehgflAOzE)|wkphLViXdeoe|~NHw<#^RlTcyt?^<*
zQH*o)6ZTunk4?GX^*K@ML0F;5=AgGL*OcCxe(hz#)VNjJ-t)g0FZB@4_tiI=Vjer?
z9{==@92SbxcY0eN&a<-ryCqKagh98kLd~AiWlERyznDsy)s`IEYnaVc<<2y@AR|$@
zap4yUmvv6UCO39xAJe!Oa#vmK#cBoKm&d1`S+8@Etvz_IhRffIS9yy(BzoT@&#PSf
z!1K+$jD;R&wX&OiI8sZ`9@734vE{MWu?t^0ALjr6qkrnt(<v)LKdfK(|E27=d2i?b
zx2^oM#qR%ay|-MK*Vf%`*?HYB@q7G+)oc-}`JL5$$F?RPNRzO;sBiEtuIxqV;lg!m
zF2z_+abTFSs>pPX&7rpNB^kW!(fJIAEqnep2l4AmEr0N1+nW6h)fWqVbG|JyJl-zb
z$NztEmD;6*|0Q#?kKcc)R?uSCZR8*)R=)p&K;=~KrPJ(qtsT#)9iKk8Ex9P>#(s<9
z9FdsJ=$;SX{PpF^=b4|e&AgG*k;K<ys^`7Cdvi^BVaX9a-~1n6c07~{c)jw&&7&0^
zp2?F{THh}{wMORGq8rnC&Tebjbx_av{&W@I6c@!#KPmQKw=y53889hHsqDzNiOaI+
zo&K`P*GJ>;>IGZWHF|HE{xN&=|LOVlw+?^4-2Fc2|IO-OKOUT{`+NNI@zTHVzpwni
zMf2!4{bOeG8xQ-PKltZ^;ObXx!44B9Sv(f`=1^F6WZ$t1R%R#i_RG$d-FU}t=lz<r
z_@mLAg75d;lF~e$lW^<F)V_$5p&RY%?zsLvxpCQ^S}o>;wQDz&^>P0%JhJbMc*w4r
zdx{|k_8%~`Tzu#H%y#!4o4LVWf3_P&HXciw<Z2-5v$51sIIm!NlY2_3V_<ARh`7jA
kR#)<wcVC5V-*t6uUg_#+5}3DbZ~bSI&z*XbVF3dJ0FFuAA^-pY

literal 0
HcmV?d00001

diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..ccba303
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,22 @@
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"
+
+[project]
+name = "omnibot_client"
+version = "0.0.1"
+authors = [
+  { name="Felix Agner", email="felix.agner@control.lth.se" },
+]
+description = "A python package for connecting to omnibots via TCP."
+readme = "README.md"
+requires-python = ">=3.7"
+classifiers = [
+    "Programming Language :: Python :: 3",
+    "License :: OSI Approved :: MIT License",
+    "Operating System :: OS Independent",
+]
+
+[project.urls]
+"Homepage" = "https://gitlab.control.lth.se/processes/omnibot/omnibotclient.py"
+"Bug Tracker" = "https://gitlab.control.lth.se/processes/omnibot/omnibotclient.py/issues"
diff --git a/src/omnibot_client/ControllerTest.py b/src/omnibot_client/ControllerTest.py
new file mode 100644
index 0000000..3e81f50
--- /dev/null
+++ b/src/omnibot_client/ControllerTest.py
@@ -0,0 +1,120 @@
+# general
+import numpy as np
+import sys
+from time import time, sleep
+
+# threads
+import threading
+
+# Controller
+from inputs import get_gamepad
+
+from Omnibot import OmniBot
+
+class ControllerState:
+    """
+    Class that logs the controller state
+    """
+    def __init__(self):
+        """ Initialize the controller """
+        self.x = 128
+        self.y = 128
+        self.r = 128
+        self.on = True
+        
+    def set_x(self,x):
+        self.x = x
+    def set_y(self,y):
+        self.y = y
+    def set_r(self,r):
+        self.r = r
+    def state_data(self):
+        return 'x'+str(self.x)+'y'+str(self.y)+'r'+str(self.r)
+    def set_state(self,data):
+        s = str(data)
+        ind = [s.find(i) for i in ['x','y','r']]
+        self.set_x(int(s[ind[0]+1:ind[1]]))
+        self.set_y(int(s[ind[1]+1:ind[2]]))
+        self.set_r(int(s[ind[2]+1:-1]))
+        
+def log_controller(state):
+    """
+    Function that continuously listens to any event from the controller, and logs the change in
+    the controller state "state"
+    """
+    while 1:
+        events = get_gamepad()
+        for event in events:
+            if event.code == "ABS_X":
+                state.set_x(event.state)
+            elif event.code == "ABS_Y":
+                state.set_y(event.state)
+            elif event.code == "ABS_Z":
+                state.set_r(event.state)
+            elif event.code == "BTN_PINKIE" and event.state == 1:
+                # The "BTN_PINKIE" (right index trigger) 
+                # is currently used as an off-button.
+                state.on = False
+
+
+
+def run_omnibot_with_controller(HOST,verbose=False):
+    """
+    Connect to an omnibot at ip HOST and proceed to steer it with a controller and print current position of omnibot.
+    """
+    
+    # Robot parameters
+    R = 0.16 		# Distance between center of robot and wheels
+    a1 = 2*np.pi/3 	# Angle between x axis and first wheel
+    a2 = 4*np.pi/3  # Angle between x axis and second wheel
+    r = 0.028*0.45/18 # Wheel radius. Has been fudge-factored because the actual velocity of the wheels did not align with the set-points.
+
+    
+    vel_factor = 6 # Fudge factor that changes the relative velocity of the bot
+    
+    def phidot(xdot,ang):
+        """Returns reference velocities for the wheels, given current system state and reference velocities"""
+        M = -1/r*np.array([[-np.sin(ang), np.cos(ang), R ],[-np.sin(ang+a1), np.cos(ang+a1), R],[-np.sin(ang+a2), np.cos(ang+a2), R]])
+        return M.dot(xdot) 
+    
+    # Start loggin controller
+    state = ControllerState()
+    t = threading.Thread(target=log_controller, args=(state,), daemon=True)
+    t.start()
+    
+    ts = 0.1 # sampling time
+    
+    with OmniBot(HOST,verbose=verbose) as bot:
+        no_error = True
+        while state.on and no_error:
+            t0 = time()
+
+            # Get gamepad updates.
+            # Use some scaling to turn the controller state values into
+            # reasonable velocities.
+            w = (128-state.r)*np.pi/1000
+            vy = (128-state.x)/1500
+            vx = (128-state.y)/1500
+            dphi = vel_factor*phidot([vx,vy,w],0.0)
+            
+            for i,v in enumerate(dphi):
+                bot.set_speed(i,round(v))
+                    
+            print('x:'+bot.get_x())
+            print('y:'+bot.get_y())
+
+            sleep(max(0,t0+ts-time()))
+                    
+
+if __name__ == '__main__':
+
+    # Server settings
+    HOST = "localhost"
+
+    if len(sys.argv) > 1:
+        # If an input is given to the script, it will be interpreted as the intended
+        # IP-address. Baseline is localhost.
+        HOST = sys.argv[1]
+    
+    print(f"HOST: \t {HOST}")
+    run_omnibot_with_controller(HOST)
\ No newline at end of file
diff --git a/src/omnibot_client/Dummybot.py b/src/omnibot_client/Dummybot.py
new file mode 100644
index 0000000..d710439
--- /dev/null
+++ b/src/omnibot_client/Dummybot.py
@@ -0,0 +1,40 @@
+# general
+import sys
+from time import time, sleep
+
+from Omnibot import OmniBot
+
+
+def run_dummybot(HOST,verbose=True):
+    """
+    Runs a dummybot at host HOST that rotates a bit while printing x and y coordinates.
+    """
+    
+    ts = 0.1 # sampling time
+    tstart = time()
+
+    with OmniBot(HOST,verbose=verbose) as bot:
+        print("Connected")
+        
+        while time() < tstart + 5:
+            t0 = time()
+
+            bot.set_speed([])
+            
+                    
+            print('x:'+bot.get_x())
+            print('y:'+bot.get_y())
+
+            sleep(max(0,t0+ts-time()))
+                    
+if __name__ == '__main__':
+    # Server settings
+    HOST = "localhost"
+
+    if len(sys.argv) > 1:
+        # If an input is given to the script, it will be interpreted as the intended
+        # IP-address. Baseline is localhost.
+        HOST = sys.argv[1]
+    
+    print(f"HOST: \t {HOST}")
+    run_dummybot(HOST)
\ No newline at end of file
diff --git a/src/omnibot_client/Omnibot.py b/src/omnibot_client/Omnibot.py
new file mode 100644
index 0000000..4482668
--- /dev/null
+++ b/src/omnibot_client/Omnibot.py
@@ -0,0 +1,159 @@
+import socket
+        
+class OmniBot(object):
+    """
+    A class that implements a servo-client which is capable of sending servo speed-settings
+    to an omnibot. 
+
+    HOST: Host name of server (string)
+    PORT: Port to connect to (int) (default 9998)
+    verbose: choose level of chit-chat (boolean)
+    """
+    def __init__(self,HOST,PORT=9998,verbose=False):
+        self.HOST = HOST
+        self.PORT = PORT
+        self.verbose=verbose
+        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+
+    def __enter__(self):
+        """
+        Boot up the socket and connect to the omnibot server
+        """
+        if self.verbose:
+            print("Connecting to HOST: " + str(self.HOST) + ", PORT: " + str(self.PORT))
+
+        self.sock.connect((self.HOST, self.PORT)) # Connect to the robot
+        if self.verbose:
+            print("Connection successful.")
+
+
+        return self
+    
+    def __exit__(self, exc_type, exc_value, exc_tb):
+        """
+        Close down the socket after closing the client
+        """
+        if self.verbose:
+            print("Closing down socket")
+        self.sock.close()
+
+    def send_and_receive(self,message):
+        """Send a message to the omnibot and return the answer that the omnibot gave."""
+        # Send the package                      
+        self.sock.sendall(bytes(message,'utf-8'))
+        # Receive confirmation
+        ret=self.sock.recv(1024).decode('utf-8')
+
+        return ret
+
+    def set_speed(self,i,v):
+        """
+        Set the speed of servo i to v
+
+
+        Communicates with messages on the form "wivvvv"
+        where w stands for "write"self.
+        i stands for index of desired servo
+        vvvv stands for requested target speed for the servo
+        """
+
+        package = 'w'+str(i)+str(v)
+        if self.verbose:
+            print("Sending " + package)
+
+
+        ret = self.send_and_receive(package)
+
+        if ret[0] == "e":
+            raise Exception(ret)
+        elif self.verbose:
+            print(ret)
+
+    def set_speeds(self,v):
+        """ 
+        Set the speed of the servos to the values in vector v
+        """
+        for (i,vi) in enumerate(v):
+            self.set_speed(i,vi)
+
+    def get_x(self):
+        """Get x coordinate
+        
+        Sends a message "rx"
+        where "r" stands for "read" and "x" stands for "x"
+        """
+        if self.verbose:
+            print("Requesting x")                 
+
+        ret = self.send_and_receive("rx")
+        if ret[0] == "e":
+            raise Exception(ret)
+
+        if self.verbose:
+            print("x: "+ret)
+
+        return float(ret)
+
+    def get_y(self):
+        """Get y coordinate
+        
+        Sends a message "ry"
+        where "r" stands for "read" and "y" stands for "y"
+        """
+        if self.verbose:
+            print("Requesting y")                 
+
+        ret = self.send_and_receive("ry")
+        if ret[0] == "e":
+            raise Exception(ret)
+
+        if self.verbose:
+            print("y: "+ret)
+
+        return float(ret)
+        
+    def get_z(self):
+        """Get z coordinate
+        
+        Sends a message "rz"
+        where "r" stands for "read" and "z" stands for "z"
+        """
+        if self.verbose:
+            print("Requesting z")                 
+
+        ret = self.send_and_receive("rz")
+        if ret[0] == "e":
+            raise Exception(ret)
+
+        if self.verbose:
+            print("z: "+ret)
+
+        return float(ret)
+
+    def get_theta(self):
+        """Get x coordinate
+        
+        Sends a message "rtheta"
+        where "r" stands for "read" and "theta" stands for "theta"
+        """
+        if self.verbose:
+            print("Requesting theta")                 
+
+        ret = self.send_and_receive("rtheta")
+        if ret[0] == "e":
+            raise Exception(ret)
+
+        if self.verbose:
+            print("theta: "+ret)
+
+        return float(ret)
+
+
+    def get_state(self):
+        """"Get state vector of [x y theta]^T."""
+
+        x = self.get_x()
+        y = self.get_y()
+        theta = self.get_theta()
+
+        return [x,y,theta]
diff --git a/src/omnibot_client/__init__.py b/src/omnibot_client/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/test/gitkeep b/test/gitkeep
new file mode 100644
index 0000000..e69de29
-- 
GitLab