Skip to content

Commit 067f930

Browse files
committed
Improved ping and added RW for CLI manipulation
1 parent 4c36f80 commit 067f930

File tree

4 files changed

+201
-4
lines changed

4 files changed

+201
-4
lines changed

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,6 @@
22
__pycache__
33
.venv
44
.vscode
5+
build
6+
dist
7+
*.spec

README.md

+50
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ Pings all available servos on multiple boards connected
2828
--from_id FROM_ID From ID (default: 0)
2929
--to_id TO_ID To ID (default: 110)
3030
--json JSON Output as JSON (default: False)
31+
--find_any FIND_ANY If True, will print motor ID and return immediatly
32+
after found, if none servo found it will fail with
33+
exit code 1 (default: False)
3134
```
3235
Examples:
3336
```
@@ -40,6 +43,53 @@ Output:
4043
Where device port is `/dev/ttyUSB0` and `83` is servo ID and `6662` internal servo model.
4144

4245

46+
### RW
47+
Allows to read and write registers to the servo
48+
```bash
49+
./rw.py --help
50+
```
51+
52+
```
53+
usage: rw.py [-h] [--hardware_regex HARDWARE_REGEX] [--baud BAUD]
54+
[--protocol PROTOCOL] --id ID [--addr ADDR] [--length {1,2}]
55+
[--negative_bit NEGATIVE_BIT] [--write WRITE]
56+
[--action {reset_neutral,id}]
57+
58+
Read Write Feetech registers
59+
60+
optional arguments:
61+
-h, --help show this help message and exit
62+
--hardware_regex HARDWARE_REGEX
63+
Serial port filter for detecting multiple boards or
64+
specify single board (default: 1A86:7523)
65+
--baud BAUD Baudrate (default: 1000000)
66+
--protocol PROTOCOL SCS Protocol (default: 0)
67+
--id ID Servo ID (default: None)
68+
--addr ADDR Register (default: None)
69+
--length {1,2} Address length in bytes (default: None)
70+
--negative_bit NEGATIVE_BIT
71+
Negative sign bit for this register (default: None)
72+
--write WRITE Data to write in the decimal format (default: None)
73+
--action {reset_neutral,id}
74+
Defined actions such as set (default: None)
75+
```
76+
Examples:
77+
Writes goal_position:
78+
```
79+
./rw.py --id 3 --addr 42 --length 2 --write 1000
80+
```
81+
82+
Resets servo to neutral:
83+
```
84+
./rw.py --id 3 --action reset_neutral
85+
```
86+
87+
Reads present position:
88+
```
89+
./rw.py --id 3 --addr 56 --length 2
90+
```
91+
92+
4393
### Meassure
4494
Measures the time it takes for servo to move from one position to another
4595
Usage:

ping.py

+16-4
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,16 @@
2828
def argument_parser():
2929
parser = argparse.ArgumentParser(description="Ping all feetech servos", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
3030
# By default we search for all CH430 ports with
31-
parser.add_argument("--hardware_regex", type=str, default='1A86:7523', help='Serial port filter for detecting multiple boards')
31+
parser.add_argument("--hardware_regex", type=str, default='1A86:7523', help='Serial port filter for detecting multiple boards or specify single board')
3232
parser.add_argument("--baud", type=int, default=1000000, help='Baudrate')
3333
parser.add_argument("--from_id", type=int, default=0, help='From ID')
3434
parser.add_argument("--to_id", type=int, default=110, help='To ID')
3535
parser.add_argument("--json", type=bool, default=False, help="Output as JSON")
36+
parser.add_argument("--find_any", type=bool, default=False, help="If True, will print motor ID and return immediatly after found, if none servo found it will fail with exit code 1")
3637
args = parser.parse_args()
3738
return vars(args)
39+
# Global dictionary for args
40+
args = argument_parser()
3841

3942
def pingservos(port, baud, from_id, to_id):
4043
port = sdk.PortHandler(port)
@@ -49,22 +52,31 @@ def pingservos(port, baud, from_id, to_id):
4952
model_number, result, error = handler.ping(port, id)
5053
if result == sdk.COMM_SUCCESS:
5154
servos.append((id, model_number))
55+
if args['find_any']:
56+
servo_found(id)
5257
return servos
5358
except Exception as e:
5459
print(e)
5560
return None
5661

57-
def find_all_servos(args):
62+
def find_all_servos():
5863
ports = [p.device for p in grep_serial_ports(args['hardware_regex'])]
5964
devices = {}
6065
for p in ports:
6166
devices[p] = pingservos(p, args['baud'], args['from_id'], args['to_id'])
6267
return devices
6368

69+
def servo_found(id):
70+
# Simple output
71+
print(id)
72+
exit(0)
6473

6574
def main():
66-
args = argument_parser()
67-
device_servos = find_all_servos(args)
75+
device_servos = find_all_servos()
76+
if args['find_any']:
77+
print("No servo found")
78+
exit(1)
79+
6880
if args['json']:
6981
print(json.dumps(device_servos, indent=4, sort_keys=True))
7082
else:

rw.py

+132
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
#!/usr/bin/env python
2+
#
3+
# Copyright (c) 2022 Hanson Robotics.
4+
#
5+
# This file is part of Hanson AI.
6+
# See https://www.hansonrobotics.com/hanson-ai for further info.
7+
#
8+
# Licensed under the Apache License, Version 2.0 (the "License");
9+
# you may not use this file except in compliance with the License.
10+
# You may obtain a copy of the License at
11+
#
12+
# http://www.apache.org/licenses/LICENSE-2.0
13+
#
14+
# Unless required by applicable law or agreed to in writing, software
15+
# distributed under the License is distributed on an "AS IS" BASIS,
16+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
# See the License for the specific language governing permissions and
18+
# limitations under the License.
19+
#
20+
21+
import argparse
22+
import json
23+
from pprint import pprint as pp
24+
from serial.tools.list_ports import grep as grep_serial_ports
25+
import scservo_sdk as sdk
26+
27+
ACTIONS = {
28+
'reset_neutral':
29+
{
30+
'addr': 40,
31+
'length': 1,
32+
'write': 128
33+
},
34+
'id':
35+
{
36+
'addr': 5,
37+
'length': 1,
38+
}
39+
}
40+
41+
def argument_parser():
42+
parser = argparse.ArgumentParser(description="Read Write Feetech registers", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
43+
# By default we search for all CH430 ports with
44+
parser.add_argument("--hardware_regex", type=str, default='1A86:7523', help='Serial port filter for detecting multiple boards or specify single board')
45+
parser.add_argument("--baud", type=int, default=1000000, help='Baudrate')
46+
parser.add_argument("--protocol", type=int, default=0, help='SCS Protocol')
47+
parser.add_argument("--id", type=int, help='Servo ID', required=True)
48+
parser.add_argument("--addr", type=int, help='Register')
49+
parser.add_argument("--length", type=int, help='Address length in bytes', choices=[1,2])
50+
parser.add_argument("--negative_bit", type=int, help='Negative sign bit for this register')
51+
parser.add_argument("--write", type=int, help='Data to write in the decimal format ')
52+
parser.add_argument("--action", type=str, help="Defined actions such as set", choices=list(ACTIONS.keys()))
53+
args = parser.parse_args()
54+
args = vars(args)
55+
if args.get('action', False):
56+
args.update(ACTIONS[args['action']])
57+
if not args.get('addr', False):
58+
parser.error("Register address is required")
59+
if not args.get('length', False):
60+
parser.error("Register address length is required")
61+
return args
62+
63+
64+
args = argument_parser()
65+
66+
def find_servo_port(port, baud, id):
67+
port = sdk.PortHandler(port)
68+
handler = sdk.PacketHandler(args['protocol'])
69+
try:
70+
port.openPort()
71+
port.setBaudRate(baud)
72+
model_number, result, error = handler.ping(port, id)
73+
if result == sdk.COMM_SUCCESS:
74+
return port, handler
75+
except Exception as e:
76+
print(e)
77+
return None, None
78+
79+
def find_servo():
80+
ports = [p.device for p in grep_serial_ports(args['hardware_regex'])]
81+
for p in ports:
82+
port, handler = find_servo_port(p, args['baud'], args['id'])
83+
if port is not None:
84+
return port, handler
85+
print("Servo not found")
86+
exit(1)
87+
88+
def write(port, handler):
89+
data = args['write']
90+
length = args['length']
91+
# negative values
92+
if data < 0:
93+
bit = args['negative_bit']
94+
if bit < 2 or bit > length * 8 - 1:
95+
print("Invalid negative bit")
96+
data = abs(data) + 2**bit
97+
if length == 1:
98+
result, error = handler.write1ByteTxRx(port, args['id'], args['addr'], data)
99+
if length == 2:
100+
result, error = handler.write2ByteTxRx(port, args['id'], args['addr'], data)
101+
if not result == sdk.COMM_SUCCESS:
102+
print("Error wiriting data")
103+
exit(1)
104+
else:
105+
print("Sucessfully written")
106+
exit()
107+
108+
def read(port, handler):
109+
length = args['length']
110+
if length == 1:
111+
data, result, error = handler.read1ByteTxRx(port, args['id'], args['addr'])
112+
if length == 2:
113+
data, result, error = handler.read2ByteTxRx(port, args['id'], args['addr'])
114+
if not result == sdk.COMM_SUCCESS:
115+
print("Error reading data")
116+
exit(1)
117+
else:
118+
if args['negative_bit'] is not None:
119+
bit = args['negative_bit']
120+
if data > 2**bit:
121+
data = 0-data%(2**bit)
122+
print(data)
123+
124+
def main():
125+
port, handler = find_servo()
126+
if args.get('write') is not None:
127+
write(port, handler)
128+
else:
129+
read(port, handler)
130+
131+
if __name__ == '__main__':
132+
main()

0 commit comments

Comments
 (0)