From 535ad634ab17a659c144cf4aef0833e9e2aad637 Mon Sep 17 00:00:00 2001 From: lgv Date: Mon, 14 Sep 2026 16:14:28 +0800 Subject: [PATCH] feat:add fill current pose & joint --- .../arm_motion_client.cpython-39.pyc | Bin 0 -> 4635 bytes .../__pycache__/base_client.cpython-39.pyc | Bin 2052 -> 2052 bytes .../__pycache__/hand_client.cpython-39.pyc | Bin 2320 -> 2320 bytes .../__pycache__/movej_client.cpython-39.pyc | Bin 7055 -> 7079 bytes clients/arm_motion_client.py | 134 ++++++++ clients/base_client.py | 2 +- clients/hand_client.py | 2 +- ui/__pycache__/app.cpython-313.pyc | Bin 48863 -> 66097 bytes ui/__pycache__/run_action.cpython-39.pyc | Bin 2483 -> 4392 bytes ui/app.py | 289 +++++++++++++++++- ui/run_action.py | 145 +++++++-- 11 files changed, 546 insertions(+), 26 deletions(-) create mode 100644 clients/__pycache__/arm_motion_client.cpython-39.pyc create mode 100644 clients/arm_motion_client.py diff --git a/clients/__pycache__/arm_motion_client.cpython-39.pyc b/clients/__pycache__/arm_motion_client.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b4267b64450158e098b2a399b9d5bc813d83d18 GIT binary patch literal 4635 zcmb_gOK%*<5uW$VE_eA5B}I}Yi+*cln^c?!5<}68de{uPQb^g3M^Prj?IE?}*_~0( zEM+cMfC1?c7~mnt#0o$jbhhqQ>ZBqH#%4)a=(_n8MUpX{w&860gNtQ}^_y;Tf{6$7a*= zEL91uM~ajfvE6h$NAhM|YL>mScH2n(6~ef`~#R!@l>XG8dI6Z^d}lKnEBZ7bhIsI zqwTN~+6LNXHimYERna!t>uic0!jI(BPfT{09eHee7JGvomvZW%z7tPu_5wSJKAW8x z(K!X37ujjZ9p?6Q3XPd37JG@kjGhuZE9anfy~19Fq|9D}vELLEmw9t3AtM(?5_*>N z&dkstv-5?i-U|a3^4aC^ej65sLrN^%Xw*Z$!Gy)byvib=9us>$$OQqNR8BZEFXZ>S~d_?R51WAh4^gC{NXzTjFG6uexUq!tO1CPok4s zG@^97?|5(gMwrjHIfutdC`toNc+Py1ZeHg}(}kC_FkX{02dg&4ckXQf;gc*4>fzivPwReNLTaw~lg$yPn$nEDp4oI$*nTnbam<)i zKoqsCTB?IzMeTfNpXFzJqlMmRk$ufI9yCLa$W`_jmW)ism4-bECReuAt~BkozN6$u zWbVLx{1CcaOH{8fUtYTAU-{Ut;jhrw!(5o~aU&IGoID7*(9%^getCJx?~S~?a?REF z5vcQ%M93fop^XU3mkWCdd?p>Pc8)%?6*)v0wWJkdD^^e^gcW-sQ1l<#2l-F#(I{?O8Giz}Y zV3F7y3pR3q7dUi|CW^q?4ro*kI_DF1-zg;vUIId#rTTtI8vl z>09bU6H+bGyUMoFHFnf4waqR$+1S#Uxurj}&}T8w$c~&YQYdq_v>lDk6}YlgtCOYs z58;p&u|mm)@Vc&amAjJ<)f^vX%5i-5H`;vzimK3gz@Do7dl>ap6WZ)Kp+@^nUp`NJ zMG{Oeyn}In!>e@M{kCo<8(}niK2CtA>;jm+j;nf3nq&c)aWJ$e|Z;A@+7RETo zWRG6J#L5FsQE4YBi8Em?Vd90a$<UY1T=gU3-@e$frXZirdxwbH1ws5#0QNe+aV;U}Nw6h`|$u&yY z5zP9J17-}bgx%+02zHSM5b0fO2RMj~+yoX>!h+VQ-lOKe^Fg8{eiuC%Wh|vcC!T?m zZ}8hS97EOU_pM<%f3>C##!igPQ|*V2Og<8%!eQ+qfc~2dMC&Ig{O1hR%RYw8JSH>Y zOe?aHd60WXoay|JnFlFHdh{d0A^RoCe*)4uyFZ$;6vx1U*@pid3Vm!0 z5%UqG!dfA%}TO|aLWMZgzM9gX;M5%B?O58UTX4I;kp=OXI&1$^P3#r~1-Tzfy?xA8rE zIC~L9aSn=h^0M=HYy3H0j`!5JduqZ6QzEzG6yH&CaVT=Sykl)?s1s2W$_f!>LvEmW zF!xl5I!WM9vbS z1CqZ*gh~`Rnoi^*x0bWPl>BXy&Jv-ZK>W(OP$HWw?HN=Y?n@G4j3(&aG(N+q!L?!% z!6Ylf7oJHRLya1alylJH-z7qo4PdrFyd*y*t~}_{i(#@$jx|Qc4u`45L}SA7{T6O4 zzAwr?mdM&M@fF{{-wxuUr!d73fA7=BJ|N-|StU{@GR%#Vql*WpnuA&y8O>2Aok@MN zjEkeH{PWl$`Iq>AoIKr=9qGw-PaKoFHTQ^zm94dLSH;RW6^+8mH|c)~Tt|Zw^u%=U zPB$z6tKet2+NPX}OK)O5N!H_VHswj4thU#RtLI=m{o9~-`{R^~Jcq81vq9S64$V&0 zo4n8Rm#KH;%60qZ%|&lwcm@0D=JMjz(E%T@~ literal 0 HcmV?d00001 diff --git a/clients/__pycache__/base_client.cpython-39.pyc b/clients/__pycache__/base_client.cpython-39.pyc index 806cfb0657e144c1d837c6f599ad3bb025192b1c..51d90ea226d75ae572f1cf07ecd4686aac6574a9 100644 GIT binary patch delta 91 zcmZn>Xc6E}0T3ZGIf31jO$x*mpFEpA69C*p6wd$v delta 91 zcmZn>Xc6E}Vr0Cv`9D)O6XUJP*{sq)vYl0#RSc-? l*5>uBc8rV)li#tWbAtqm1VDts?c2Q=}>|ADRquLy~E5CX(Gb{Jt!{PR8ceE-h1KR)r*iJGbF z8iDK8)mOa3FMnJ6zR;|j%r~si_szgIqV*&5R8GN`_`UGiR++h-E;m553g|YxYVgD` zOK`_|N(d(&wW+OqND_L6Y*Rb8Lk9(}c)8vDeKJs!e4-i!uI?5=DkjC<(tScnqI%_B zEh*lo6eCnTuu2NBswPU3v-3M!ie1>r4N7-eLOk88B_&(6i#y7o%>F>cor?F;gyGL*)W%7`?cRR5K(lAx6*d}49)Z%PI_ zC3mO6c_Pub6-us>Ys4-O^bhnv%bts55q2(<`N@Z07hk7ad7$MoQZoh6AqiQl4roGc zGD|*D?&QIc${aK8JuJY(<+=6T4-NJ|!NM+dV2vQ5d`24FgD3+HiWIBgH7(2O zIm}EoIjMFX({`9txyd>Xm&GtbP5cW^8LBOE*LT7`DB?bN#_@{O9mYat_#l`aM-nLx zXzt>qRHAh`wH&$uzckFel$|Yxv7|nf-pmq;d)mwmSzd+kF*p54*3LP6sm~a6h?}yE znKM;sDU3GXV4*Jy7og{5sb2`O%8Oa8G9UB)x-1|c%d!YHpNwrYqY)uT13dB}_rn7+ftANHy&|H5N=#tf+3=;F(mO?U?0 zuUN&LkV~WNyr_@=a4_ux-@`qH9mjkp0DguWuh-Z)QJgru|2(Xv7DGHU==7XemeaUl zkMmPZv#jzP0qu<)nzF~mxVobTD^h>SH**=v&Y}TGxyTGTgw(M$&U* z&azri-N@8P)u@4u{tm*UTG=fWv-+C2{2y^?n6skPT%evf-aL8;X@QwN%CtwbR!15X zA2nz7H5|JF#zbWSc((Vk+Zerv;3JHB{Mnp65{!6+U?59|;hWH)k&b4bt_=UjWZpM< zmt7I(W?pQ{dM6CwbhIKC@^HO>i{W9D;?tRD$XHnD=RfD!}y|e2$oo zB7W{tUfDV-CSfkdQL!}394fP!a46) LH7%Q6o6-T*Sq%G>(p-koc^LBkb$_NElp@kXmS4tZJ<98x(!-wb|&^3uXi&$ z4obBqhg71M3ZXr4K%n$gg%c7jqrK2g?kx}T6kM}fO=}AM5-loWjiAz zEz-8t2MD7`ZE4%tNPB=W;<=XIk|OmUisUFm?To2=DyU+aU;sL zibN&amfX_^QkLX|l_Y9g5JvUIbwEIwd4R2~93wt3QPYX0q zgx=fV!;pjs6I4NhxPtB>BHfoik=7KUC?|T$cRSqb*d9$}W|OMSNp+KuWTBk-)ELb z!_aQf1etMBcQ;E6&Vgyv`={t~s&8u7=~|bDJjoHd?mEm3`-(#bol({NAUoe%BcUQ~|gt3rl4a#lKWybc$2lCa;)Ma)+ zOlL;`2Lby95{ZYV%;=h_Fd7EzDbZ)<2C;+<(|EkB6;da`lud%_1fT-?N=Q3oT(rWP z05Zz5j#Zo$pMtDnssEAxRV`t{0ALgRR}YTVP6lco3JyN>fY&V>M~J?H`W+C?CNKaeM5G_?x|3=Ux`d>=fV(;4}ahXRiQ`2~J61@qx2KT#8lciCgjib?XY8@t=kG~(iaPn1XUKH4o*(GR$d+ah`D1IR(`oJ&S zWk=)rv1xpBw>@?cKl6qd9A1=N0p2PR*$X86BFi0 z!}l9)S__!Z{gv)&zv|*ICy$>nv*MayH9K(Al4W`D1?r0x1Vo7h4l_VJ3s?la8J8** z^(|;#i)Sk1>f6wKC%#e%H`Dw={>@V8b{clu>R)DXr}VceP5wF1xFC>>a8dZfljdM( o0dNzr1h@@=fY}Rx^wgx=4aaCGbCd9I#6)3DP2*F$Yf~rjKR?2*e*gdg diff --git a/clients/arm_motion_client.py b/clients/arm_motion_client.py new file mode 100644 index 0000000..6b53064 --- /dev/null +++ b/clients/arm_motion_client.py @@ -0,0 +1,134 @@ +from __future__ import annotations + +from typing import Iterable, Mapping + +from clients._path_setup import ensure_paths + +ensure_paths() + +from google.protobuf import timestamp_pb2 + +from clients.base_client import RobotClientBase +from cmvr.api import arm_command_pb2 as pb +from cmvr.api import common_pb2 + + +FRAME_BY_NAME = { + "base": pb.ARM_FRAME_BASE, + "tool": pb.ARM_FRAME_TOOL, + "world": pb.ARM_FRAME_WORLD, + "user": pb.ARM_FRAME_USER, +} + + +class ArmMotionClient(RobotClientBase): + def _header(self, device_id: str) -> common_pb2.CommandHeader.Request: + header = common_pb2.CommandHeader.Request() + header.device_id = device_id + ts = timestamp_pb2.Timestamp() + ts.GetCurrentTime() + header.timestamp.CopyFrom(ts) + return header + + def _frame(self, frame: str) -> int: + return FRAME_BY_NAME.get(frame.strip().lower(), pb.ARM_FRAME_BASE) + + def _print_feedback(self, action: str, response) -> None: + header = response.header + print(f"{action} RPC call succeeded") + print(f"Success: {getattr(header, 'success', None)}") + print(f"Error message: {getattr(header, 'error_message', '')}") + print(f"Timestamp: {getattr(header.timestamp, 'seconds', 0)}") + + def movej( + self, + joint_list: Iterable[Mapping[str, float]], + vel: float = 1.0, + acc: float = 0.5, + device_id: str = "right_arm", + ) -> None: + target = pb.JointPositionCommand(position=[float(j["rad"]) for j in joint_list]) + req = pb.MoveJ.Request( + header=self._header(device_id), + target=target, + options=pb.MotionOptions(velocity=vel, acceleration=acc), + ) + self._print_feedback("MoveJ", self.stub.moveJ(req, timeout=10)) + + def movel( + self, + pose: Mapping[str, float], + vel: float = 0.2, + acc: float = 0.2, + blend_radius: float = 0.0, + frame: str = "base", + device_id: str = "right_arm", + ) -> None: + req = pb.MoveL.Request( + header=self._header(device_id), + target=pb.CartesianPose( + x=float(pose["x"]), + y=float(pose["y"]), + z=float(pose["z"]), + rx=float(pose["rx"]), + ry=float(pose["ry"]), + rz=float(pose["rz"]), + ), + options=pb.MotionOptions( + velocity=vel, + acceleration=acc, + blend_radius=blend_radius, + ), + frame=self._frame(frame), + ) + self._print_feedback("MoveL", self.stub.moveL(req, timeout=10)) + + def speedj( + self, + velocity_list: Iterable[Mapping[str, float]], + acc: float = 0.5, + duration: float = 0.2, + device_id: str = "right_arm", + ) -> None: + velocity = pb.JointVelocityCommand( + velocity=[float(j["velocity"]) for j in velocity_list] + ) + req = pb.SpeedJ.Request( + header=self._header(device_id), + velocity=velocity, + acceleration=acc, + duration=duration, + ) + self._print_feedback("SpeedJ", self.stub.speedJ(req, timeout=10)) + + def speedl( + self, + velocity: Mapping[str, float], + acc: float = 0.2, + duration: float = 0.2, + frame: str = "base", + device_id: str = "right_arm", + ) -> None: + req = pb.SpeedL.Request( + header=self._header(device_id), + velocity=pb.CartesianVelocity( + vx=float(velocity["vx"]), + vy=float(velocity["vy"]), + vz=float(velocity["vz"]), + wx=float(velocity["wx"]), + wy=float(velocity["wy"]), + wz=float(velocity["wz"]), + ), + acceleration=acc, + duration=duration, + frame=self._frame(frame), + ) + self._print_feedback("SpeedL", self.stub.speedL(req, timeout=10)) + + def stop_motion(self, device_id: str = "right_arm") -> None: + req = self._header(device_id) + resp = self.stub.stopMotion(req, timeout=10) + print("StopMotion RPC call succeeded") + print(f"Success: {getattr(resp, 'success', None)}") + print(f"Error message: {getattr(resp, 'error_message', '')}") + print(f"Timestamp: {getattr(resp.timestamp, 'seconds', 0)}") diff --git a/clients/base_client.py b/clients/base_client.py index 6925ee4..87ef91b 100644 --- a/clients/base_client.py +++ b/clients/base_client.py @@ -11,7 +11,7 @@ from cmvr.api import arm_service_pb2_grpc class RobotClientBase: - def __init__(self, address="192.168.0.222:50052", timeout=2, retries=1): + def __init__(self, address="192.168.0.233:50052", timeout=2, retries=1): self.address = address self.timeout = timeout self.retries = retries diff --git a/clients/hand_client.py b/clients/hand_client.py index 729203f..75e0951 100644 --- a/clients/hand_client.py +++ b/clients/hand_client.py @@ -104,7 +104,7 @@ if __name__ == "__main__": # ] # client.set_angles(hand_open, device_id="hand2") - client.set_angles(hand_touch, device_id="hand2") + client.set_angles(hand_open, device_id="hand2") # client.set_angles(handshake, device_id="hand1") # Close client diff --git a/ui/__pycache__/app.cpython-313.pyc b/ui/__pycache__/app.cpython-313.pyc index 628103de0fa6576f79b8d47aed222141a9aa2974..fbccee7fbae3d7b3f222bc9d92684dfb90bf165f 100644 GIT binary patch delta 14538 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 delta 4182 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 WZ6K~^Y2@n{7#qmj;$9EsEa$)JDusRk diff --git a/ui/__pycache__/run_action.cpython-39.pyc b/ui/__pycache__/run_action.cpython-39.pyc index b0b6771e978fb20b7acdc45ed3034e18ceddd524..e2bb397088390890d209d154d6eb689031233979 100644 GIT binary patch literal 4392 zcmb7H&2JmW72hu|mlR3KvaByVHWMedX?rDEar_<2vMn1*9YwHR$0b@2YtE9QyyP;o zOG;djXaVIapvSgx3%j@c9X+*&9(vqUffguoD^Q<|6zK2Ga^;Pk#O@OB?ac4Jd2i;; z&b&t}pU+Bo^uMfZUOXsC|DaCt(a^aD@>*a_VzMKZqqkg^(N`R$sg_k)veg|4T-wx_ z`b1_LOMR)8Q?`y@+BRk+raw_xni)Vdc2?+QSQfnzJI6*ID}2+IpXTk69qE}Y!_Pgb zl=}ugrL~eA=IoZ&;dZ5M`5PYkI=B4}Z&gViKQbCR=myC98+yL9DKnWVyYfKlOPdN) z>k`xcD6$cF+sn?ODi$hib^ULY3|$VGu%N+6d7awUO$ERd@SWJw^`63DVZt|yQi z0=bz$Rs?b@MxLggN%$geuLU!AJFOyjAGv<9>NM<@U$m-z!)M$xl* z*XBi1ppne3Sslmc1$?qJu>G^so7+W1em`>GgF)x6nN0w)sPNVg>Y^<-` zd9Zq~axWOa4zNgIvF7q3MJ33Xrae^5-L2eOUAw(?(rw%Ak~K zc5SDQndABQQZV|d2zC*mTW$Eg018&MI#l@kQm}uh0MK@9Zqe4zVpu0?4;S<2rC{y+9fU2C=g}M zcU`BP+H$$WLal>s`!Z+i`^wdXM!sPa7IiOtEG=BA6-Bc`4oO2NrbsE4?C zJ_WOB`Im~He73rF|I&lyJ3n2yoM;ECX>JE&FI`dKi`xYEUW0oC2d}|FpqVD$4)(ob z1rP~(uM2uY5WFrBrn=j~#7k50zF|_Z*6n?-Oc?A2-zyU~x3+^rFKt?_ddIQ&-Zv51 z-s=6Rj_|P+{HRV)(nG`I^|r;avxQ||?=*33tW%ela~5MbH4zr7*rPC2b6m?0vtj_r zka5bo=<%;5K7);ZaejWoZQAory*poRcKQ5*iErhx*S4zme4V$emFRAo?=vgrGe6yo+9-=lW$7DcsNk|DlagluXbf# z{G!lTAwv>(ORY&w@|E6Q86^Ocw>Hp!Cbg6Wsjoa$S*ow~ z<-XofD6zwif{R>Vdm&$bAz%82vNqobR?dp^B4&$onDW|A!{<|wgxLqQoE6Spj!V7W z#<4D?IjR+jA0m0QH#n#b-)@E)tv6J;wN=(zo#rE)UD~pU&656jw6l>v5i0Lv;NB#d zgbgWRTNG@Cf_F~N$`dj_4oQ{7@k3RU0TSUmi|G2&jwI>~`74BwYKyO=->U;^sZ2qA z^9QCf&C!~vfr1brERNnx52y}N2O6{t{?O0Ro9R2sAk|Othy4^JN?&Ga)G&H*y033$ z5lm@w1e62inL%}cU+8N)2=3=nUw=CK3|r93#;pbTx<|E+$}*3YU(0+lIgf<(Td+O` zy?vl@(0fQq1Z*_c>i>^EB7zTMOtS2qry3cvOPK4;d9?EI9l`%l=N zc1q2Y0|R!AJ-akVvio*(oPjNsFTmEUbE%t3v;3-s6;gZvH(>C+$1sGm7x)5y^KdAf#6a@3}K1uM(nw*7TXZx{kCG?1A@ z9VgVn43#MrD$T;Q9X0q!G_-JE)UBVjqOm*|!J|XCRvY3+2scH_C~H)aQb4%R=^tNt zrYd6Z&7q(imd3-B#!rxZB9@EFIu?~63WxVN?UT4Rqs`q5b2R@{n~PLib!ZQ0pZFZH zd14F1&JbH9_93ye#6-wwGolDm#Q6nc7l~aWcA3}}Vsxc*3T=5Ro@`v($Md}kX{dR~ zCwz&tuMt}YD^JHm;>y1jkGW3bz3{~1S}m4SLMUZJjp_z|k_=M7IE9AaBz6?6tVi}a z&480hPUBupR9e$&w0;7C_g64U*5z>}OG!!5(5Bi!Ltl}n<*YJ+Hc6(c=)s5=2X0tGix}MqRbK>F4TPkHst>SR`Fvr literal 2483 zcmb7G%WoS+7~k2Kcm0avq|KwRZHo|=x=ES?j}{S16RK1pjgqJ>E6}p_j2%1saA(JX ztn5>Ijf6NATBL+qFZ~;k_%HT^1P6NKz^MX>Z)WY__ENFYe3|d{`(|e6`>nlFDKEjZ zw0*t(xF|`#ab@&TK=~L(d>;gsF!CiQ`;Qz1y6nq=;wVTW%7Fw@YO7dzim-~cN2;R{ zU1&PaftDi%Hg;vYP0*1^atG2eLV&m>)yx-wF0I#4S|nlIrNr%cY$pa?r-XHBSm*xX zgJ7b=xCtZv83dEs2qP>XqQ2CV+A>y}5>|ggSi|}utWDT**uW;vpXddq;36*J@}WA2 z7{e7jJ`z!7DxScTBN3IaK+SKobq zZeeb5aWSbhH#crtF`;{eT0Ed8r#Va-Ue{;TgmdSKortp5qhCOIC7hdUN~*H`4x#*t zWeEk_#$*pl+b!&vwAI{Uu1AB!TozVqbven~b}M91)bpD^e9xa}OG#yQV1nh;qY%dl zAU*J6V*9Od*D*F$*6yxuxm!u~6EIrbXf+~g@l#1m+a`m+;->rQ>iVsfO?P{BBbi+Z zu}vBcQisEOElIQOJ>oZET}rB%A8f=Y)Z=d| zDcW{Bg3Z7pW0p$El`KZspj^!O8Uv4Avu@v6UEjX8d1Lwd$*iWQhDVznkH*9)-=NKI z0Jma;tJEoa7{kG3wp4){sn+l#kEMB$zly9xjTTU9`E*KCM7Df8+5xAS6^oDw2_oTS zj04m+?@8flRbu)5?-1}7@KCOAelCUbg4ENF3cXy<#Ku#ZGZdg`^m$K%x^fR;6Idpn zThqDC3RJ1d-^zb(+E_9VxJaSFz1T;lj|z%2{h$ACNLPX*KcBH$KJaAya&z;uUiUgNmH z`W>*U{!B32F9BBR|6^et7;a+yCAvm6c>k2sH!w*-G{a57b?F!*F>^&@xV$l5qU$es z+@QaBf|JkOp%;);p&o{>Lk`-2k~jvYQJ^zZ1==q)Glp?nnDVa#gq3a)wgF6#6$9cl zh73c`LPTnECV{%)Zki+6gyyo;^0r{qJK-P~i41CF-m!tV(HOYTh&%77oGl%dlOR3JQNWeT*Igcu`3NsbhhqI9ZqXMG{e|f<}D#XZW8s+6lq{?6yx(d6zg65Aey4P(3~t9 QtWidgets.QGroupBox: - group = QtWidgets.QGroupBox("MoveJ") + def _build_motion_group(self) -> QtWidgets.QGroupBox: + group = QtWidgets.QGroupBox("Motion") layout = QtWidgets.QVBoxLayout(group) + tabs = QtWidgets.QTabWidget() + tabs.addTab(self._build_movej_tab(), "MoveJ") + tabs.addTab(self._build_movel_tab(), "MoveL") + tabs.addTab(self._build_speedj_tab(), "SpeedJ") + tabs.addTab(self._build_speedl_tab(), "SpeedL") + layout.addWidget(tabs) + return group + + def _build_movej_tab(self) -> QtWidgets.QWidget: + tab = QtWidgets.QWidget() + layout = QtWidgets.QVBoxLayout(tab) params = QtWidgets.QFormLayout() params.addRow("MoveJ Vel", self.vel_edit) params.addRow("MoveJ Acc", self.acc_edit) layout.addLayout(params) layout.addWidget(self._build_movej_table()) + movej_actions = QtWidgets.QHBoxLayout() + self.movej_current_btn = QtWidgets.QPushButton("Fill Current Angles") + self.movej_current_btn.clicked.connect(self._on_fill_movej_current_angles) + movej_actions.addWidget(self.movej_current_btn) self.movej_btn = QtWidgets.QPushButton("Send MoveJ") self.movej_btn.clicked.connect(self._on_movej) - layout.addWidget(self.movej_btn) - return group + movej_actions.addWidget(self.movej_btn) + layout.addLayout(movej_actions) + return tab + + def _build_movel_tab(self) -> QtWidgets.QWidget: + tab = QtWidgets.QWidget() + layout = QtWidgets.QVBoxLayout(tab) + form = QtWidgets.QFormLayout() + self.movel_x_edit = self._build_double_spin(-10.0, 10.0, 0.0, decimals=6, step=0.001) + self.movel_y_edit = self._build_double_spin(-10.0, 10.0, 0.0, decimals=6, step=0.001) + self.movel_z_edit = self._build_double_spin(-10.0, 10.0, 0.0, decimals=6, step=0.001) + self.movel_rx_edit = self._build_double_spin(-6.283185, 6.283185, 0.0, decimals=6, step=0.01) + self.movel_ry_edit = self._build_double_spin(-6.283185, 6.283185, 0.0, decimals=6, step=0.01) + self.movel_rz_edit = self._build_double_spin(-6.283185, 6.283185, 0.0, decimals=6, step=0.01) + self.movel_vel_edit = self._build_double_spin(0.001, 10.0, 0.2, decimals=4, step=0.01) + self.movel_acc_edit = self._build_double_spin(0.001, 10.0, 0.2, decimals=4, step=0.01) + self.movel_blend_edit = self._build_double_spin(0.0, 10.0, 0.0, decimals=4, step=0.001) + self.movel_frame_combo = self._build_frame_combo() + form.addRow("X", self.movel_x_edit) + form.addRow("Y", self.movel_y_edit) + form.addRow("Z", self.movel_z_edit) + form.addRow("RX", self.movel_rx_edit) + form.addRow("RY", self.movel_ry_edit) + form.addRow("RZ", self.movel_rz_edit) + form.addRow("Vel", self.movel_vel_edit) + form.addRow("Acc", self.movel_acc_edit) + form.addRow("Blend Radius", self.movel_blend_edit) + form.addRow("Frame", self.movel_frame_combo) + layout.addLayout(form) + movel_actions = QtWidgets.QHBoxLayout() + self.movel_current_btn = QtWidgets.QPushButton("Fill Current Pose") + self.movel_current_btn.clicked.connect(self._on_fill_movel_current_pose) + movel_actions.addWidget(self.movel_current_btn) + self.movel_btn = QtWidgets.QPushButton("Send MoveL") + self.movel_btn.clicked.connect(self._on_movel) + movel_actions.addWidget(self.movel_btn) + layout.addLayout(movel_actions) + return tab + + def _build_speedj_tab(self) -> QtWidgets.QWidget: + tab = QtWidgets.QWidget() + layout = QtWidgets.QVBoxLayout(tab) + form = QtWidgets.QFormLayout() + self.speedj_acc_edit = self._build_double_spin(0.001, 10.0, 0.5, decimals=4, step=0.01) + self.speedj_duration_edit = self._build_double_spin(0.001, 10.0, 0.2, decimals=4, step=0.01) + form.addRow("Acc", self.speedj_acc_edit) + form.addRow("Duration", self.speedj_duration_edit) + layout.addLayout(form) + layout.addWidget(self._build_speedj_table()) + self.speedj_btn = QtWidgets.QPushButton("Send SpeedJ") + self.speedj_btn.clicked.connect(self._on_speedj) + layout.addWidget(self.speedj_btn) + return tab + + def _build_speedl_tab(self) -> QtWidgets.QWidget: + tab = QtWidgets.QWidget() + layout = QtWidgets.QVBoxLayout(tab) + form = QtWidgets.QFormLayout() + self.speedl_vx_edit = self._build_double_spin(-10.0, 10.0, 0.0, decimals=6, step=0.001) + self.speedl_vy_edit = self._build_double_spin(-10.0, 10.0, 0.0, decimals=6, step=0.001) + self.speedl_vz_edit = self._build_double_spin(-10.0, 10.0, 0.0, decimals=6, step=0.001) + self.speedl_wx_edit = self._build_double_spin(-10.0, 10.0, 0.0, decimals=6, step=0.001) + self.speedl_wy_edit = self._build_double_spin(-10.0, 10.0, 0.0, decimals=6, step=0.001) + self.speedl_wz_edit = self._build_double_spin(-10.0, 10.0, 0.0, decimals=6, step=0.001) + self.speedl_acc_edit = self._build_double_spin(0.001, 10.0, 0.2, decimals=4, step=0.01) + self.speedl_duration_edit = self._build_double_spin(0.001, 10.0, 0.2, decimals=4, step=0.01) + self.speedl_frame_combo = self._build_frame_combo() + form.addRow("VX", self.speedl_vx_edit) + form.addRow("VY", self.speedl_vy_edit) + form.addRow("VZ", self.speedl_vz_edit) + form.addRow("WX", self.speedl_wx_edit) + form.addRow("WY", self.speedl_wy_edit) + form.addRow("WZ", self.speedl_wz_edit) + form.addRow("Acc", self.speedl_acc_edit) + form.addRow("Duration", self.speedl_duration_edit) + form.addRow("Frame", self.speedl_frame_combo) + layout.addLayout(form) + self.speedl_btn = QtWidgets.QPushButton("Send SpeedL") + self.speedl_btn.clicked.connect(self._on_speedl) + layout.addWidget(self.speedl_btn) + return tab + + def _build_double_spin( + self, + minimum: float, + maximum: float, + value: float, + decimals: int = 3, + step: float = 0.001, + ) -> QtWidgets.QDoubleSpinBox: + spin = QtWidgets.QDoubleSpinBox() + spin.setDecimals(decimals) + spin.setRange(minimum, maximum) + spin.setSingleStep(step) + spin.setValue(value) + return spin + + def _build_frame_combo(self) -> QtWidgets.QComboBox: + combo = QtWidgets.QComboBox() + combo.addItems(["base", "tool", "world", "user"]) + return combo def _build_actions_group(self) -> QtWidgets.QGroupBox: group = QtWidgets.QGroupBox("Actions") @@ -303,12 +418,15 @@ class MainWindow(QtWidgets.QMainWindow): self.jointstate_toggle = self._build_switch_row("JointState") self.torque_on_btn = QtWidgets.QPushButton("Torque On") self.torque_off_btn = QtWidgets.QPushButton("Torque Off") + self.stop_motion_btn = QtWidgets.QPushButton("Stop Motion") self.jointstate_toggle.toggled.connect(self._on_jointstate_toggle) self.torque_on_btn.clicked.connect(self._on_torque_on) self.torque_off_btn.clicked.connect(self._on_torque_off) + self.stop_motion_btn.clicked.connect(self._on_stop_motion) layout.addLayout(self._wrap_switch_row(self.jointstate_toggle, "Start JointState")) layout.addWidget(self.torque_on_btn) layout.addWidget(self.torque_off_btn) + layout.addWidget(self.stop_motion_btn) return group def _build_switch_row(self, name: str) -> QtWidgets.QCheckBox: @@ -444,6 +562,23 @@ class MainWindow(QtWidgets.QMainWindow): self.movej_table.itemChanged.connect(self._on_movej_angle_changed) return self.movej_table + def _build_speedj_table(self) -> QtWidgets.QWidget: + self.speedj_table = QtWidgets.QTableWidget(len(self.joint_names), 3) + self.speedj_table.setHorizontalHeaderLabels(["Use", "Joint", "Velocity (rad/s)"]) + self.speedj_table.verticalHeader().setVisible(False) + self.speedj_table.setSizePolicy( + QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding + ) + for row, name in enumerate(self.joint_names): + use_item = QtWidgets.QTableWidgetItem() + use_item.setCheckState(QtCore.Qt.Unchecked) + self.speedj_table.setItem(row, 0, use_item) + self.speedj_table.setItem(row, 1, QtWidgets.QTableWidgetItem(name)) + self.speedj_table.setItem(row, 2, QtWidgets.QTableWidgetItem("0.0")) + self.speedj_table.horizontalHeader().setStretchLastSection(True) + self._fit_table_height(self.speedj_table) + return self.speedj_table + def _fit_table_height(self, table: QtWidgets.QTableWidget) -> None: header_height = table.horizontalHeader().height() row_height = table.verticalHeader().defaultSectionSize() @@ -503,6 +638,72 @@ class MainWindow(QtWidgets.QMainWindow): ] self._start_process(args + ["--joint-list", self._encode_joint_list(joint_list)]) + def _on_movel(self) -> None: + args = self._base_args() + [ + "movel", + "--x", + str(self.movel_x_edit.value()), + "--y", + str(self.movel_y_edit.value()), + "--z", + str(self.movel_z_edit.value()), + "--rx", + str(self.movel_rx_edit.value()), + "--ry", + str(self.movel_ry_edit.value()), + "--rz", + str(self.movel_rz_edit.value()), + "--vel", + str(self.movel_vel_edit.value()), + "--acc", + str(self.movel_acc_edit.value()), + "--blend-radius", + str(self.movel_blend_edit.value()), + "--frame", + self.movel_frame_combo.currentText(), + ] + self._start_process(args) + + def _on_speedj(self) -> None: + joint_list = self._get_selected_speedj_joints() + + if not joint_list: + self._append_log("[warn] No joints selected for SpeedJ.") + return + + args = self._base_args() + [ + "speedj", + "--acc", + str(self.speedj_acc_edit.value()), + "--duration", + str(self.speedj_duration_edit.value()), + ] + self._start_process(args + ["--joint-list", self._encode_joint_value_list(joint_list, "velocity")]) + + def _on_speedl(self) -> None: + args = self._base_args() + [ + "speedl", + "--vx", + str(self.speedl_vx_edit.value()), + "--vy", + str(self.speedl_vy_edit.value()), + "--vz", + str(self.speedl_vz_edit.value()), + "--wx", + str(self.speedl_wx_edit.value()), + "--wy", + str(self.speedl_wy_edit.value()), + "--wz", + str(self.speedl_wz_edit.value()), + "--acc", + str(self.speedl_acc_edit.value()), + "--duration", + str(self.speedl_duration_edit.value()), + "--frame", + self.speedl_frame_combo.currentText(), + ] + self._start_process(args) + def _on_get_pose(self) -> None: if self._joint_worker and self._joint_worker.isRunning(): self._append_log("[info] JointState already running.") @@ -531,6 +732,10 @@ class MainWindow(QtWidgets.QMainWindow): args = self._base_args() + ["torque_off"] self._start_process(args) + def _on_stop_motion(self) -> None: + args = self._base_args() + ["stop_motion"] + self._start_process(args) + def _on_jointstate_toggle(self, checked: bool) -> None: if checked: self._on_get_pose() @@ -547,9 +752,42 @@ class MainWindow(QtWidgets.QMainWindow): self._last_data = data self._render_joint_table_and_plot() + def _on_fill_movej_current_angles(self) -> None: + """Copy the latest JointState positions into the MoveJ command table.""" + if not self._last_data: + self._append_log("[warn] No joint state received. Start JointState first.") + return + + self._updating_movej_table = True + try: + filled = 0 + for row, name in enumerate(self.joint_names): + state = self._last_data.get(name) + if state is None: + continue + position = float(state[0]) + deg_item = self.movej_table.item(row, 2) + rad_item = self.movej_table.item(row, 3) + if deg_item is None: + deg_item = QtWidgets.QTableWidgetItem() + self.movej_table.setItem(row, 2, deg_item) + if rad_item is None: + rad_item = QtWidgets.QTableWidgetItem() + self.movej_table.setItem(row, 3, rad_item) + rad_item.setText(f"{position:.6f}") + deg_item.setText(f"{position * 180.0 / 3.141592653589793:.3f}") + filled += 1 + finally: + self._updating_movej_table = False + + self._append_log(f"[info] Filled {filled} MoveJ angle(s) from JointState.") + def _encode_joint_list(self, joint_list: list[dict]) -> str: return ";".join(f"{j['joint_name']}:{j['rad']}" for j in joint_list) + def _encode_joint_value_list(self, joint_list: list[dict], field_name: str) -> str: + return ";".join(f"{j['joint_name']}:{j[field_name]}" for j in joint_list) + def _get_selected_movej_joints(self) -> list[dict]: joint_list = [] for row, name in enumerate(self.joint_names): @@ -563,6 +801,19 @@ class MainWindow(QtWidgets.QMainWindow): joint_list.append({"joint_name": name, "rad": rad}) return joint_list + def _get_selected_speedj_joints(self) -> list[dict]: + joint_list = [] + for row, name in enumerate(self.joint_names): + use_item = self.speedj_table.item(row, 0) + if use_item and use_item.checkState() == QtCore.Qt.Checked: + velocity_item = self.speedj_table.item(row, 2) + try: + velocity = float(velocity_item.text()) if velocity_item else 0.0 + except ValueError: + velocity = 0.0 + joint_list.append({"joint_name": name, "velocity": velocity}) + return joint_list + def _on_movej_angle_changed(self, item: QtWidgets.QTableWidgetItem) -> None: if self._updating_movej_table: return @@ -629,11 +880,39 @@ class MainWindow(QtWidgets.QMainWindow): self._stop_pose_worker() def _update_pose(self, data: Dict[str, float]) -> None: + self._last_pose = dict(data) for key, value in data.items(): if key in self.pose_labels: self.pose_labels[key].setText(f"{key}: {value:.6f}") return + def _on_fill_movel_current_pose(self) -> None: + """Copy the latest GetPose result into the MoveL target fields.""" + if not self._last_pose: + self._append_log("[warn] No pose received. Start GetPose first.") + return + + fields = ( + ("x", self.movel_x_edit), + ("y", self.movel_y_edit), + ("z", self.movel_z_edit), + ("rx", self.movel_rx_edit), + ("ry", self.movel_ry_edit), + ("rz", self.movel_rz_edit), + ) + filled = 0 + for key, field in fields: + value = self._last_pose.get(key) + if value is None: + continue + field.setValue(float(value)) + filled += 1 + + if filled: + self._append_log(f"[info] Filled {filled} MoveL pose value(s) from GetPose.") + else: + self._append_log("[warn] Current pose has no usable position or orientation values.") + def _render_joint_table_and_plot(self) -> None: selected = set(self._plot_selected) if not self._last_data: diff --git a/ui/run_action.py b/ui/run_action.py index ed7a80e..4b6b204 100644 --- a/ui/run_action.py +++ b/ui/run_action.py @@ -10,7 +10,16 @@ def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Run robot client actions.") parser.add_argument( "action", - choices=["movej", "get_pose", "torque_on", "torque_off"], + choices=[ + "movej", + "movel", + "speedj", + "speedl", + "stop_motion", + "get_pose", + "torque_on", + "torque_off", + ], help="Action to run", ) parser.add_argument("--host", default="192.168.0.222", help="gRPC server host") @@ -19,48 +28,146 @@ def parse_args() -> argparse.Namespace: parser.add_argument("--interval", type=float, default=0.05, help="Interval seconds") parser.add_argument("--base-link", default="PELVIS_S", help="Base link for pose") parser.add_argument("--ee-link", default="R_FINGER_TIP", help="End-effector link for pose") - parser.add_argument("--vel", type=float, default=1.0, help="MoveJ velocity") - parser.add_argument("--acc", type=float, default=0.5, help="MoveJ acceleration") + parser.add_argument("--vel", type=float, default=1.0, help="Motion velocity") + parser.add_argument("--acc", type=float, default=0.5, help="Motion acceleration") + parser.add_argument("--duration", type=float, default=0.2, help="Speed command duration") + parser.add_argument("--blend-radius", type=float, default=0.0, help="MoveL blend radius") + parser.add_argument( + "--frame", + choices=["base", "tool", "world", "user"], + default="base", + help="Cartesian command frame", + ) parser.add_argument( "--joint-list", default="", - help="MoveJ joint list format: JOINT:RAD;JOINT:RAD", + help="Joint list format: JOINT:VALUE;JOINT:VALUE", ) + parser.add_argument("--x", type=float, default=0.0, help="Cartesian pose x") + parser.add_argument("--y", type=float, default=0.0, help="Cartesian pose y") + parser.add_argument("--z", type=float, default=0.0, help="Cartesian pose z") + parser.add_argument("--rx", type=float, default=0.0, help="Cartesian pose rx") + parser.add_argument("--ry", type=float, default=0.0, help="Cartesian pose ry") + parser.add_argument("--rz", type=float, default=0.0, help="Cartesian pose rz") + parser.add_argument("--vx", type=float, default=0.0, help="Cartesian velocity x") + parser.add_argument("--vy", type=float, default=0.0, help="Cartesian velocity y") + parser.add_argument("--vz", type=float, default=0.0, help="Cartesian velocity z") + parser.add_argument("--wx", type=float, default=0.0, help="Cartesian angular velocity x") + parser.add_argument("--wy", type=float, default=0.0, help="Cartesian angular velocity y") + parser.add_argument("--wz", type=float, default=0.0, help="Cartesian angular velocity z") return parser.parse_args() +def parse_joint_values(value: str, field_name: str) -> list[dict]: + parsed = [] + for item in value.split(";"): + if not item or ":" not in item: + continue + name, raw = item.split(":", 1) + try: + number = float(raw) + except ValueError: + number = 0.0 + parsed.append({"joint_name": name, field_name: number}) + return parsed + + def main() -> int: args = parse_args() address = f"{args.host}:{args.port}" if args.action == "movej": - from clients.movej_client import MoveJClient, test_joint_list + from clients.arm_motion_client import ArmMotionClient + from clients.movej_client import test_joint_list joint_list = test_joint_list if args.joint_list: - parsed = [] - for item in args.joint_list.split(";"): - if not item: - continue - if ":" not in item: - continue - name, rad_str = item.split(":", 1) - try: - rad = float(rad_str) - except ValueError: - rad = 0.0 - parsed.append({"joint_name": name, "rad": rad}) + parsed = parse_joint_values(args.joint_list, "rad") if parsed: joint_list = parsed - client = MoveJClient(address=address) + client = ArmMotionClient(address=address) try: - client.send(joint_list, vel=args.vel, acc=args.acc, device_id=args.device_id) + client.movej(joint_list, vel=args.vel, acc=args.acc, device_id=args.device_id) print(joint_list) finally: client.close() return 0 + if args.action == "movel": + from clients.arm_motion_client import ArmMotionClient + + client = ArmMotionClient(address=address) + try: + client.movel( + pose={ + "x": args.x, + "y": args.y, + "z": args.z, + "rx": args.rx, + "ry": args.ry, + "rz": args.rz, + }, + vel=args.vel, + acc=args.acc, + blend_radius=args.blend_radius, + frame=args.frame, + device_id=args.device_id, + ) + finally: + client.close() + return 0 + + if args.action == "speedj": + from clients.arm_motion_client import ArmMotionClient + + joint_list = parse_joint_values(args.joint_list, "velocity") + client = ArmMotionClient(address=address) + try: + client.speedj( + joint_list, + acc=args.acc, + duration=args.duration, + device_id=args.device_id, + ) + print(joint_list) + finally: + client.close() + return 0 + + if args.action == "speedl": + from clients.arm_motion_client import ArmMotionClient + + client = ArmMotionClient(address=address) + try: + client.speedl( + velocity={ + "vx": args.vx, + "vy": args.vy, + "vz": args.vz, + "wx": args.wx, + "wy": args.wy, + "wz": args.wz, + }, + acc=args.acc, + duration=args.duration, + frame=args.frame, + device_id=args.device_id, + ) + finally: + client.close() + return 0 + + if args.action == "stop_motion": + from clients.arm_motion_client import ArmMotionClient + + client = ArmMotionClient(address=address) + try: + client.stop_motion(device_id=args.device_id) + finally: + client.close() + return 0 + if args.action == "get_pose": from clients.get_pose_client import GetPoseClient