Update workstation UI, ROS task control and device integration
This commit is contained in:
@@ -8,27 +8,40 @@ from station.control_service import StationService
|
||||
class Gateway:
|
||||
active_arm='89'
|
||||
def __init__(self): self.started=None;self.requests=[]
|
||||
def graph(self): return {'nodes':[],'topics':[],'services':['/station/grippers/arm_89/execute']}
|
||||
def graph(self):
|
||||
return {'nodes':['dh_pgea'],'topics':['/gripper/states'],
|
||||
'services':['/gripper/open','/gripper/close','/gripper/initialize','/dh_pgea/execute']}
|
||||
def publisher_count(self, topic): return 1 if topic=='/gripper/states' else 0
|
||||
def states(self):
|
||||
elapsed=time.monotonic()-self.started if self.started else 0
|
||||
return {},{'89':{'connected':True,'moving':bool(self.started and elapsed<.25),
|
||||
'grip_state':0 if self.started and elapsed<.25 else 1,'permille':500.}}
|
||||
moving=bool(self.started and elapsed<getattr(self,'motion_duration',.05))
|
||||
last=self.requests[-1][0] if self.requests else ''
|
||||
if last=='/gripper/close':
|
||||
permille=0. if not moving else 400.
|
||||
elif last=='/gripper/open':
|
||||
permille=1000. if not moving else 400.
|
||||
else:
|
||||
permille=500.
|
||||
return {},{'89':{'connected':True,'moving':moving,
|
||||
'grip_state':0 if moving else 1,'permille':permille,
|
||||
'gripped':False,'opening_mm':permille/1000.*20}}
|
||||
def call(self,typ,name,request,event,timeout):
|
||||
self.requests.append(request);self.started=time.monotonic()
|
||||
self.requests.append((name,request));self.started=time.monotonic()
|
||||
return type('Reply',(),{'success':True,'message':'accepted'})()
|
||||
def close(self): pass
|
||||
|
||||
|
||||
class GripperCommandsTest(TestCase):
|
||||
def test_waits_for_physical_completion_and_rejects_bounds(self):
|
||||
gateway=Gateway()
|
||||
gateway=Gateway();gateway.motion_duration=.25
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
service=StationService(root=root,gateway=gateway)
|
||||
try:
|
||||
arm=service.arms['89'];start=time.monotonic()
|
||||
result=service._gripper('gripper_tune',arm,{'kind':'pos','pos':500,'force':33,'speed':25},threading.Event())
|
||||
self.assertTrue(result['ok']);self.assertGreater(time.monotonic()-start,.2)
|
||||
self.assertEqual(gateway.requests[0].command.position,arm['stroke_mm']/2)
|
||||
self.assertEqual(gateway.requests[0][0],'/dh_pgea/execute')
|
||||
self.assertEqual(gateway.requests[0][1].command.position,arm['stroke_mm']/2)
|
||||
count=len(gateway.requests)
|
||||
with self.assertRaises(ValueError):
|
||||
service._gripper('gripper_tune',arm,{'kind':'pos','pos':500,'force':999,'speed':25},threading.Event())
|
||||
@@ -49,3 +62,73 @@ class GripperCommandsTest(TestCase):
|
||||
self.assertEqual(gateway.requests,[])
|
||||
self.assertTrue(service._frozen_config.is_file())
|
||||
finally: service.close()
|
||||
|
||||
def test_open_close_do_not_require_initialized(self):
|
||||
gateway=Gateway()
|
||||
original=gateway.states
|
||||
def states():
|
||||
arms,grips=original()
|
||||
row=dict(grips['89']);row['initialized']=False
|
||||
return arms,{'89':row}
|
||||
gateway.states=states
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
service=StationService(root=root,gateway=gateway)
|
||||
try:
|
||||
closed=service._gripper('gripper_close',service.arms['89'],{},threading.Event())
|
||||
self.assertTrue(closed['ok'])
|
||||
self.assertEqual(gateway.requests[-1][0],'/gripper/close')
|
||||
opened=service._gripper('gripper_open',service.arms['89'],{},threading.Event())
|
||||
self.assertTrue(opened['ok'])
|
||||
self.assertEqual(gateway.requests[-1][0],'/gripper/open')
|
||||
finally: service.close()
|
||||
|
||||
def test_need_init_summary_does_not_block_open_close(self):
|
||||
from station.operator_gripper_state import present
|
||||
row=present('89',rotary=False,stroke_mm=20.0,
|
||||
reading={'connected':True,'initialized':False,'permille':981,
|
||||
'grip_state':1,'source':'ROS'})
|
||||
self.assertIn('零点未标定', row['summary'])
|
||||
self.assertIn('开口 19.6 mm', row['summary'])
|
||||
self.assertNotIn('不会动', row['summary'])
|
||||
self.assertNotIn('请先点到位', row['summary'])
|
||||
|
||||
def test_open_close_init_call_dh_pgea_triggers_and_close_must_reach_zero(self):
|
||||
gateway=Gateway()
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
service=StationService(root=root,gateway=gateway)
|
||||
try:
|
||||
arm=service.arms['89']
|
||||
closed=service._gripper('gripper_close',arm,{},threading.Event())
|
||||
self.assertTrue(closed['ok'])
|
||||
self.assertEqual(gateway.requests[-1][0],'/gripper/close')
|
||||
opened=service._gripper('gripper_open',arm,{},threading.Event())
|
||||
self.assertTrue(opened['ok'])
|
||||
self.assertEqual(gateway.requests[-1][0],'/gripper/open')
|
||||
inited=service._gripper('gripper_init',arm,{},threading.Event())
|
||||
self.assertTrue(inited['ok'])
|
||||
self.assertEqual(gateway.requests[-1][0],'/gripper/initialize')
|
||||
finally: service.close()
|
||||
|
||||
def test_close_fails_when_firmware_says_arrived_but_opening_remains(self):
|
||||
gateway=Gateway()
|
||||
def states():
|
||||
return {},{'89':{'connected':True,'moving':False,'grip_state':1,'permille':130.,
|
||||
'gripped':False,'opening_mm':2.6}}
|
||||
gateway.states=states
|
||||
with tempfile.TemporaryDirectory() as root:
|
||||
service=StationService(root=root,gateway=gateway)
|
||||
try:
|
||||
result=service._gripper('gripper_close',service.arms['89'],{},threading.Event())
|
||||
self.assertFalse(result['ok'])
|
||||
self.assertIn('未闭合到位', result['message'])
|
||||
self.assertIn('2.6', result['message'])
|
||||
finally: service.close()
|
||||
|
||||
def test_gripper_plan_starts_dh_pgea_without_auto_init(self):
|
||||
from station.launch_plans import gripper_plan
|
||||
argv=gripper_plan({'id':'89','rotary':False,'port':'/dev/ttyUSB0','stroke_mm':20.0,'gripper':'PGEA-100-20-W'},
|
||||
{'baud':115200,'force_pct':20,'speed_pct':50})
|
||||
joined=' '.join(str(part) for part in argv)
|
||||
self.assertIn('arm_application.dh_pgea_node', joined)
|
||||
self.assertIn('auto_init:=false', joined)
|
||||
self.assertNotIn('gripper_node', joined)
|
||||
|
||||
Reference in New Issue
Block a user