From 6b9bf3be30ffe5c140d77aff81f1716686af0951 Mon Sep 17 00:00:00 2001 From: lgv Date: Tue, 3 Feb 2026 13:36:52 +0800 Subject: [PATCH] feat:add readme.txt --- .idea/grpc_client.iml | 3 +- .idea/misc.xml | 2 +- README.md | 44 +++++ clients/__pycache__/__init__.cpython-39.pyc | Bin 142 -> 142 bytes .../__pycache__/_path_setup.cpython-39.pyc | Bin 0 -> 499 bytes .../__pycache__/base_client.cpython-39.pyc | Bin 2032 -> 1906 bytes .../get_joint_state_client.cpython-39.pyc | Bin 0 -> 4324 bytes .../__pycache__/hand_client.cpython-39.pyc | Bin 2316 -> 2313 bytes .../__pycache__/http_server.cpython-39.pyc | Bin 3074 -> 3146 bytes .../__pycache__/movej_client.cpython-39.pyc | Bin 6659 -> 6656 bytes clients/_path_setup.py | 11 ++ clients/base_client.py | 15 +- clients/cal.py | 165 ------------------ clients/get_joint_state_client.py | 21 ++- clients/get_pose_client.py | 12 +- clients/hand_client.py | 12 +- clients/http_server.py | 6 +- clients/movej_client.py | 38 ++-- clients/press_client.py | 9 +- clients/save_joint_to_csv.py | 12 +- clients/torque_key_client.py | 20 +-- clients/torque_off_client.py | 13 +- clients/torque_on_client.py | 13 +- .../cmvr/__pycache__/__init__.cpython-39.pyc | Bin 149 -> 149 bytes .../api/__pycache__/__init__.cpython-39.pyc | Bin 153 -> 153 bytes .../api/__pycache__/common_pb2.cpython-39.pyc | Bin 2186 -> 2175 bytes .../dexhand_command_pb2.cpython-39.pyc | Bin 6894 -> 6883 bytes .../dexhand_service_pb2_grpc.cpython-39.pyc | Bin 6977 -> 6966 bytes .../humanoid_robot_command_pb2.cpython-39.pyc | Bin 5397 -> 5397 bytes ...noid_robot_service_pb2_grpc.cpython-39.pyc | Bin 6691 -> 6691 bytes requirements.txt | 8 + run_client.py | 27 +-- 32 files changed, 178 insertions(+), 253 deletions(-) create mode 100644 README.md create mode 100644 clients/__pycache__/_path_setup.cpython-39.pyc create mode 100644 clients/__pycache__/get_joint_state_client.cpython-39.pyc create mode 100644 clients/_path_setup.py delete mode 100644 clients/cal.py create mode 100644 requirements.txt diff --git a/.idea/grpc_client.iml b/.idea/grpc_client.iml index 2c80e12..9c1850b 100644 --- a/.idea/grpc_client.iml +++ b/.idea/grpc_client.iml @@ -2,9 +2,10 @@ + - + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 366343e..6ce4790 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -3,5 +3,5 @@ - + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..cc17057 --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# grpc_client + +## Environment + +- Python: 3.9.25 + +## Setup + +```bash +pip install -r requirements.txt +``` + +## Generate gRPC code + +From repo root: + +```bash +python scripts/gen_grpc_py.py +``` + +This reads `.proto` files under `protos/` and writes Python code to `generated/`. + +## Run (module mode) + +```bash +python -m clients.movej_client +python -m clients.get_joint_state_client +python -m clients.get_pose_client +python -m clients.torque_on_client +python -m clients.torque_off_client +python -m clients.torque_key_client +python -m clients.press_client +python -m clients.save_joint_to_csv +python -m clients.http_server +``` + +## Runtime parameters + +- gRPC server address/port: set in `clients/base_client.py` (default `192.168.0.222:50052`). +- HTTP server host/port: set in `clients/http_server.py` (default `0.0.0.0:8000`). +- `device_id` defaults to `hc01` in most clients; change in code if needed. + + + diff --git a/clients/__pycache__/__init__.cpython-39.pyc b/clients/__pycache__/__init__.cpython-39.pyc index bf6364807ba813ba1cd095e03729ae05cebf3462..e6ee043e6749dfb863a68c87759c7cf24e10f5f1 100644 GIT binary patch delta 19 YcmeBU>|^9k|^9kvU%$wQSHyd}mTfnv}-_jlee7j`RJWejz%^`yVg&B-7KyU_bH%;T$6DUT_VI>;CTTmNJaPQLNe&w9SM27R!GgsFeu^bLc?$Pn z4i&$#4|A-b!l4+z{lQ#VS&0SCy~?wVr{)x?=g!ZTVrD52UU~{xgS=VsI#9PqI@c6i zt;pqlpM<0RC>o}jj-sGN+7#JT2dy$Dom*3T#uCHK)Uss#bu-P4Cfk>_q@u9oL{pm~ z@LfIP_Aj6g6F;YevoIM?DLgrPE$G?AWun6ol}VIjDGv|-4iiRyH~Ck}<8oGabZ#c3 z*Q{~#jT;BJgFbfHw~)3uX9=v*K4$5|dJXcKTvyi5Z$Os|wSPV?)FjgvZgb=Qn`e2y Do&LuD{>%MiV=wzBv^Re6bYM~0T*^}kGHeIE<*eV z!u}O$v`A-R{tdk{sq8dmu<*T2;o-o1^WOX3H}A)<)tkXd+V6J-ou>~!X1|pX*X%5x zfSea}t9=41Eabx6yeCHnQrIuR895BV5P=_r4 zU-f`ukDJdb>Cxr|b{KSXr5>HoUhY0c&RNp^D;W{NZC9+-&|T3R$gxD@h#uS^SgcR; z%*}i?ZdZF)IbY9FGNY%(F^{v*L&x)@5qCT6rc!|gFvE3yhMa(5F*`zIGOB{_+;N%YZHYk#Zl2k zE@_eyA%ReO#P3+>sOS(S4a^utM2NNg&GVZX&-zFFeXyTKQIFw?v&Y%nfU&P+oIXAp zH}S1rA!w!{XX=jx1+DEV52crP3a&UX!;L?OLvh3=!6Z=9c$4m7&xA(m&MKS35n#@V zPqj|XPXbuT&-+EBBA_M$^;pHg?lK){`I2cl_tZecCQG{Ca3FR+0mmPt^^WUqo2jvB zZM5TC#jZVgcQM4^iZ**Y=~RFC^avJ0WY|7Igz$#h#pbfN0nu^gJ%NxG@e`5+Y`^)^ zc`qo(4x0mhPPD|hIp%AkrTfiienAW%k#Lj`GFo1(1kB86L6BJB*q z*=EoGcr`}N`CIv{GImHIDvxc6{x9o1`YtuwA_*l$y9B-FwOHR@CY6d>$~ndR+Y&F7 z(Y%#FkkISOWlO$fXh$gQDuU}&FDe(5xWhbqYCUqv@%UMmoAKBM)>N~6*Emt8)=X<9 zZ{OP77~i?~&^S&UC(^PsQ;lV6>m+UdN?K-?{u@he+lK#}&dF=VOh3=fb-=WWK3)Pn NeHeO6u!6Gh{RV~Xiah`T diff --git a/clients/__pycache__/get_joint_state_client.cpython-39.pyc b/clients/__pycache__/get_joint_state_client.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..c8ceaa5e58a1fffbea0a174ed7aacf4f1ee8523b GIT binary patch literal 4324 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 MXdz1}&x{AZ0i<~5Sg9kA} zOgxZC_GIE0Fvb&!XV0F!*l*xBFrJ)wRfFPg-Y+x1`IDJ>v-_g-xs-7-nIyw!qw?HU zrriWUH{*?oF)u5!-nba|Cd7o7 z6FDz0@)%=70+J_O6m}S-AbrdrEt5y4m_$7TW2ld5y(nWxOiV%6DS_!s0CyP48 zJXAGXmkUsPY`o^`Cbn#L8=@f_dy7zq1!zDP7Edf}*1XG_OW$Z*nTE7R4N%z(z=N8{afn+31z|AX`Lg z7*_+|cl}4I5dAQBZk3Qz(*!dFvjn4bf20aYa|q;nF0aisyRC9(FRcxOi|A8qd3=Jr z^8}*`qwle+RjNu+MQWa)N>D=>TA}MnRgdn(t4)z|I@E9b;lS^x1vHCTr|U{T9CCTC zs7mxE{=Rqx?N2i+qmUJE5V)NtmLTkFwFH`)h9VC|^0`>uS|$2u*Lf@YYH!wPLx1~{ ze9?D3QROSqy~I{6bIzCKi@qNdb-o&XP29Ntcb}?Gp_lZ${uzNbUCeYpxyaX|XUW!~ zuy%VvD6D=@g~OPV-JS!@C6R37oI=`(wlXlg$d6_fwDgj+0~rqb%A_)v$(#sxm2`k^ z-XcBz&{Lt;F7^=fqc+e-$IAe!Q6n+ksmH75mclrd zCZN#>)9Lq>zINIiwM6irndnQR-hxoI$Y+IM6=7&TlA&5-cTlIr6Y;^m+vzEPXyOK6 l7#Ky^n_dqFUAcw-85Z0`NE>M*X3!^~U!!U!jp$3N^%Ey0_m=9P1RiSiCmti`h>yfx}%T}Qlu``^Vg*wegTg|Q-b7d0B0~kkc;eZp>soNFw#Z6wqi!_g6IzV#YIoudvKk!4 zH=D@`Eyj1X*YOrv3vS{sf;U<&)}D&I3tnpl+UYP6e{GRN829%XwE)jn!aTNX4ScI_GZ{0re*~86+g&z=sp22? zQK?{(bi6})&>1^AKBvf>`%&BYlClH>b{0xjzt5rr#XG_7alkf4=qXk@@YxD3tRk!- zDeY08tz(L}EsPBh?XJh%P~undj!~#kHyjU!J-W&N5QM=dhop%2I2vL@(iB56R7H|W NG$i9uk_1c1+F#(n?@9mw diff --git a/clients/__pycache__/movej_client.cpython-39.pyc b/clients/__pycache__/movej_client.cpython-39.pyc index 90a9f1c7b1df54a33e4b2af15ea0b11410f82199..d2776e4570c243ba69b71960c482757106ee03d2 100644 GIT binary patch delta 1617 zcmZuxO>7%g5Z-66|GjY%$8kdJCV__5Nt%Qv^v|U<%}+x~LrD;acj#)f@5R|>*K6Lc z3q3$8B|r-W5uUh}pI(r9L*z`a2&p$NXyJsAs$SrTgoMP*Itr;^t^MA&GxKKV&CK)G z)Xgc)(6qDy&%@G9>+Kz_On%uJDQ`L|Rh<}(5rru%YR8=fO%R1AwvM%**tU{2IRd z<*$GP@=&x&1|^MF=bmOJW1(}-*21;M)^V*x=`*0S_H@_wbQ^oR!>qviLr&V*T0R$s zT_Hi1*S)6Tdc&Ca*F6wuzU6SwH=KsvSUnDc+_Jms`ZKo0>;479<7hL>-&!#0tR)0N z+H@Vqt>X?k8yl2fY+(1h*l0A~4$(!2{3Cgobjq&OcC`}(LDVqKKs6oaJ;s@-5SqZK zZWB`Tnx@G;Z;5JL2v_KkjAzt%piuc^YNL_@d#cSqIEcT%4aNmcEb}*-kaLh?{JLdw z-C`mxYw1Hpn%X}NP0qND8}q_-L|)!a4?@cyq=(Cxk0>DwB9syO0BFSbsElQfWr`^3 zM(B|*WM0o`NZSx{@{7#m=nS^6yM}uO_<#SwBj&-OqBA z41}@K@%@O{&*%|+<8xk2$Xv$~%*xe{tF3gxNImlVj&U+0OI@GJ+nsMtPlBzOLO6l& zJVKc4Nn~RHl{Rq-`DuV4y$%)BeYa_@i|`CpIh~)5pF@Ke<*oc&@eI)WL`G%+Cxi7&j7^wH>>F+TfZ#>DtwLP$(}^~J<!70)rQBEo>$&6Kn89cM1(h^It zG#j`RVQb$`k-@b;8l)^|NP}#VUDD7iyh4ZM1DFrX-+&`>ccek8U>lboMzZ==zLjp1 zUxIGC`~^5FN7M${Aup(n>SN4eEDX)r<#24VMI2iy-6A>Vg*`kn%%A?K9Z?MGPs`5Fn*zU^?&H=S;?yD)`{WBiR% zW``YZ+q~nynM&2`lP%ui!t^cGEgmR={2_6*rh`9CABBt{=TqEdTm-SR{Iwn&Do8MX&9->c zW+Eb6$*tuev2k1)Kk9auP6*c#d3h%}N`~Y&$vsufM+_r&B1RBHfFR;~fz*u&V-Qs( zL|Gm(&KoHd(})52xiP(W0>#kjACysN01EJV8X!)zh%n@zM)mKStZz^GDoL$>lA&aK zxHksA9}ycFJ%l+vv*Jma$<31qc`0|PpH8?_S$>zBB&X#_@nd;2|N7j1@D&FT2N4ZK znCu~xW&pJTaTw*N0YP#RD%kYhp0y~#Gt9}k!d&ziIvkfb3dhT{Al@h9Bx*LDP5xSV zikw>?DMrciGho{sRXmH%{ctFqK|BWt3Qo_nt)|^+UUBV?-)uw6i2i11F7?fBm;cM* z1zi6kVis{0u{o7{rTyf*{HZiOisyssZAAR9@8sYZxd^_K!{QZ;bK!p@ela*ULBSHl zn@z`rbepxAK=qcqFf`4yT({Nc^{#MzccFI~8tqWIvaI3?t!w&=LDcNRAsEeX+@iYW zToZM(YYPpx%p`z1t%z@^FLH(t!-D!z9E=wTr;)E?G z=AgU!MHch&cDb_Sc{E|=XqG!&e`#~YUqai<^82#49F`S-5Y2fI+iY+43;*LRIv+ut zK%7RLL!3ukL}01<9TIj{9j1Y9wOx;I`cM6KMN|Bs TYS{zwQe~TTD%tR>R382XKW>Z- diff --git a/clients/_path_setup.py b/clients/_path_setup.py new file mode 100644 index 0000000..7b8ff6f --- /dev/null +++ b/clients/_path_setup.py @@ -0,0 +1,11 @@ +from pathlib import Path +import sys + + +def ensure_paths() -> None: + root = Path(__file__).resolve().parents[1] + generated = root / "generated" + for path in (root, generated): + path_str = str(path) + if path_str not in sys.path: + sys.path.insert(0, path_str) diff --git a/clients/base_client.py b/clients/base_client.py index 6896f03..995770b 100644 --- a/clients/base_client.py +++ b/clients/base_client.py @@ -1,13 +1,14 @@ -import grpc import time -import sys -from pathlib import Path -BASE_DIR = Path(__file__).resolve().parent.parent # 上一级目录 -sys.path.append(str(BASE_DIR / "generated")) -sys.path.append(str(BASE_DIR / "generated" / "cmvr")) -from cmvr.api import humanoid_robot_service_pb2_grpc +import grpc + +from clients._path_setup import ensure_paths + +ensure_paths() + from cmvr.api import dexhand_service_pb2_grpc +from cmvr.api import humanoid_robot_service_pb2_grpc + class RobotClientBase: def __init__(self, address="192.168.0.222:50052", timeout=2, retries=1): diff --git a/clients/cal.py b/clients/cal.py deleted file mode 100644 index efb6443..0000000 --- a/clients/cal.py +++ /dev/null @@ -1,165 +0,0 @@ - -import csv - -import math -import sys -from google.protobuf import timestamp_pb2 - -sys.path.append("../generated") -import math -from typing import List, Dict, Literal -from generated.cmvr.api import humanoid_robot_command_pb2 as pb -from generated.cmvr.api import common_pb2 -from generated.cmvr.api import humanoid_robot_service_pb2_grpc as rpc -from clients.base_client import RobotClientBase - - -class MoveJClient(RobotClientBase): - """Client to send MoveJ commands to the robot""" - - def send(self, joint_list, vel=1.0, acc=0.5, device_id="hc01"): - """ - joint_list: list of dicts, e.g., - [ - {"joint_name": "L_SHOULDER_P", "rad": 0.0}, - {"joint_name": "L_SHOULDER_R", "rad": -1.31873}, - ... - ] - vel: velocity - acc: acceleration - device_id: device ID - """ - # Construct JointCmd list - cmds = [pb.JointCmd(joint_name=j["joint_name"], rad=j["rad"], vel=vel) for j in joint_list] - - # Construct request header - header = common_pb2.CommandHeader.Request() - header.device_id = device_id - ts = timestamp_pb2.Timestamp() - ts.GetCurrentTime() - header.timestamp.CopyFrom(ts) - - # Construct MoveJ request - req = pb.MoveJ.Request( - header=header, - vel=vel, - acc=acc, - cmds=cmds - ) - - # Call RPC - try: - resp = self.stub.moveJ(req, timeout=10) - success = getattr(resp.header, "success", None) - error_msg = getattr(resp.header, "error_message", "") - timestamp_sec = getattr(resp.header.timestamp, "seconds", 0) - print("MoveJ RPC call succeeded") - print(f"Success: {success}") - print(f"Error message: {error_msg}") - print(f"Timestamp: {timestamp_sec}") - except Exception as e: - print("MoveJ RPC call failed:", e) - - def read_joint_from_csv(self, file_name, row_num): - """Read joint data from a CSV file and return joint list for the specified row.""" - joint_order = [ - 'R_SHOULDER_P', 'R_SHOULDER_R', 'R_SHOULDER_Y', - 'R_ELBOW_R', 'R_WRIST_P', 'R_WRIST_Y', 'R_WRIST_R' - ] - - try: - with open(file_name, newline='') as csvfile: - reader = csv.reader(csvfile) - # Skip header row - next(reader) - # Read specific row - for i, row in enumerate(reader, start=1): - if i == row_num: - # Assign joint values to the list - joint_list = [ - {'joint_name': joint_order[j], 'rad': round(float(row[j]) * math.pi / 180, 10)} - for j in range(len(joint_order)) - ] - return joint_list - print(f"Row {row_num} not found.") - return [] - except Exception as e: - print(f"Error reading CSV: {e}") - return [] - - def apply_joint_offsets(self, - joint_list: List[Dict[str, float]], - on_missing: Literal["ignore", "error", "warn"] = "ignore", - ) -> List[Dict[str, float]]: - """ - 将零偏置 joint_zero_offset 叠加到 joint_list 上,按 joint_name 对齐。 - - 参数: - joint_list: [{'joint_name': str, 'rad': float}, ...] 原始指令 - joint_zero_offset: [{'joint_name': str, 'rad': float}, ...] 零偏置 - on_missing: 当 joint_list 中出现 zero_offset 里没有的关节名时的处理: - - "ignore": 当作偏置为 0(默认) - - "warn": 打印警告,再当作 0 - - "error": 抛出 KeyError - - 返回: - 与 joint_list 顺序一致、已叠加偏置的新列表 - """ - # 构造偏置表,并检查是否有重复关节名 - - deg_to_rad = math.pi / 180.0 - - joint_zero_offset = [ - {'joint_name': 'R_SHOULDER_P', 'rad': 0}, - {'joint_name': 'R_SHOULDER_R', 'rad': 0 * deg_to_rad}, - {'joint_name': 'R_SHOULDER_Y', 'rad': 0.2 * deg_to_rad}, - {'joint_name': 'R_ELBOW_R', 'rad': 0.2 * deg_to_rad}, - {'joint_name': 'R_WRIST_P', 'rad': 0.6 * deg_to_rad}, - {'joint_name': 'R_WRIST_Y', 'rad': 2.5 * deg_to_rad}, - {'joint_name': 'R_WRIST_R', 'rad': 0}, - ] - - offset_map: Dict[str, float] = {} - for j in joint_zero_offset: - name = j["joint_name"] - if name in offset_map: - raise ValueError(f"Duplicated joint in zero_offset: {name}") - offset_map[name] = float(j["rad"]) - - joint_cmd: List[Dict[str, float]] = [] - for j in joint_list: - name = j["joint_name"] - base = float(j["rad"]) - if name not in offset_map: - if on_missing == "error": - raise KeyError(f"Missing zero offset for joint: {name}") - elif on_missing == "warn": - print(f"[apply_joint_offsets] WARN: Missing zero offset for {name}, using 0.0") - off = 0.0 - else: - off = offset_map[name] - - joint_cmd.append({"joint_name": name, "rad": base + off}) - return joint_cmd - - -if __name__ == "__main__": - # Initialize client - client = MoveJClient() - - # Specify CSV file and row number to read from - file_name = 'joint_states.csv' - row_num = 2 # Row number you want to read (e.g., 2 for the second row) - - # Read joint data from CSV - joint_list = client.read_joint_from_csv(file_name, 8) - joint_list = client.apply_joint_offsets(joint_list) - print(joint_list) - - if joint_list: - - # Send MoveJ command with the read joint data - client.send(joint_list, vel=0.8, acc=0.8) - - # Close client - client.close() \ No newline at end of file diff --git a/clients/get_joint_state_client.py b/clients/get_joint_state_client.py index e856b76..bf85d4b 100644 --- a/clients/get_joint_state_client.py +++ b/clients/get_joint_state_client.py @@ -1,14 +1,14 @@ -import sys import time from google.protobuf import timestamp_pb2 - import matplotlib.pyplot as plt # 新增 -sys.path.append("../generated") +from clients._path_setup import ensure_paths + +ensure_paths() -from generated.cmvr.api import humanoid_robot_service_pb2_grpc as rpc -from generated.cmvr.api import humanoid_robot_command_pb2 as pb from clients.base_client import RobotClientBase +from cmvr.api import humanoid_robot_command_pb2 as pb +from cmvr.api import humanoid_robot_service_pb2_grpc as rpc # 固定关节顺序 JOINT_ORDER = [ @@ -88,12 +88,11 @@ class GetJointStateClient(RobotClientBase): except KeyboardInterrupt: print("\nStopped fetching joint states.") # 例子:退出时画几个关节的角度 & 速度曲线 - # try: - # # 你可以根据需要改关节列表 - # self.plot_pos_and_vel([ "R_SHOULDER_P", "R_SHOULDER_R", "R_SHOULDER_Y", - # "R_ELBOW_R", "R_WRIST_P", "R_WRIST_Y", "R_WRIST_R"]) - # except Exception as e: - # print(f"绘图失败: {e}") + try: + # 你可以根据需要改关节列表 + self.plot_pos_and_vel([ "L_SHOULDER_R"]) + except Exception as e: + print(f"绘图失败: {e}") # ====== 3. 绘制速度和角度(同一张图,两行子图) ====== def plot_pos_and_vel(self, joint_names): diff --git a/clients/get_pose_client.py b/clients/get_pose_client.py index aebc3d4..95bff89 100644 --- a/clients/get_pose_client.py +++ b/clients/get_pose_client.py @@ -1,12 +1,14 @@ -import sys import time + +from clients._path_setup import ensure_paths + +ensure_paths() + from google.protobuf import timestamp_pb2 -sys.path.append("../generated") - -from generated.cmvr.api import humanoid_robot_service_pb2_grpc as rpc -from generated.cmvr.api import humanoid_robot_command_pb2 as pb from clients.base_client import RobotClientBase +from cmvr.api import humanoid_robot_command_pb2 as pb +from cmvr.api import humanoid_robot_service_pb2_grpc as rpc class GetPoseClient(RobotClientBase): diff --git a/clients/hand_client.py b/clients/hand_client.py index dc212e4..75e0951 100644 --- a/clients/hand_client.py +++ b/clients/hand_client.py @@ -1,12 +1,12 @@ -import sys +from clients._path_setup import ensure_paths + +ensure_paths() + from google.protobuf import timestamp_pb2 -sys.path.append("../generated") - - -from generated.cmvr.api import dexhand_command_pb2 as pb -from generated.cmvr.api import common_pb2 from clients.base_client import RobotClientBase +from cmvr.api import common_pb2 +from cmvr.api import dexhand_command_pb2 as pb handshake = [ diff --git a/clients/http_server.py b/clients/http_server.py index 9489167..78edb30 100644 --- a/clients/http_server.py +++ b/clients/http_server.py @@ -1,5 +1,9 @@ from __future__ import annotations +from clients._path_setup import ensure_paths + +ensure_paths() + import time from threading import Lock from fastapi import FastAPI @@ -130,4 +134,4 @@ if __name__ == "__main__": host="0.0.0.0", port=8000, reload=False - ) \ No newline at end of file + ) diff --git a/clients/movej_client.py b/clients/movej_client.py index e6c3440..4285b84 100644 --- a/clients/movej_client.py +++ b/clients/movej_client.py @@ -1,18 +1,18 @@ -import sys +import math import time +from typing import Dict, List, Literal + +from clients._path_setup import ensure_paths + +ensure_paths() from google.protobuf import timestamp_pb2 -from clients.hand_client import DexHandClient - -sys.path.append("../generated") - -from generated.cmvr.api import humanoid_robot_command_pb2 as pb -from generated.cmvr.api import common_pb2 -from generated.cmvr.api import humanoid_robot_service_pb2_grpc as rpc from clients.base_client import RobotClientBase -import math -from typing import List, Dict, Literal +from clients.hand_client import DexHandClient +from cmvr.api import common_pb2 +from cmvr.api import humanoid_robot_command_pb2 as pb +from cmvr.api import humanoid_robot_service_pb2_grpc as rpc @@ -212,7 +212,7 @@ head_screen_joint_list = [ ] singal_joint_list = [ -{'joint_name': 'R_WRIST_P', 'rad': 0.217252876821}, +{'joint_name': 'L_SHOULDER_R', 'rad': 0}, ] hand_touch = [ @@ -263,6 +263,18 @@ hello_r_joint_list = [ +test_joint_list = [ + # {'joint_name': 'R_SHOULDER_P', 'rad':0}, + {'joint_name': 'R_SHOULDER_R', 'rad': 0}, + # {'joint_name': 'R_SHOULDER_Y', 'rad': 0}, + # {'joint_name': 'R_ELBOW_R', 'rad':0}, + # {'joint_name': 'R_WRIST_P', 'rad': 0}, + # {'joint_name': 'R_WRIST_Y', 'rad': 0}, + # {'joint_name': 'R_WRIST_R', 'rad': 0}, +] + + + # @@ -363,8 +375,8 @@ if __name__ == "__main__": # Initialize client # action_air(client) - action_hello(client) - # client.send(head_front_joint_list, vel=1.0, acc=1.0) + # action_hello(client) + client.send(test_joint_list, vel=1.0, acc=1.0) # client.send(go_home_joint_list, vel=3.0, acc=3.0) # action_music(client) diff --git a/clients/press_client.py b/clients/press_client.py index 40bcef9..87bf02e 100644 --- a/clients/press_client.py +++ b/clients/press_client.py @@ -1,10 +1,11 @@ -import sys +from clients._path_setup import ensure_paths + +ensure_paths() + from google.protobuf import timestamp_pb2 -sys.path.append("../generated") - -from generated.cmvr.api import hlc_command_pb2 as pb from clients.base_client import RobotClientBase +from cmvr.api import hlc_command_pb2 as pb class TouchClient(RobotClientBase): diff --git a/clients/save_joint_to_csv.py b/clients/save_joint_to_csv.py index 04611c4..28540f1 100644 --- a/clients/save_joint_to_csv.py +++ b/clients/save_joint_to_csv.py @@ -1,13 +1,15 @@ -import sys import csv import math + +from clients._path_setup import ensure_paths + +ensure_paths() + from google.protobuf import timestamp_pb2 -sys.path.append("../generated") - -from generated.cmvr.api import humanoid_robot_service_pb2_grpc as rpc -from generated.cmvr.api import humanoid_robot_command_pb2 as pb from clients.base_client import RobotClientBase +from cmvr.api import humanoid_robot_command_pb2 as pb +from cmvr.api import humanoid_robot_service_pb2_grpc as rpc class GetJointStateClient(RobotClientBase): diff --git a/clients/torque_key_client.py b/clients/torque_key_client.py index 21ddf48..045695e 100644 --- a/clients/torque_key_client.py +++ b/clients/torque_key_client.py @@ -1,19 +1,17 @@ - import sys -import tty import termios +import tty + +from clients._path_setup import ensure_paths + +ensure_paths() + from google.protobuf import timestamp_pb2 -# sys.path.append("../generated") -sys.path.append("/home/lgv/cmvr/0-workspace/grpc_client/generated") # 指向 generated -sys.path.append("/home/lgv/cmvr/0-workspace/grpc_client/generated/cmvr") # 指向 cmvr 顶层 - -from cmvr.api import humanoid_robot_service_pb2_grpc as rpc -from cmvr.api import humanoid_robot_command_pb2 as pb +from clients.base_client import RobotClientBase from cmvr.api import common_pb2 - - -from base_client import RobotClientBase +from cmvr.api import humanoid_robot_command_pb2 as pb +from cmvr.api import humanoid_robot_service_pb2_grpc as rpc class TorqueClient(RobotClientBase): diff --git a/clients/torque_off_client.py b/clients/torque_off_client.py index c8cd09b..293edf3 100644 --- a/clients/torque_off_client.py +++ b/clients/torque_off_client.py @@ -1,12 +1,13 @@ -import sys +from clients._path_setup import ensure_paths + +ensure_paths() + from google.protobuf import timestamp_pb2 -sys.path.append("../generated") - -from generated.cmvr.api import humanoid_robot_service_pb2_grpc as rpc -from generated.cmvr.api import humanoid_robot_command_pb2 as pb -from generated.cmvr.api import common_pb2 from clients.base_client import RobotClientBase +from cmvr.api import common_pb2 +from cmvr.api import humanoid_robot_command_pb2 as pb +from cmvr.api import humanoid_robot_service_pb2_grpc as rpc class TorqueOffClient(RobotClientBase): diff --git a/clients/torque_on_client.py b/clients/torque_on_client.py index a39e47d..df42df1 100644 --- a/clients/torque_on_client.py +++ b/clients/torque_on_client.py @@ -1,12 +1,13 @@ -import sys +from clients._path_setup import ensure_paths + +ensure_paths() + from google.protobuf import timestamp_pb2 -sys.path.append("../generated") - -from generated.cmvr.api import humanoid_robot_service_pb2_grpc as rpc -from generated.cmvr.api import humanoid_robot_command_pb2 as pb -from generated.cmvr.api import common_pb2 from clients.base_client import RobotClientBase +from cmvr.api import common_pb2 +from cmvr.api import humanoid_robot_command_pb2 as pb +from cmvr.api import humanoid_robot_service_pb2_grpc as rpc class TorqueOnClient(RobotClientBase): diff --git a/generated/cmvr/__pycache__/__init__.cpython-39.pyc b/generated/cmvr/__pycache__/__init__.cpython-39.pyc index 526278d8e6ccfed8cd866b263ef7ce06148945d7..b1b4d0bb73b89e69575f36a3b12912b2f5885cb5 100644 GIT binary patch delta 19 ZcmbQrIF*q*k(ZZ?0SKNmHcaI10stnF1WNz_ delta 19 ZcmbQrIF*q*k(ZZ?0SG+3oF;O20RSUX1GoSH diff --git a/generated/cmvr/api/__pycache__/__init__.cpython-39.pyc b/generated/cmvr/api/__pycache__/__init__.cpython-39.pyc index df87170aed6beb8c3cd3507912b978049ca8d18f..a78f275564e1ff525566ac1806877b25327ec772 100644 GIT binary patch delta 19 ZcmbQqIFpe(k(ZZ?0SKNmHcaI10{|x_1Xln6 delta 19 ZcmbQqIFpe(k(ZZ?0SG+3oF;Pj0RSWB1H=FT diff --git a/generated/cmvr/api/__pycache__/common_pb2.cpython-39.pyc b/generated/cmvr/api/__pycache__/common_pb2.cpython-39.pyc index 235d56c4e82423cf52017c78308ac88cf2621a7b..33fbda6cfd9226849c3c5ef3b13d3f546a140982 100644 GIT binary patch delta 58 zcmeAY{4c_J-lbl;t6l0)Uo?nz* boLG>Y8k1gBkQ|?!lbM=V60>=qgaH!(a61^P delta 87 zcmaEC`p%R)k(ZZ?0SG+3oHlaL7G}J)dAaaI7Bf?zP!Wju72;wQlbl;t6l0)Uo?nz* ioLG>Y8k1gBkQ|?!lbM=V5(A-%WAya&H*c0OU;+S@FC2XU diff --git a/generated/cmvr/api/__pycache__/dexhand_service_pb2_grpc.cpython-39.pyc b/generated/cmvr/api/__pycache__/dexhand_service_pb2_grpc.cpython-39.pyc index 41cc4b18529d637cd740e5600b044e268e132ec3..3d840e8c08e568e3d01065984d9f474a3096f672 100644 GIT binary patch delta 211 zcmX?Tw#|$?k(ZZ?0SKNmHf-cpU}N-~tiz_TIh;+IOE`+7IJKxOGdZ>B78{6_S`;<8 zS70Bb>t-LpbS6gk$yFSQ;> Ro|iIXa|Ow}P3D$10|1-{KN|o5 delta 219 zcmdmHcF>GFk(ZZ?0SG+3oHlYRurWqX)?riNPR_|p%_}L6(bLo4?8BzaC47scIJKxO zGdZ>B78{5Kq$f8C>|=D_>?oMd#OOJBrLa1q*W`=BD}d}O5fIyuU3l_Vk?BCbuPBV) zAqwIvh{5>LVj%u!IA2eEHjsZx945~yF%`(4EulC0vV<~Q5YU}P!61V-vr6(YGI~rF Zky;NVk4TxZxr5|9CU4{wpDZpd4*-ggJ|F-9 diff --git a/generated/cmvr/api/__pycache__/humanoid_robot_command_pb2.cpython-39.pyc b/generated/cmvr/api/__pycache__/humanoid_robot_command_pb2.cpython-39.pyc index 14150d66209c55cb04d984c4d6b28fd182ef8112..1856ba58addf754b67a7a124eea38075a0969733 100644 GIT binary patch delta 54 zcmbQLHC2l{k(ZZ?0SKNmHf-d6&M$C_BRRLMNH4Jci-12^pqrAe>hU`Bj8zdBCbSi?l^uR5T40wwrLe{QivB66|9uar!W1 z%Af5%OCJ>sWXh_ZL)5l_iz;>8HWjmET!j(wZdRI|yE!#GmAx}HNr_kJNnT(yv}Hd! zi=}HOS~hFgGY}_C=7qJcL1+#ETmh&m;BzA6hwwb{_c@(x-3`+37$BcoCmZm^NXD_! zfG^JY?vaPiLGshlh_9Q>J69XT>?M27mks75$vfBh`bBWy5e11OW2HF@QKg zH$X2y5};}vZ*lhq*o$%YHGB*Ni>+C(*M2a(SDP7Bp7NX+vV>{iplouerf|sG*>LKF zVpgMM*BZT_>cAt6nZ`<*f>-1Uvigyikh!SmWLAQ?0>YIPy=*21o!Ac$0tf;G0ICMG zLARYuxT6W~z&Yr18xqj!0pNz5XDv!f?s%R?lw3|;KpHy+stf}5BO56j)HZ&WSJ~09 zLyP_T|CDG@_rdZ5(N({}YMO=+q$qTfc8_M6CYzpJOPWaD2cz5q7g{c|=u1d5l>+vF z$Ri2!G~U6We1?kcZv(|fG*#& zN41WM_#R5 z0snNn&5*sw2XO{0a#ili5wg?fAL9qh3id#yQoH`>2~tgLgAus#@H7CdtEqZ$182yu Gww~W!5_A>- delta 1893 zcmcIlO-~b16s5G%q3zJ`mXAWK1xo?N3IS0P6Qw3qsU&JlCd4{UpHNe#_07}(ku=5x zg9|q|{sLAkjFSEUw(i}{N`HWf8xmdVyRVQIq_&B1nx4LSALpHS?tQo0liQPaSrlz; z>`C3cwrh8r{gQ{0XCz6dPZ*PHAag?;z%2V}9tMKR(k^t=e<+SC?F_0f|BnrZL*{;abTk> z7;jO`dOz%$`^)h@nqbV*KuMM9TBeFDe=;jXUoK=bECsW9LaRq~vYFI{;70Hvco9&0 zodH37?MCQ9;C}RBi<{Dqtq4N6jSW@(IgRgU5V%u=tR+Fo8OcfAayFY;A}YHdN(Pl3 zNYdgKOnUk}r?5NIfi3py{ZqRpIS-c~o~9NEjDazNTR>3=L9a_SO~ICH&lH1{`{4|F zV>rlak?p=hR9()~E);pp34VU=1bVV#qm%H(?Yk;@53X=Vp(M+evm35SE=A7gkMsyR zRU=A?62q<(R*IBpTCSi1j!I=0@`>k7*oqo1AzVf{HYWg1?^a4+Bk@iiT{?ra zxbRwNcnG<09wZ*jlh}IUt9KK=do0NYY&^29vW4KIZ!?88umKy-j#k+QpzMF0vNvW6 z{XE-$Wr;<~uwc(%2-ZboYe-Nk9B1g4|CSbS6dxc*tGunfhYIGDHKP8Gz XE!cylkWL}sy1K0A`9BQ5dIo<3xHf6I diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..4d66caa --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +fastapi==0.124.4 +grpcio==1.76.0 +grpcio-tools==1.76.0 +matplotlib==3.9.4 +protobuf==6.33.2 +pydantic==2.12.5 +requests==2.32.5 +uvicorn==0.38.0 diff --git a/run_client.py b/run_client.py index 0c317e8..06e6b9f 100644 --- a/run_client.py +++ b/run_client.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ -统一启动 gRPC 客户端脚本 -自动设置 PYTHONPATH,让 generated 下的 cmvr 包可用 -用法: +Unified entrypoint for running client scripts. + +Usage: python run_client.py get_joint_state_client python run_client.py movej_client """ @@ -16,20 +16,25 @@ PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__)) GENERATED_DIR = os.path.join(PROJECT_ROOT, "generated") CLIENTS_DIR = os.path.join(PROJECT_ROOT, "clients") -# 添加 generated 到 PYTHONPATH -sys.path.insert(0, GENERATED_DIR) -os.environ["PYTHONPATH"] = GENERATED_DIR + os.pathsep + os.environ.get("PYTHONPATH", "") - if len(sys.argv) < 2: - print("请指定要运行的客户端脚本名,例如:get_joint_state_client") + print("Please provide a client script name, e.g. get_joint_state_client") sys.exit(1) client_name = sys.argv[1] client_script = os.path.join(CLIENTS_DIR, f"{client_name}.py") if not os.path.isfile(client_script): - print(f"客户端脚本不存在: {client_script}") + print(f"Client script not found: {client_script}") sys.exit(1) -# 支持传递额外参数给客户端 -subprocess.run([sys.executable, client_script] + sys.argv[2:]) +module_name = f"clients.{client_name}" +env = os.environ.copy() +extra_paths = [PROJECT_ROOT, GENERATED_DIR] +existing = env.get("PYTHONPATH", "") +env["PYTHONPATH"] = os.pathsep.join(extra_paths + ([existing] if existing else [])) + +subprocess.run( + [sys.executable, "-m", module_name] + sys.argv[2:], + cwd=PROJECT_ROOT, + env=env, +)