feetech-cli 0.1.0__tar.gz → 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {feetech_cli-0.1.0 → feetech_cli-0.1.2}/PKG-INFO +66 -10
- {feetech_cli-0.1.0 → feetech_cli-0.1.2}/README.md +65 -9
- {feetech_cli-0.1.0 → feetech_cli-0.1.2}/feetech_cli/__init__.py +1 -1
- {feetech_cli-0.1.0 → feetech_cli-0.1.2}/feetech_cli/cli.py +17 -4
- {feetech_cli-0.1.0 → feetech_cli-0.1.2}/feetech_cli/registers.py +61 -1
- {feetech_cli-0.1.0 → feetech_cli-0.1.2}/feetech_cli/tui.py +6 -1
- {feetech_cli-0.1.0 → feetech_cli-0.1.2}/feetech_cli.egg-info/PKG-INFO +66 -10
- {feetech_cli-0.1.0 → feetech_cli-0.1.2}/feetech_cli.egg-info/SOURCES.txt +1 -0
- {feetech_cli-0.1.0 → feetech_cli-0.1.2}/pyproject.toml +1 -1
- feetech_cli-0.1.2/tests/test_registers.py +29 -0
- {feetech_cli-0.1.0 → feetech_cli-0.1.2}/LICENSE +0 -0
- {feetech_cli-0.1.0 → feetech_cli-0.1.2}/feetech_cli/controller.py +0 -0
- {feetech_cli-0.1.0 → feetech_cli-0.1.2}/feetech_cli/protocol.py +0 -0
- {feetech_cli-0.1.0 → feetech_cli-0.1.2}/feetech_cli/simulator.py +0 -0
- {feetech_cli-0.1.0 → feetech_cli-0.1.2}/feetech_cli.egg-info/dependency_links.txt +0 -0
- {feetech_cli-0.1.0 → feetech_cli-0.1.2}/feetech_cli.egg-info/entry_points.txt +0 -0
- {feetech_cli-0.1.0 → feetech_cli-0.1.2}/feetech_cli.egg-info/requires.txt +0 -0
- {feetech_cli-0.1.0 → feetech_cli-0.1.2}/feetech_cli.egg-info/top_level.txt +0 -0
- {feetech_cli-0.1.0 → feetech_cli-0.1.2}/setup.cfg +0 -0
- {feetech_cli-0.1.0 → feetech_cli-0.1.2}/tests/test_cli.py +0 -0
- {feetech_cli-0.1.0 → feetech_cli-0.1.2}/tests/test_controller.py +0 -0
- {feetech_cli-0.1.0 → feetech_cli-0.1.2}/tests/test_protocol.py +0 -0
- {feetech_cli-0.1.0 → feetech_cli-0.1.2}/tests/test_terminal.py +0 -0
- {feetech_cli-0.1.0 → feetech_cli-0.1.2}/tests/test_tui.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: feetech-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Command line tool to inspect and configure FEETECH STS/SMS/SCS serial bus servos
|
|
5
5
|
Author-email: Iori Yanokura <ab.ioryz@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -31,14 +31,17 @@ or watch a live status screen.
|
|
|
31
31
|
|
|
32
32
|
## Install
|
|
33
33
|
|
|
34
|
+
With [uv](https://docs.astral.sh/uv/), which installs the `feetech` command in its
|
|
35
|
+
own environment and on your path:
|
|
36
|
+
|
|
34
37
|
```console
|
|
35
|
-
$
|
|
38
|
+
$ uv tool install feetech-cli
|
|
36
39
|
```
|
|
37
40
|
|
|
38
|
-
or
|
|
41
|
+
or with pip:
|
|
39
42
|
|
|
40
43
|
```console
|
|
41
|
-
$
|
|
44
|
+
$ pip install feetech-cli
|
|
42
45
|
```
|
|
43
46
|
|
|
44
47
|
The package is also a Python library: `from feetech_cli import FeetechServoController`.
|
|
@@ -71,13 +74,64 @@ is running, so a simulation is never mistaken for a real bus.
|
|
|
71
74
|
|
|
72
75
|
## Changing a servo id
|
|
73
76
|
|
|
74
|
-
|
|
77
|
+
New STS3215 servos all leave the factory as **id 1 at 1 Mbps**. Every servo on
|
|
78
|
+
a bus needs its own id, so a robot with several servos starts with giving each
|
|
79
|
+
one a different id — **one servo at a time**.
|
|
75
80
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
+
### Walkthrough: numbering a batch of new servos
|
|
82
|
+
|
|
83
|
+
1. **Connect exactly one servo** to the USB adapter, and power the adapter
|
|
84
|
+
from its own supply. USB alone does not power a servo.
|
|
85
|
+
|
|
86
|
+
2. Check that it answers, and at which id:
|
|
87
|
+
|
|
88
|
+
```console
|
|
89
|
+
$ feetech scan
|
|
90
|
+
Found 1 servo(s) on /dev/ttyUSB0 at 1.00Mbps:
|
|
91
|
+
id 1 model 777 position 2048 (+0.0 deg) 12.1V 35C
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
3. Give it its new id. Numbering three servos 1, 2 and 3, the first one can
|
|
95
|
+
keep id 1; the second becomes 2:
|
|
96
|
+
|
|
97
|
+
```console
|
|
98
|
+
$ feetech set-id 1 2
|
|
99
|
+
Change servo 1 to id 2? This writes the servo EEPROM. [y/N] y
|
|
100
|
+
Servo 1 is now id 2.
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The id is stored in EEPROM, so it survives a power cycle.
|
|
104
|
+
|
|
105
|
+
4. **Label the servo** with its new id (a piece of masking tape is enough).
|
|
106
|
+
Once they are mixed up, the only way to tell them apart again is to connect
|
|
107
|
+
them one by one.
|
|
108
|
+
|
|
109
|
+
5. Unplug it, connect the next new servo, and repeat from step 2 with the
|
|
110
|
+
next id (`feetech set-id 1 3` for the third).
|
|
111
|
+
|
|
112
|
+
6. With every servo numbered, daisy-chain them all and check the whole set
|
|
113
|
+
answers:
|
|
114
|
+
|
|
115
|
+
```console
|
|
116
|
+
$ feetech scan
|
|
117
|
+
Found 3 servo(s) on /dev/ttyUSB0 at 1.00Mbps:
|
|
118
|
+
id 1 model 777 position 2048 (+0.0 deg) 12.1V 35C
|
|
119
|
+
id 2 model 777 position 2048 (+0.0 deg) 12.1V 35C
|
|
120
|
+
id 3 model 777 position 2048 (+0.0 deg) 12.1V 35C
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Why one at a time.** Two new servos on the same bus both answer to id 1.
|
|
124
|
+
Their replies collide, and a write addressed to id 1 reaches both of them:
|
|
125
|
+
`set-id 1 2` would renumber both servos to 2, and the check that follows
|
|
126
|
+
would find "a servo at id 2" and report success. `set-id` cannot tell one
|
|
127
|
+
servo at an id from two, so keeping a single unnumbered servo on the bus is
|
|
128
|
+
up to you.
|
|
129
|
+
|
|
130
|
+
If `scan` finds nothing, the servo may have been set to another bus speed
|
|
131
|
+
before. `feetech scan --all-baudrates` tries every speed. Servos with an id
|
|
132
|
+
above 16 need `--max-id 253`.
|
|
133
|
+
|
|
134
|
+
### What set-id does
|
|
81
135
|
|
|
82
136
|
`set-id` refuses an id that another servo on the bus already answers to,
|
|
83
137
|
switches torque off first, unlocks the EEPROM, writes, locks it again, and
|
|
@@ -404,6 +458,8 @@ Sources:
|
|
|
404
458
|
- AkariGroup feetech_setup (baud table): https://github.com/AkariGroup/feetech_setup/blob/main/set_baudrate.py
|
|
405
459
|
- LeRobot control table and sign-magnitude encodings: https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/feetech/tables.py
|
|
406
460
|
- FEETECH start tutorial: https://www.feetechrc.com/Data/feetechrc/upload/file/20201127/start%20%20tutorial201015.pdf
|
|
461
|
+
- Factory default id 1: https://www.waveshare.com/wiki/ST3215_Servo ; id 1 and 1 Mbps: the STS3215 register reference above
|
|
462
|
+
- LeRobot SO-101 assembly, numbering the motors one at a time: https://huggingface.co/docs/lerobot/main/en/assemble_so101
|
|
407
463
|
|
|
408
464
|
[lerobot]: https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/feetech/tables.py
|
|
409
465
|
|
|
@@ -6,14 +6,17 @@ or watch a live status screen.
|
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
|
+
With [uv](https://docs.astral.sh/uv/), which installs the `feetech` command in its
|
|
10
|
+
own environment and on your path:
|
|
11
|
+
|
|
9
12
|
```console
|
|
10
|
-
$
|
|
13
|
+
$ uv tool install feetech-cli
|
|
11
14
|
```
|
|
12
15
|
|
|
13
|
-
or
|
|
16
|
+
or with pip:
|
|
14
17
|
|
|
15
18
|
```console
|
|
16
|
-
$
|
|
19
|
+
$ pip install feetech-cli
|
|
17
20
|
```
|
|
18
21
|
|
|
19
22
|
The package is also a Python library: `from feetech_cli import FeetechServoController`.
|
|
@@ -46,13 +49,64 @@ is running, so a simulation is never mistaken for a real bus.
|
|
|
46
49
|
|
|
47
50
|
## Changing a servo id
|
|
48
51
|
|
|
49
|
-
|
|
52
|
+
New STS3215 servos all leave the factory as **id 1 at 1 Mbps**. Every servo on
|
|
53
|
+
a bus needs its own id, so a robot with several servos starts with giving each
|
|
54
|
+
one a different id — **one servo at a time**.
|
|
50
55
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
### Walkthrough: numbering a batch of new servos
|
|
57
|
+
|
|
58
|
+
1. **Connect exactly one servo** to the USB adapter, and power the adapter
|
|
59
|
+
from its own supply. USB alone does not power a servo.
|
|
60
|
+
|
|
61
|
+
2. Check that it answers, and at which id:
|
|
62
|
+
|
|
63
|
+
```console
|
|
64
|
+
$ feetech scan
|
|
65
|
+
Found 1 servo(s) on /dev/ttyUSB0 at 1.00Mbps:
|
|
66
|
+
id 1 model 777 position 2048 (+0.0 deg) 12.1V 35C
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
3. Give it its new id. Numbering three servos 1, 2 and 3, the first one can
|
|
70
|
+
keep id 1; the second becomes 2:
|
|
71
|
+
|
|
72
|
+
```console
|
|
73
|
+
$ feetech set-id 1 2
|
|
74
|
+
Change servo 1 to id 2? This writes the servo EEPROM. [y/N] y
|
|
75
|
+
Servo 1 is now id 2.
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The id is stored in EEPROM, so it survives a power cycle.
|
|
79
|
+
|
|
80
|
+
4. **Label the servo** with its new id (a piece of masking tape is enough).
|
|
81
|
+
Once they are mixed up, the only way to tell them apart again is to connect
|
|
82
|
+
them one by one.
|
|
83
|
+
|
|
84
|
+
5. Unplug it, connect the next new servo, and repeat from step 2 with the
|
|
85
|
+
next id (`feetech set-id 1 3` for the third).
|
|
86
|
+
|
|
87
|
+
6. With every servo numbered, daisy-chain them all and check the whole set
|
|
88
|
+
answers:
|
|
89
|
+
|
|
90
|
+
```console
|
|
91
|
+
$ feetech scan
|
|
92
|
+
Found 3 servo(s) on /dev/ttyUSB0 at 1.00Mbps:
|
|
93
|
+
id 1 model 777 position 2048 (+0.0 deg) 12.1V 35C
|
|
94
|
+
id 2 model 777 position 2048 (+0.0 deg) 12.1V 35C
|
|
95
|
+
id 3 model 777 position 2048 (+0.0 deg) 12.1V 35C
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**Why one at a time.** Two new servos on the same bus both answer to id 1.
|
|
99
|
+
Their replies collide, and a write addressed to id 1 reaches both of them:
|
|
100
|
+
`set-id 1 2` would renumber both servos to 2, and the check that follows
|
|
101
|
+
would find "a servo at id 2" and report success. `set-id` cannot tell one
|
|
102
|
+
servo at an id from two, so keeping a single unnumbered servo on the bus is
|
|
103
|
+
up to you.
|
|
104
|
+
|
|
105
|
+
If `scan` finds nothing, the servo may have been set to another bus speed
|
|
106
|
+
before. `feetech scan --all-baudrates` tries every speed. Servos with an id
|
|
107
|
+
above 16 need `--max-id 253`.
|
|
108
|
+
|
|
109
|
+
### What set-id does
|
|
56
110
|
|
|
57
111
|
`set-id` refuses an id that another servo on the bus already answers to,
|
|
58
112
|
switches torque off first, unlocks the EEPROM, writes, locks it again, and
|
|
@@ -379,6 +433,8 @@ Sources:
|
|
|
379
433
|
- AkariGroup feetech_setup (baud table): https://github.com/AkariGroup/feetech_setup/blob/main/set_baudrate.py
|
|
380
434
|
- LeRobot control table and sign-magnitude encodings: https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/feetech/tables.py
|
|
381
435
|
- FEETECH start tutorial: https://www.feetechrc.com/Data/feetechrc/upload/file/20201127/start%20%20tutorial201015.pdf
|
|
436
|
+
- Factory default id 1: https://www.waveshare.com/wiki/ST3215_Servo ; id 1 and 1 Mbps: the STS3215 register reference above
|
|
437
|
+
- LeRobot SO-101 assembly, numbering the motors one at a time: https://huggingface.co/docs/lerobot/main/en/assemble_so101
|
|
382
438
|
|
|
383
439
|
[lerobot]: https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/feetech/tables.py
|
|
384
440
|
|
|
@@ -20,6 +20,7 @@ from feetech_cli.registers import INFO_REGISTERS
|
|
|
20
20
|
from feetech_cli.registers import OPERATING_MODES
|
|
21
21
|
from feetech_cli.registers import degree_to_position
|
|
22
22
|
from feetech_cli.registers import format_baud
|
|
23
|
+
from feetech_cli.registers import format_current
|
|
23
24
|
from feetech_cli.registers import position_to_degree
|
|
24
25
|
from feetech_cli.tui import ServoTui
|
|
25
26
|
|
|
@@ -411,13 +412,16 @@ def command_info(args, controller):
|
|
|
411
412
|
value = values[name]
|
|
412
413
|
print(
|
|
413
414
|
" {:<{width}} addr {:>3} {}".format(
|
|
414
|
-
name,
|
|
415
|
+
name,
|
|
416
|
+
register.address,
|
|
417
|
+
_annotate(name, value, values),
|
|
418
|
+
width=width,
|
|
415
419
|
)
|
|
416
420
|
)
|
|
417
421
|
return 0
|
|
418
422
|
|
|
419
423
|
|
|
420
|
-
def _annotate(name, value):
|
|
424
|
+
def _annotate(name, value, context=None):
|
|
421
425
|
"""Render a register value with a unit or label where one applies.
|
|
422
426
|
|
|
423
427
|
Parameters
|
|
@@ -426,6 +430,9 @@ def _annotate(name, value):
|
|
|
426
430
|
Register name.
|
|
427
431
|
value : int or None
|
|
428
432
|
Register value.
|
|
433
|
+
context : dict or None, optional
|
|
434
|
+
Other register values of the same servo. ``present_current`` uses
|
|
435
|
+
``model_number`` and ``present_load`` from it.
|
|
429
436
|
|
|
430
437
|
Returns
|
|
431
438
|
-------
|
|
@@ -450,7 +457,10 @@ def _annotate(name, value):
|
|
|
450
457
|
if name == "present_load":
|
|
451
458
|
return f"{value} ({value / 10.0:+.1f} %)"
|
|
452
459
|
if name == "present_current":
|
|
453
|
-
|
|
460
|
+
context = context or {}
|
|
461
|
+
return format_current(
|
|
462
|
+
value, context.get("model_number"), context.get("present_load")
|
|
463
|
+
)
|
|
454
464
|
if name == "torque_enable":
|
|
455
465
|
return "{} ({})".format(value, "on" if value else "off")
|
|
456
466
|
if name == "lock":
|
|
@@ -532,7 +542,10 @@ def command_read(args, controller):
|
|
|
532
542
|
Process exit status.
|
|
533
543
|
"""
|
|
534
544
|
value = controller.read_register(args.id, args.register)
|
|
535
|
-
|
|
545
|
+
context = None
|
|
546
|
+
if args.register == "present_current":
|
|
547
|
+
context = controller.dump(args.id, ["model_number", "present_load"])
|
|
548
|
+
print(f"{args.register} = {_annotate(args.register, value, context)}")
|
|
536
549
|
return 0
|
|
537
550
|
|
|
538
551
|
|
|
@@ -126,7 +126,9 @@ CONTROL_TABLE = {
|
|
|
126
126
|
"async_write_flag": _sram(64, 1, writable=False),
|
|
127
127
|
"status": _sram(65, 1, writable=False),
|
|
128
128
|
"moving": _sram(66, 1, writable=False),
|
|
129
|
-
|
|
129
|
+
# Bit 15 carries the sign, as in FEETECH's SMS_STS::ReadCurrent:
|
|
130
|
+
# https://github.com/adityakamath/SCServo_Linux/blob/main/src/SMS_STS.cpp
|
|
131
|
+
"present_current": _sram(69, 2, writable=False, sign_bit=15),
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
#: Register value 0-7 written to ``baud_rate`` and the bus speed it selects.
|
|
@@ -161,6 +163,26 @@ OPERATING_MODES = {
|
|
|
161
163
|
3: "step",
|
|
162
164
|
}
|
|
163
165
|
|
|
166
|
+
#: ``(milliamps per count, what is measured)`` of ``present_current``, by
|
|
167
|
+
#: ``model_number``. Only models whose scale is backed by a document are
|
|
168
|
+
#: listed; for any other model the count is shown without a conversion rather
|
|
169
|
+
#: than with a guessed one.
|
|
170
|
+
#:
|
|
171
|
+
#: - 777 (STS3215): 6.5 mA,
|
|
172
|
+
#: https://www.mantech.co.za/datasheets/products/sts3215-200620a-1.pdf
|
|
173
|
+
#: - 4618 (HLS series, seen on an HLS3960M): 6.5 mA of motor *phase* current,
|
|
174
|
+
#: not supply current. At a low PWM duty the phase current is far larger
|
|
175
|
+
#: than what the power supply delivers. Source is a third party transcript
|
|
176
|
+
#: of the HLS memory table, not a FEETECH document:
|
|
177
|
+
#: https://wiki.aifitlab.com/feetech-servo-motor-docs/feetech-hls-servo-memory-table-analysis
|
|
178
|
+
CURRENT_MA_PER_COUNT = {
|
|
179
|
+
777: (6.5, "supply"),
|
|
180
|
+
4618: (6.5, "phase"),
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
#: ``present_load`` count that means 100 % PWM duty (the unit is 0.1 %).
|
|
184
|
+
LOAD_FULL_SCALE = 1000
|
|
185
|
+
|
|
164
186
|
#: Encoder counts per full turn for the STS/SMS series.
|
|
165
187
|
RESOLUTION = 4096
|
|
166
188
|
|
|
@@ -265,3 +287,41 @@ def format_baud(baud):
|
|
|
265
287
|
if baud >= 1000:
|
|
266
288
|
return f"{baud / 1000:.0f}k"
|
|
267
289
|
return str(baud)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def format_current(current, model, load=None):
|
|
293
|
+
"""Render a ``present_current`` reading, in mA when the scale is known.
|
|
294
|
+
|
|
295
|
+
For a model that reports motor phase current, the current drawn from the
|
|
296
|
+
power supply is also estimated as ``|phase current| * |PWM duty|``, the
|
|
297
|
+
duty being ``present_load`` in 0.1 % steps. The estimate ignores driver
|
|
298
|
+
losses and the servo's own idle draw, so it is a rough figure only.
|
|
299
|
+
|
|
300
|
+
Parameters
|
|
301
|
+
----------
|
|
302
|
+
current : int
|
|
303
|
+
Signed ``present_current`` count.
|
|
304
|
+
model : int or None
|
|
305
|
+
``model_number`` of the servo, ``None`` if it could not be read.
|
|
306
|
+
load : int or None, optional
|
|
307
|
+
Signed ``present_load`` count, needed for the supply estimate.
|
|
308
|
+
|
|
309
|
+
Returns
|
|
310
|
+
-------
|
|
311
|
+
str
|
|
312
|
+
For example ``"-147 (-956 mA)"``, ``"-506 (-3289 mA phase, ~987 mA
|
|
313
|
+
supply est.)"`` for a model that reports motor phase current, or
|
|
314
|
+
``"-147 (mA scale unknown for model 1234)"`` when
|
|
315
|
+
:data:`CURRENT_MA_PER_COUNT` has no entry.
|
|
316
|
+
"""
|
|
317
|
+
entry = CURRENT_MA_PER_COUNT.get(model)
|
|
318
|
+
if entry is None:
|
|
319
|
+
return f"{current} (mA scale unknown for model {model})"
|
|
320
|
+
scale, kind = entry
|
|
321
|
+
milliamps = current * scale
|
|
322
|
+
if kind != "phase":
|
|
323
|
+
return f"{current} ({milliamps:.0f} mA)"
|
|
324
|
+
if load is None:
|
|
325
|
+
return f"{current} ({milliamps:.0f} mA phase)"
|
|
326
|
+
supply = abs(milliamps) * abs(load) / LOAD_FULL_SCALE
|
|
327
|
+
return f"{current} ({milliamps:.0f} mA phase, ~{supply:.0f} mA supply est.)"
|
|
@@ -16,6 +16,7 @@ from feetech_cli.registers import CENTER_POSITION
|
|
|
16
16
|
from feetech_cli.registers import OPERATING_MODES
|
|
17
17
|
from feetech_cli.registers import RESOLUTION
|
|
18
18
|
from feetech_cli.registers import format_baud
|
|
19
|
+
from feetech_cli.registers import format_current
|
|
19
20
|
from feetech_cli.registers import position_to_degree
|
|
20
21
|
|
|
21
22
|
#: Encoder counts moved per arrow key press, about five degrees.
|
|
@@ -69,6 +70,8 @@ STATUS_REGISTERS = [
|
|
|
69
70
|
"present_voltage",
|
|
70
71
|
"present_temperature",
|
|
71
72
|
"present_current",
|
|
73
|
+
# Only needed to pick the mA scale for present_current.
|
|
74
|
+
"model_number",
|
|
72
75
|
"torque_enable",
|
|
73
76
|
"operating_mode",
|
|
74
77
|
"moving",
|
|
@@ -603,7 +606,9 @@ class ServoTui:
|
|
|
603
606
|
current = values.get("present_current")
|
|
604
607
|
if current is None:
|
|
605
608
|
return "no data"
|
|
606
|
-
return
|
|
609
|
+
return format_current(
|
|
610
|
+
current, values.get("model_number"), values.get("present_load")
|
|
611
|
+
)
|
|
607
612
|
if row == "Torque":
|
|
608
613
|
torque = values.get("torque_enable")
|
|
609
614
|
if torque is None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: feetech-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Command line tool to inspect and configure FEETECH STS/SMS/SCS serial bus servos
|
|
5
5
|
Author-email: Iori Yanokura <ab.ioryz@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -31,14 +31,17 @@ or watch a live status screen.
|
|
|
31
31
|
|
|
32
32
|
## Install
|
|
33
33
|
|
|
34
|
+
With [uv](https://docs.astral.sh/uv/), which installs the `feetech` command in its
|
|
35
|
+
own environment and on your path:
|
|
36
|
+
|
|
34
37
|
```console
|
|
35
|
-
$
|
|
38
|
+
$ uv tool install feetech-cli
|
|
36
39
|
```
|
|
37
40
|
|
|
38
|
-
or
|
|
41
|
+
or with pip:
|
|
39
42
|
|
|
40
43
|
```console
|
|
41
|
-
$
|
|
44
|
+
$ pip install feetech-cli
|
|
42
45
|
```
|
|
43
46
|
|
|
44
47
|
The package is also a Python library: `from feetech_cli import FeetechServoController`.
|
|
@@ -71,13 +74,64 @@ is running, so a simulation is never mistaken for a real bus.
|
|
|
71
74
|
|
|
72
75
|
## Changing a servo id
|
|
73
76
|
|
|
74
|
-
|
|
77
|
+
New STS3215 servos all leave the factory as **id 1 at 1 Mbps**. Every servo on
|
|
78
|
+
a bus needs its own id, so a robot with several servos starts with giving each
|
|
79
|
+
one a different id — **one servo at a time**.
|
|
75
80
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
+
### Walkthrough: numbering a batch of new servos
|
|
82
|
+
|
|
83
|
+
1. **Connect exactly one servo** to the USB adapter, and power the adapter
|
|
84
|
+
from its own supply. USB alone does not power a servo.
|
|
85
|
+
|
|
86
|
+
2. Check that it answers, and at which id:
|
|
87
|
+
|
|
88
|
+
```console
|
|
89
|
+
$ feetech scan
|
|
90
|
+
Found 1 servo(s) on /dev/ttyUSB0 at 1.00Mbps:
|
|
91
|
+
id 1 model 777 position 2048 (+0.0 deg) 12.1V 35C
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
3. Give it its new id. Numbering three servos 1, 2 and 3, the first one can
|
|
95
|
+
keep id 1; the second becomes 2:
|
|
96
|
+
|
|
97
|
+
```console
|
|
98
|
+
$ feetech set-id 1 2
|
|
99
|
+
Change servo 1 to id 2? This writes the servo EEPROM. [y/N] y
|
|
100
|
+
Servo 1 is now id 2.
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The id is stored in EEPROM, so it survives a power cycle.
|
|
104
|
+
|
|
105
|
+
4. **Label the servo** with its new id (a piece of masking tape is enough).
|
|
106
|
+
Once they are mixed up, the only way to tell them apart again is to connect
|
|
107
|
+
them one by one.
|
|
108
|
+
|
|
109
|
+
5. Unplug it, connect the next new servo, and repeat from step 2 with the
|
|
110
|
+
next id (`feetech set-id 1 3` for the third).
|
|
111
|
+
|
|
112
|
+
6. With every servo numbered, daisy-chain them all and check the whole set
|
|
113
|
+
answers:
|
|
114
|
+
|
|
115
|
+
```console
|
|
116
|
+
$ feetech scan
|
|
117
|
+
Found 3 servo(s) on /dev/ttyUSB0 at 1.00Mbps:
|
|
118
|
+
id 1 model 777 position 2048 (+0.0 deg) 12.1V 35C
|
|
119
|
+
id 2 model 777 position 2048 (+0.0 deg) 12.1V 35C
|
|
120
|
+
id 3 model 777 position 2048 (+0.0 deg) 12.1V 35C
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Why one at a time.** Two new servos on the same bus both answer to id 1.
|
|
124
|
+
Their replies collide, and a write addressed to id 1 reaches both of them:
|
|
125
|
+
`set-id 1 2` would renumber both servos to 2, and the check that follows
|
|
126
|
+
would find "a servo at id 2" and report success. `set-id` cannot tell one
|
|
127
|
+
servo at an id from two, so keeping a single unnumbered servo on the bus is
|
|
128
|
+
up to you.
|
|
129
|
+
|
|
130
|
+
If `scan` finds nothing, the servo may have been set to another bus speed
|
|
131
|
+
before. `feetech scan --all-baudrates` tries every speed. Servos with an id
|
|
132
|
+
above 16 need `--max-id 253`.
|
|
133
|
+
|
|
134
|
+
### What set-id does
|
|
81
135
|
|
|
82
136
|
`set-id` refuses an id that another servo on the bus already answers to,
|
|
83
137
|
switches torque off first, unlocks the EEPROM, writes, locks it again, and
|
|
@@ -404,6 +458,8 @@ Sources:
|
|
|
404
458
|
- AkariGroup feetech_setup (baud table): https://github.com/AkariGroup/feetech_setup/blob/main/set_baudrate.py
|
|
405
459
|
- LeRobot control table and sign-magnitude encodings: https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/feetech/tables.py
|
|
406
460
|
- FEETECH start tutorial: https://www.feetechrc.com/Data/feetechrc/upload/file/20201127/start%20%20tutorial201015.pdf
|
|
461
|
+
- Factory default id 1: https://www.waveshare.com/wiki/ST3215_Servo ; id 1 and 1 Mbps: the STS3215 register reference above
|
|
462
|
+
- LeRobot SO-101 assembly, numbering the motors one at a time: https://huggingface.co/docs/lerobot/main/en/assemble_so101
|
|
407
463
|
|
|
408
464
|
[lerobot]: https://github.com/huggingface/lerobot/blob/main/src/lerobot/motors/feetech/tables.py
|
|
409
465
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Tests for register decoding and rendering."""
|
|
2
|
+
|
|
3
|
+
from feetech_cli.protocol import from_sign_magnitude
|
|
4
|
+
from feetech_cli.registers import CONTROL_TABLE
|
|
5
|
+
from feetech_cli.registers import format_current
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_present_current_decodes_bit_15_as_sign():
|
|
9
|
+
# Raw 0x8164 read from a holding servo: magnitude 356, negative.
|
|
10
|
+
sign_bit = CONTROL_TABLE["present_current"].sign_bit
|
|
11
|
+
assert from_sign_magnitude(0x8164, sign_bit) == -356
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_format_current_converts_known_model():
|
|
15
|
+
assert format_current(-147, 777) == "-147 (-956 mA)"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_format_current_labels_phase_current():
|
|
19
|
+
assert format_current(-147, 4618) == "-147 (-956 mA phase)"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_format_current_estimates_supply_from_pwm_duty():
|
|
23
|
+
# 3289 mA phase at 30 % duty draws roughly 987 mA from the supply.
|
|
24
|
+
expected = "-506 (-3289 mA phase, ~987 mA supply est.)"
|
|
25
|
+
assert format_current(-506, 4618, load=-300) == expected
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_format_current_leaves_unknown_model_unconverted():
|
|
29
|
+
assert format_current(-147, 1234) == "-147 (mA scale unknown for model 1234)"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|