Skip to content

Commit 3e7d736

Browse files
committed
Initial commit
0 parents  commit 3e7d736

11 files changed

+1063
-0
lines changed

.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
*.pyc
2+
__pycache__
3+
.venv
4+
.vscode

README.md

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
## REQUIREMENT
2+
- Python 3.8
3+
- Serial port access, for Ubuntu current user needs to be in dialout group
4+
## INSTALLATION
5+
For Ubuntu:
6+
7+
```bash
8+
git clone https://github.com/hansonrobotics/servo_experiments
9+
cd servo_expriments
10+
python3.8 -m venv .venv --python /usr/bin/python3.8
11+
source .venv/bin/activate
12+
pip install -r requirements.txt
13+
```
14+
15+
## Tools
16+
17+
### Meassure
18+
Measures the time it takes for servo to move from one position to another
19+
Useage:
20+
```bash
21+
./meassure.py --help
22+
```
23+
```txt
24+
usage: meassure.py [-h] [--port PORT] [--baud BAUD] [--protocol PROTOCOL] [--id ID] [--from FROM] [--to TO] [--goback GOBACK] [--title TITLE] [--percieved PERCIEVED]
25+
26+
Meassure servo speed based on feedback
27+
28+
optional arguments:
29+
-h, --help show this help message and exit
30+
--port PORT Serial port (default: /dev/ttyUSB0)
31+
--baud BAUD Baudrate (default: 1000000)
32+
--protocol PROTOCOL SCS Protocol (default: 0)
33+
--id ID Servo ID (default: 1)
34+
--from FROM From Position (default: 500)
35+
--to TO To Position (default: 1000)
36+
--goback GOBACK Meassures time to get back to initial position as well (default: False)
37+
--title TITLE Title for graph (default: Servo Position)
38+
--percieved PERCIEVED
39+
Remove first n percent of movement, to show percieved stats (default: 0)
40+
```
41+
Examples:
42+
```bash
43+
./meassure.py --id 1 --protocol 0 --from 300 --to 910 --goback true --percieved 5
44+
45+
```

meassure.py

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#!/usr/bin/env python
2+
3+
4+
from ast import Return
5+
from dataclasses import dataclass
6+
import time
7+
import argparse
8+
import time
9+
import scservo_sdk as sdk
10+
import matplotlib.pyplot as plt
11+
import numpy as np
12+
13+
# Control table address
14+
ADDR_SCS_TORQUE_ENABLE = 40
15+
ADDR_SCS_GOAL_ACC = 41
16+
ADDR_SCS_GOAL_POSITION = 42
17+
ADDR_SCS_GOAL_SPEED = 46
18+
ADDR_SCS_PRESENT_POSITION = 56
19+
MOVING_THRESHOLD = 5
20+
STEPS_IN_DEG = {
21+
0: 11.38,
22+
1: 4.65,
23+
}
24+
25+
def argument_parser():
26+
parser = argparse.ArgumentParser(description="Meassure servo speed based on feedback", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
27+
parser.add_argument("--port", type=str, default='/dev/ttyUSB0', help='Serial port')
28+
parser.add_argument("--baud", type=int, default=1000000, help='Baudrate')
29+
parser.add_argument("--protocol", type=int, default=0, help='SCS Protocol')
30+
parser.add_argument("--id", type=int, default=1, help='Servo ID')
31+
parser.add_argument("--from", type=int, default=500, help='From Position')
32+
parser.add_argument("--to", type=int, default=1000, help='To Position')
33+
parser.add_argument("--goback", type=bool, default=False, help='Meassures time to get back to initial position as well')
34+
parser.add_argument("--title", type=str, default="Servo Position", help='Title for graph')
35+
parser.add_argument("--percieved", type=int, default=0, help='Remove first n percent of movement, to show percieved stats')
36+
37+
args = parser.parse_args()
38+
return vars(args)
39+
40+
41+
42+
def plot(x, y, xlabel='Time (s)', ylabel='Degrees', title='Servo Position'):
43+
plt.plot(x, y)
44+
plt.xlabel(xlabel)
45+
plt.ylabel(ylabel)
46+
plt.title(title)
47+
plt.show()
48+
49+
50+
def move_to(group_sync_write, id, position):
51+
param_goal_position = [sdk.SCS_LOBYTE(position), sdk.SCS_HIBYTE(position)]
52+
group_sync_write.addParam(id, param_goal_position)
53+
group_sync_write.txPacket()
54+
group_sync_write.clearParam()
55+
56+
def move_to_single(port, packets, id, position):
57+
packets.write2ByteTxRx(port, id, ADDR_SCS_GOAL_POSITION, position)
58+
59+
def current_position(group_sync_read, id, ph=None):
60+
scs_comm_result = group_sync_read.txRxPacket()
61+
if scs_comm_result != 0:
62+
print("Error")
63+
print(ph.getTxRxResult(scs_comm_result))
64+
65+
data = group_sync_read.getData(id, ADDR_SCS_PRESENT_POSITION, 2)
66+
return data
67+
68+
def current_position_single(port, packets, id):
69+
data, status, error = packets.read2ByteTxRx(port, id, ADDR_SCS_PRESENT_POSITION)
70+
if status or error:
71+
raise Exception(f"Error reading position: {status} {error}")
72+
return data
73+
74+
75+
76+
77+
def main():
78+
args = argument_parser()
79+
port = sdk.PortHandler(args['port'])
80+
print(args['protocol'])
81+
82+
packets = sdk.PacketHandler(args['protocol'])
83+
if not port.openPort() and port.setBaudRate(args['baud']):
84+
print(f"Cant Open port {args['port']}")
85+
return
86+
id = args['id']
87+
scs_model_number, scs_comm_result, scs_error = packets.ping(port, id)
88+
if scs_comm_result or scs_error:
89+
print("Cant ping servo")
90+
return
91+
else:
92+
print(f"Servo with ID: {id} is found. Model {scs_model_number}")
93+
times, positions = meassure(port, packets, id, args['from'], args['to'], args['goback'], STEPS_IN_DEG[args['protocol']])
94+
if args['percieved'] > 0:
95+
percieved_angle_threshold = (args['to'] - args['from']) * args['percieved'] / 100 / STEPS_IN_DEG[args['protocol']]
96+
print(percieved_angle_threshold)
97+
times, positions = perceived_data(times, positions, percieved_angle_threshold)
98+
print(f'Starting: {times[0]}, duration: {times[-1] - times[0]}')
99+
100+
plot(times, positions, args['title'])
101+
102+
def perceived_data(times, positions,threshold):
103+
percieved_times = []
104+
percieved_positions = []
105+
for p,t in zip(positions, times):
106+
if abs(p) > abs(threshold):
107+
percieved_positions.append(p)
108+
percieved_times.append(t)
109+
return percieved_times, percieved_positions
110+
111+
def meassure(port,packets, id, start, end, back=True, steps_in_deg=1):
112+
group_sync_read = sdk.GroupSyncRead(port, packets, ADDR_SCS_PRESENT_POSITION, 2)
113+
group_sync_read.addParam(id)
114+
group_sync_write = sdk.GroupSyncWrite(port, packets, ADDR_SCS_GOAL_POSITION, 2)
115+
move_to(group_sync_write, id, start)
116+
print("Moving to initial position")
117+
time.sleep(1)
118+
#starting = current_position(group_sync_read, id, packets)
119+
starting = current_position_single(port, packets, id)
120+
time.sleep(1)
121+
print(f"Starting position: {starting}")
122+
# position
123+
positions = [starting]
124+
start_time = time.perf_counter()
125+
times = [0]
126+
current = starting
127+
move_to(group_sync_write, id, end)
128+
#move_to_single(port, packets, id, end)
129+
goal = end
130+
while True:
131+
current = current_position_single(port, packets, id)
132+
positions.append(current)
133+
times.append(time.perf_counter() - start_time)
134+
if abs(goal-current) < MOVING_THRESHOLD + 50*back:
135+
if back:
136+
back = False
137+
goal = start
138+
move_to(group_sync_write, id, goal)
139+
else:
140+
break
141+
if back:
142+
move_to(group_sync_write, id, start)
143+
print(f"Movement finished in {times[-1]} seconds. samples {len(times)}")
144+
positions = [(p-start)/steps_in_deg for p in positions]
145+
return times, positions
146+
147+
if __name__ == '__main__':
148+
main()

requirements.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pyqt5
2+
matplotlib
3+
pyserial

scservo_sdk/__init__.py

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/usr/bin/env python
2+
3+
from .port_handler import *
4+
from .packet_handler import *
5+
from .group_sync_read import *
6+
from .group_sync_write import *

scservo_sdk/group_sync_read.py

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#!/usr/bin/env python
2+
3+
from .scservo_def import *
4+
5+
class GroupSyncRead:
6+
def __init__(self, port, ph, start_address, data_length):
7+
self.port = port
8+
self.ph = ph
9+
self.start_address = start_address
10+
self.data_length = data_length
11+
12+
self.last_result = False
13+
self.is_param_changed = False
14+
self.param = []
15+
self.data_dict = {}
16+
17+
self.clearParam()
18+
19+
def makeParam(self):
20+
if not self.data_dict: # len(self.data_dict.keys()) == 0:
21+
return
22+
23+
self.param = []
24+
25+
for scs_id in self.data_dict:
26+
self.param.append(scs_id)
27+
28+
def addParam(self, scs_id):
29+
if scs_id in self.data_dict: # scs_id already exist
30+
return False
31+
32+
self.data_dict[scs_id] = [] # [0] * self.data_length
33+
34+
self.is_param_changed = True
35+
return True
36+
37+
def removeParam(self, scs_id):
38+
if scs_id not in self.data_dict: # NOT exist
39+
return
40+
41+
del self.data_dict[scs_id]
42+
43+
self.is_param_changed = True
44+
45+
def clearParam(self):
46+
self.data_dict.clear()
47+
48+
def txPacket(self):
49+
if len(self.data_dict.keys()) == 0:
50+
return COMM_NOT_AVAILABLE
51+
52+
if self.is_param_changed is True or not self.param:
53+
self.makeParam()
54+
55+
return self.ph.syncReadTx(self.port, self.start_address, self.data_length, self.param,
56+
len(self.data_dict.keys()) * 1)
57+
58+
def rxPacket(self):
59+
self.last_result = False
60+
61+
result = COMM_RX_FAIL
62+
63+
if len(self.data_dict.keys()) == 0:
64+
return COMM_NOT_AVAILABLE
65+
66+
for scs_id in self.data_dict:
67+
self.data_dict[scs_id], result, _ = self.ph.readRx(self.port, scs_id, self.data_length)
68+
if result != COMM_SUCCESS:
69+
return result
70+
71+
if result == COMM_SUCCESS:
72+
self.last_result = True
73+
74+
return result
75+
76+
def txRxPacket(self):
77+
result = self.txPacket()
78+
if result != COMM_SUCCESS:
79+
return result
80+
81+
return self.rxPacket()
82+
83+
def isAvailable(self, scs_id, address, data_length):
84+
#if self.last_result is False or scs_id not in self.data_dict:
85+
if scs_id not in self.data_dict:
86+
return False
87+
88+
if (address < self.start_address) or (self.start_address + self.data_length - data_length < address):
89+
return False
90+
91+
if len(self.data_dict[scs_id])<data_length:
92+
return False
93+
return True
94+
95+
def getData(self, scs_id, address, data_length):
96+
if not self.isAvailable(scs_id, address, data_length):
97+
return 0
98+
99+
if data_length == 1:
100+
return self.data_dict[scs_id][address - self.start_address]
101+
elif data_length == 2:
102+
return SCS_MAKEWORD(self.data_dict[scs_id][address - self.start_address],
103+
self.data_dict[scs_id][address - self.start_address + 1])
104+
elif data_length == 4:
105+
return SCS_MAKEDWORD(SCS_MAKEWORD(self.data_dict[scs_id][address - self.start_address + 0],
106+
self.data_dict[scs_id][address - self.start_address + 1]),
107+
SCS_MAKEWORD(self.data_dict[scs_id][address - self.start_address + 2],
108+
self.data_dict[scs_id][address - self.start_address + 3]))
109+
else:
110+
return 0

0 commit comments

Comments
 (0)