Initial import of ORISYS arm workstation

This commit is contained in:
cheng
2026-10-16 17:27:30 +08:00
commit 2b74c50ba8
745 changed files with 1199101 additions and 0 deletions
+184
View File
@@ -0,0 +1,184 @@
"""Isolated DDS-domain integration fixture. All hardware transports are fake."""
import threading
import time
from pathlib import Path
# The caller assigns a private ROS_DOMAIN_ID before importing rclpy.
import rclpy
from rclpy.executors import MultiThreadedExecutor
from rclpy.node import Node
from arm_msgs.srv import GripperExecute
from arm_application.dh_pgea import GripperState
import arm_application.dh_pgea_node as driver
class FakeGripper:
writes=[]
def __init__(self,**kwargs):
self.state=GripperState(init_state=1,grip_state=1,position=300,target_position=300,target_force=33)
def open(self): pass
def close(self): pass
def probe(self): return 1
def get_run_states(self,**kwargs): return self.state
def initialize(self): self.writes.append('INIT')
def move(self,pos,force,speed):
self.writes.append((pos,force,speed));self.state.position=pos;self.state.target_position=pos;return True
def main():
driver.DhPgeaGripper=FakeGripper
rclpy.init(args=['--ros-args','-r','__ns:=/station/grippers/arm_89','-p','auto_init:=false'])
device=driver.DhPgeaNode()
assert device.get_namespace()=='/station/grippers/arm_89',device.get_namespace()
client_node=Node('station_test_client')
executor=MultiThreadedExecutor(num_threads=2);executor.add_node(device);executor.add_node(client_node)
thread=threading.Thread(target=executor.spin,daemon=True);thread.start()
try:
client=client_node.create_client(GripperExecute,'/station/grippers/arm_89/execute')
assert client.wait_for_service(timeout_sec=3)
def command(name,position=0,force=33,speed=25):
req=GripperExecute.Request();req.command.command=name;req.command.position=float(position)
req.command.force=float(force);req.command.speed=float(speed)
future=client.call_async(req);deadline=time.monotonic()+3
while not future.done() and time.monotonic()<deadline: time.sleep(.01)
assert future.done(),'service did not respond'
return future.result()
assert command('connect').success
assert FakeGripper.writes==[],FakeGripper.writes
assert not command('move',1,force=999).success
assert not command('move',float('nan')).success
assert not command('rotate_to',90).success
assert FakeGripper.writes==[]
assert command('move',10).success
assert FakeGripper.writes==[(500,33,25)],FakeGripper.writes
assert command('connect').status.finger_position_per_mille==500.
print('ROS typed service, namespace, bounds and no-auto-init PASS',flush=True)
finally:
executor.shutdown(timeout_sec=3);thread.join(timeout=3)
device.destroy_node();client_node.destroy_node();rclpy.shutdown()
# Camera subscriptions must follow consumers, not all configured cameras.
from station import operator_camera_view as camera
cams=[{'id':'left','role':'wrist'},{'id':'right','role':'wrist'},{'id':'sim_wrist','role':'wrist'}]
camera.watch(set(),set());node=camera._open_node(cams)
try:
node.sync_streams();assert node._streams.items=={}
camera.watch({'left_color'},set());node.sync_streams()
assert set(node._streams.items)=={'left_color'}
from sensor_msgs.msg import Image
from rclpy.qos import qos_profile_sensor_data
publisher=node.create_publisher(Image,'/left/color/image_raw',qos_profile_sensor_data)
message=Image();message.width=4;message.height=4;message.step=12;message.encoding='rgb8';message.data=bytes([90,20,10])*16
deadline=time.monotonic()+3
while 'left_color' not in camera.previews() and time.monotonic()<deadline:
publisher.publish(message);rclpy.spin_once(node,timeout_sec=.05);camera.publish_latest()
blob,width,height=camera.previews()['left_color']
assert (width,height)==(4,4) and blob[:3]==bytes([90,20,10])
import tempfile
from station.cam_bridge import _export
shms={}
try:
with tempfile.TemporaryDirectory() as directory:
_export(Path(directory),shms,{})
assert shms
first=next(iter(shms.values()))[1]
assert camera._shm_read(first)[3]==blob
finally:
for _,shm in shms.values(): shm.close();shm.unlink()
camera.watch({'right_depth'},set());node.sync_streams()
assert set(node._streams.items)=={'right_depth'}
camera.watch(set(),{'left_color'});node.sync_streams()
assert set(node._streams.items)=={'left_color'}
camera.watch(set(),set());node.sync_streams()
assert not node._streams.items and not node._detections.items
print('ROS dynamic camera subscriptions PASS',flush=True)
finally:
node.destroy_node();rclpy.shutdown()
# Exercise native Action feedback/cancellation without a robot controller.
from station.ros_gateway import RosGateway
from arm_application.site_io import load_site_dict
from sensor_msgs.msg import JointState
from controller_manager_msgs.srv import ListControllers, SwitchController
from controller_manager_msgs.msg import ControllerState
from rclpy.action import ActionServer, CancelResponse
from rclpy.callback_groups import ReentrantCallbackGroup
from control_msgs.action import FollowJointTrajectory
from station.tasks import Cancelled
gateway=RosGateway(load_site_dict())
fake=Node('fake_controller')
controller_state = ['active']
switches = []
def controllers(req,resp):
state=ControllerState();state.name='rizon_arm_controller';state.state=controller_state[0]
state.claimed_interfaces=[f"{arm['sn']}_joint{i}/position" for i in range(1,8)] if state.state=='active' else []
resp.controller=[state];return resp
fake.create_service(ListControllers,'/controller_manager/list_controllers',controllers)
def switch(req,resp):
switches.append((list(req.activate_controllers),list(req.deactivate_controllers)))
controller_state[0]='inactive' if req.deactivate_controllers else 'active'
resp.ok=True;return resp
fake.create_service(SwitchController,'/controller_manager/switch_controller',switch)
group=ReentrantCallbackGroup()
def execute(handle):
end=time.monotonic()+.7
while time.monotonic()<end:
if handle.is_cancel_requested:
handle.canceled();return FollowJointTrajectory.Result()
time.sleep(.01)
handle.succeed();return FollowJointTrajectory.Result()
action=ActionServer(fake,FollowJointTrajectory,'/rizon_arm_controller/follow_joint_trajectory',
execute_callback=execute,cancel_callback=lambda request:CancelResponse.ACCEPT,callback_group=group)
gateway.executor.add_node(fake)
arm=next(a for a in gateway.arms if a['id']==gateway.active_arm)
msg=JointState();msg.name=[f"{arm['sn']}_joint{i}" for i in range(1,8)];msg.position=[0.]*7
try:
gateway._joint(msg)
assert gateway.move_joints(arm,[.01]*7,.15,threading.Event())['ok']
gateway._joint(msg)
cancel=threading.Event();timer=threading.Timer(.3,cancel.set);timer.start()
try:
gateway.move_joints(arm,[.01]*7,.15,cancel)
raise AssertionError('expected action cancellation')
except Cancelled: pass
timer.join()
assert not gateway._goals
joint_stop=threading.Event()
def joint_feedback():
while not joint_stop.wait(.03): gateway._joint(msg)
joint_thread=threading.Thread(target=joint_feedback);joint_thread.start()
try:
assert gateway.stop_arm(arm)['ok']
assert gateway.confirm_stopped(arm)['ok']
assert controller_state[0]=='inactive'
assert switches[-1]==([],['rizon_arm_controller'])
# Only the next explicit command restores the controller.
gateway.require_arm(arm['id'],threading.Event())
assert controller_state[0]=='active'
assert switches[-1]==(['rizon_arm_controller'],[])
wrong={**arm,'sn':'OtherRobot'}
try:
gateway.stop_arm(wrong)
raise AssertionError('wrong-arm stop accepted')
except RuntimeError as exc: assert '不属于' in str(exc)
finally:
joint_stop.set();joint_thread.join()
print('ROS stop deactivation, stationary confirmation, explicit resume and arm ownership PASS',flush=True)
from arm_msgs.msg import TactileStatus
from station import site_catalog
force=TactileStatus();force.connected=True;force.fnormal=1.25;force.fshearx=-.5;force.fsheary=.75
gateway._tactile('left',force)
assert site_catalog._TACTILE['left']['fnormal']==1.25
assert site_catalog._TACTILE['left']['fshearx']==-.5
gateway.watch_tactile(True)
gateway._tactile_image('left_thermal',message)
assert 'left_thermal' in gateway.tactile_frames()
gateway.watch_tactile(False)
assert not gateway.tactile_frames() and not gateway._tactile_images.items
print('ROS tactile driver reuse, unchanged force values and preview lifetime PASS',flush=True)
print('ROS persistent gateway, arm identity and trajectory cancellation PASS',flush=True)
finally:
action.destroy();gateway.executor.remove_node(fake);fake.destroy_node();gateway.close()
if __name__=='__main__': main()