Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
105 commits
Select commit Hold shift + click to select a range
a34e085
kx2
rickwierenga Feb 7, 2026
18272cc
tutorial update, connection update
BioCam Feb 8, 2026
7cf61d3
Merge branch 'main' into kx2-backend
BioCam Feb 8, 2026
f3fadad
change self.calculate_move_abs_all_axes
BioCam Feb 8, 2026
32d0390
try process message; check if future already done
rickwierenga Feb 8, 2026
290ad1e
remove scila.ipynb accidentally modified in kx2 PR
rickwierenga Apr 14, 2026
74721cb
Merge remote-tracking branch 'origin/v1b1' into kx2-backend
rickwierenga Apr 14, 2026
046af46
port KX2 to new arm architecture under paa/
rickwierenga Apr 14, 2026
788d4c8
replace prints with module logger in KX2
rickwierenga Apr 14, 2026
3029e9f
scaffold KX2CanopenDriver (parallel class, not yet wired up)
rickwierenga Apr 14, 2026
f93dc6c
implement SDO on KX2CanopenDriver
rickwierenga Apr 14, 2026
95fb03e
implement binary_interpreter on KX2CanopenDriver
rickwierenga Apr 14, 2026
9344217
implement motor primitives, DS402 controlword, PVT mode on KX2Canopen…
rickwierenga Apr 14, 2026
0845a02
implement connect_part_two + PDO mapping on KX2CanopenDriver
rickwierenga Apr 14, 2026
6095dec
implement os_interpreter + homing on KX2CanopenDriver
rickwierenga Apr 14, 2026
2e1084a
expose KX2Canopen device alongside legacy KX2
rickwierenga Apr 14, 2026
ec44596
raise canopen SDO timeout to 1s
rickwierenga Apr 14, 2026
c6da02b
flip hello-world to KX2Canopen for hardware testing
rickwierenga Apr 14, 2026
1a0c2ef
stop treating OS command status=1 as an error in os_interpreter
rickwierenga Apr 14, 2026
200065b
switch KX2 over to canopen driver, delete legacy transport
rickwierenga Apr 14, 2026
6290d1e
fix freedrive re-enable + notebook blockers
rickwierenga Apr 14, 2026
c67bf61
privatize driver methods that aren't part of the user API
rickwierenga Apr 14, 2026
18dd605
privatize interpreters + I/O on driver
rickwierenga Apr 14, 2026
8ee428c
move motor_send_command + get_estop_state to KX2Driver
rickwierenga Apr 14, 2026
62f4f5f
un-privatize driver methods consumed by KX2ArmBackend
rickwierenga Apr 14, 2026
08e5e7b
add KX2BarcodeReader for the onboard Microscan-style serial reader
rickwierenga Apr 15, 2026
73bd30b
send CR+LF (not just CR) to KX2 barcode reader + expand notebook section
rickwierenga Apr 21, 2026
38718a3
harden KX2 motor_enable + fix axis-identify comprehension
rickwierenga Apr 23, 2026
b7a5688
poll MS for move completion instead of TPDO3 MotionComplete
rickwierenga Apr 23, 2026
81cff3d
split KX2 layers: driver is pure CAN transport, backend owns robot to…
rickwierenga Apr 24, 2026
52820c3
fold gripper geometry into KX2 IK/FK
rickwierenga Apr 24, 2026
7fa2d2d
drop leading underscore on KX2Driver methods used by backend
rickwierenga Apr 24, 2026
663ce42
fix KX2 dead-error paths and untruncate grip force
rickwierenga Apr 24, 2026
3a2e586
native int/float through KX2 driver — no more stringified numbers
rickwierenga Apr 24, 2026
73e262f
revert KX2 user_program_run to return 0 on success
rickwierenga Apr 24, 2026
edefa19
restructure KX2 backend: split kinematics + config; native types end-…
rickwierenga Apr 25, 2026
2393657
KX2 barcode reader: rename + Denso MDI-4050 docs + dump_config + macO…
rickwierenga Apr 25, 2026
ea04caf
KX2 proximity sensor: read + drive-side auto-halt Z search
rickwierenga Apr 26, 2026
75fa5f9
KX2 proximity search: port C# MotorStop, fix post-halt drive recovery
rickwierenga Apr 26, 2026
1fe13da
KX2 setup: split drive_get_parameters; raise on motor_enable failure
rickwierenga Apr 26, 2026
528ecdc
KX2 motion API: physical units (mm/s, deg/s) and motion_limits()
rickwierenga Apr 27, 2026
3101f1a
productive work
rickwierenga Apr 27, 2026
7d30a73
KX2: clear CI gates + review polish
rickwierenga Apr 28, 2026
3968dc7
KX2: expose has_rail/has_servo_gripper kwargs; async-safe yeet prompt
rickwierenga Apr 28, 2026
9e3b5eb
KX2 + CanFreedrive: review polish round 3
rickwierenga Apr 28, 2026
f16707c
KX2 Axis: add is_motion / is_linear properties; drop _LINEAR_AXES
rickwierenga Apr 28, 2026
89bb466
KX2: collapse motors_move_joint kwargs into a single JointMoveParams
rickwierenga Apr 28, 2026
6a133cd
KX2: drop duplicate trajectory math (_yeet_profile_time, _yeet_wrap_t…
rickwierenga Apr 28, 2026
d038c47
KX2: split GripperConfig out of KX2Config; kinematics takes (c, t)
rickwierenga Apr 28, 2026
45968db
KX2Config.axes: Dict[Axis, AxisConfig]; drop int<->Axis round-trips
rickwierenga Apr 28, 2026
bf6be16
KX2: hide internal driver types; loop property; topology guard in driver
rickwierenga Apr 28, 2026
34e96e4
KX2 motion API: revert physical units back to vel_pct/accel_pct
rickwierenga Apr 28, 2026
6d93d02
KX2: cap gripper Cartesian speed via FD-based generic helper
rickwierenga Apr 29, 2026
e6bdadd
KX2: poll setpoint_ack in motors_move_start to fix dropped moves
rickwierenga Apr 29, 2026
d053f29
KX2: motion lock, int(axis) at driver boundary, fix params silent drop
rickwierenga Apr 30, 2026
ac0d6d1
KX2: fix public surface — rename leaked types, fix __init__ exports
rickwierenga Apr 30, 2026
2009c6a
KX2: handle CANopen EMCY frames for immediate fault surfacing
rickwierenga Apr 30, 2026
6a9cd20
KX2: surface EMCY in poll loop, auto-recover disabled drives before m…
rickwierenga Apr 30, 2026
7f2861f
KX2: collapse move_to_gripper_location into move_to_location, co-loca…
rickwierenga Apr 30, 2026
f792fb3
KX2: gripping force per-close, privatize the setter and reader
rickwierenga Apr 30, 2026
37a168f
KX2: drop wrist-sign API — wrist drive wraps freely
rickwierenga Apr 30, 2026
de8773e
KX2: motor_enable retry covers both directions
rickwierenga May 1, 2026
5bf36e6
KX2: move trajectory planner from arm_backend.py into kinematics.py
rickwierenga May 1, 2026
f9ca363
KX2: rename GripperConfig -> GripperParams to disambiguate from Servo…
rickwierenga May 1, 2026
83f129a
KX2: small polish — gather joint reads, drop int(Axis) casts, op-mode…
rickwierenga May 1, 2026
e5ab70a
KX2: planner unit tests — 38 passing + 2 xfail capturing latent bugs
rickwierenga May 1, 2026
c7b1488
KX2: fix mypy narrowing in tests
rickwierenga May 1, 2026
5640cc4
KX2: TPDO3-push setpoint-ack, SDO 16-bit index, pvt re-arm, planner c…
rickwierenga May 1, 2026
c5b2882
KX2: small polish — _send_bi race fix, asin clamp, skip-axis vel, _EP…
rickwierenga May 1, 2026
b6ca5d4
KX2: tag MF-polling and PPM-setpoint errors with Axis {nid}
rickwierenga May 1, 2026
d6dd639
KX2: pin exact sync-algorithm values in planner tests
rickwierenga May 1, 2026
f4427fb
KX2: collapse duplicate direction-aware delta + parameterize cap-help…
rickwierenga May 3, 2026
4c7f1ce
KX2: DS402 fault-reset edge-pulse via SW polling
rickwierenga May 3, 2026
5917e4f
KX2: fix elbow >90° round-trip — missing zero_offset in inverse refle…
rickwierenga May 3, 2026
c8a60b6
KX2: home routines are gripper-only — drop the axis arg, rename helpers
rickwierenga May 3, 2026
e854279
KX2: FK/IK anchor tests pinning real-value behavior
rickwierenga May 3, 2026
54c69e3
KX2: driver-side tests — _trigger_new_setpoint, _read_axis_config, fi…
rickwierenga May 3, 2026
ebd74ac
KX2: Cartesian-linear path via IPM/PVT streaming, opt-in path='linear'
rickwierenga May 3, 2026
85c7d59
KX2: wire barcode reader as an optional capability on KX2
rickwierenga May 3, 2026
bb65ad9
Merge v1b1: BarcodeScanner per-call read_time
rickwierenga May 4, 2026
8382742
KX2: drop read_time from BCR object, take it per scan() call
rickwierenga May 4, 2026
4d9f44b
Merge v1b1: BarcodeScanner returns Optional[Barcode]
rickwierenga May 4, 2026
9e60bbb
KX2 BCR: return None on read-window timeout instead of raising
rickwierenga May 4, 2026
3fef4b9
KX2: collapse EMCY state to one per-node struct; clear in find_z
rickwierenga May 4, 2026
42f6ce4
KX2: split proximity sensor docs into its own notebook
rickwierenga May 4, 2026
365ec70
KX2: find_z takes z_start/z_end instead of max_descent
rickwierenga May 4, 2026
acd491f
KX2: add find_with_proximity_sensor for generic Cartesian sweeps
rickwierenga May 4, 2026
7a495f7
KX2: split barcode reader docs into its own notebook
rickwierenga May 4, 2026
6088c5b
KX2: drop direction arg from find_with_proximity_sensor
rickwierenga May 4, 2026
bb626c4
Merge v1b1: PreciseFlex pose rename + Brooks I/O error codes
rickwierenga May 12, 2026
e0e5896
KX2: get_estop_state checks only SR bits 14/15, drop full-register match
rickwierenga May 12, 2026
f1a8093
KX2: rename GripperLocation → CartesianPose; sync docs and notebooks
rickwierenga May 12, 2026
5e2a114
KX2: fix FK/IK to model gripper hanging off wrist axis (location = gr…
rickwierenga May 13, 2026
f3bccd5
Arm: rename request_gripper_location → request_gripper_pose
rickwierenga May 13, 2026
c9ebba2
Merge v1b1: rename request_gripper_location → request_gripper_pose
rickwierenga May 13, 2026
bc71517
KX2: smooth PVT trajectories via move_parametric / move_through_waypo…
rickwierenga May 17, 2026
2719e0f
Arms: rename GripDirection→GripperDirection (string literal); rotate …
rickwierenga May 14, 2026
c1368ff
Unify gripper API: move_gripper(width, force_sensing) + class hierarc…
rickwierenga May 17, 2026
dd6434b
KX2: update park pose (SHOULDER=2, Z=750, ELBOW=1, WRIST=356)
rickwierenga May 17, 2026
d664084
refactor(kx2): collapse into a single KX2 class with an inlined API
rickwierenga Jul 15, 2026
e938725
scope PR to the KX2 arm: revert unrelated changes
rickwierenga Jul 15, 2026
b4db1ab
kx2: merge barcode reader into a single class
rickwierenga Jul 17, 2026
e79e775
docs(kx2): add a KX2 installation page
rickwierenga Jul 17, 2026
a8b1cb9
docs(kx2): add can0 bring-up commands to the installation page
rickwierenga Jul 17, 2026
08ae58e
kx2: default CAN interface to socketcan on Linux, pcan elsewhere
rickwierenga Jul 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/api/pylabrobot.paa.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
.. currentmodule:: pylabrobot.paa

pylabrobot.paa package
======================

KX2
---

.. currentmodule:: pylabrobot.paa.kx2

.. autosummary::
:toctree: _autosummary
:nosignatures:
:recursive:

KX2
KX2BarcodeReader
KX2Config
Axis
1 change: 1 addition & 0 deletions docs/api/pylabrobot.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Manufacturers
pylabrobot.mettler_toledo
pylabrobot.molecular_devices
pylabrobot.opentrons
pylabrobot.paa
pylabrobot.qinstruments
pylabrobot.tecan
pylabrobot.thermo_fisher
1 change: 1 addition & 0 deletions docs/user_guide/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ liconic/index
mettler_toledo/index
molecular_devices/index
opentrons/index
paa/index
qinstruments/index
thermo_fisher/index
ufactory/index
Expand Down
11 changes: 11 additions & 0 deletions docs/user_guide/paa/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# PAA

```{toctree}
:maxdepth: 1

kx2/installation
kx2/hello-world
kx2/continuous-motion
kx2/proximity-sensor
kx2/barcode-reader
```
181 changes: 181 additions & 0 deletions docs/user_guide/paa/kx2/barcode-reader.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "kx2-bcr-intro",
"metadata": {},
"source": [
"# KX2 Barcode Reader\n",
"\n",
"The KX2's onboard barcode reader is a serial 1D laser scanner wired to the controller PC. It's fully independent of the CAN motor stack, so it works even with the arm e-stopped — exposed as its own {class}`~pylabrobot.paa.kx2.KX2BarcodeReader` `Device`.\n",
"\n",
"The model that ships with KX2 systems we've inspected is a **Denso MDI-4050** (firmware reports as `BD01J09`). Factory defaults: **9600 8N1**, no flow control. Find your port with `python -m serial.tools.list_ports -v`.\n",
"\n",
"### macOS: USB-to-serial driver\n",
"\n",
"The reader cable is typically a Prolific PL2303. macOS bundles a driver for older variants but not the newer GC/HXN chip (USB ID `067b:23a3`). If the device doesn't show up at `/dev/tty.PL2303G-USBtoUART<n>` after plugging it in:\n",
"\n",
"```bash\n",
"brew install --cask prolific-pl2303\n",
"open -a PL2303Serial # registers the system extension\n",
"```\n",
"\n",
"Then **System Settings → Privacy & Security → Allow** on the \"System software from PL2303Serial was blocked\" prompt, restart if asked, and replug the cable. Verify with `systemextensionsctl list`: `com.prolific.cdc.PLCdcFSDriver` should show `[activated enabled]`.\n",
"\n",
"### Symbologies\n",
"\n",
"The MDI-4050 is a 1D laser, so **PDF417 / DataMatrix / QR Code aren't supported** — you'd need the 2D imager variants (e.g. MDI-4150) for those.\n",
"\n",
"Typical out-of-the-box 1D enables: UPC-A, UPC-E, EAN-13, EAN-8, Code 39, Tri-Optic, Codabar, Industrial 2/5, Interleaved 2/5, IATA, Code 128, Code 93, GS1 DataBar, GS1 DataBar Limited. Add-on (supplemental) codes, postal codes, MSI/Plessey, Telepen, UK/Plessey, and Code 11 are off by default.\n",
"\n",
"You can read your unit's full configuration over the wire — model, firmware, every per-symbology enable flag, prefixes/suffixes, and length limits — via `dump_config()` (which sends `Z4`):\n",
"\n",
"```python\n",
"text = await bcr.driver.dump_config()\n",
"print(text[:200]) # header has MODEL / ROM Ver. / I/F\n",
"```\n",
"\n",
"To **change** which symbologies are enabled, three options in order of practicality:\n",
"\n",
"1. **Denso's MDI Setup utility** (free Windows download from Denso ADC) — checkbox UI, talks to the reader over the same serial port, writes to NVRAM.\n",
"2. **Configuration barcodes** printed in the MDI-4050 setting manual — scan a Start → toggle → End sequence; no host required.\n",
"3. Direct register writes via the ESC protocol — possible but the per-symbology byte layout is documented only in Denso's protocol manual.\n",
"\n",
"{class}`~pylabrobot.resources.barcode.Barcode` returned from `scan()` has `symbology=\"ANY 1D\"` by default — the reader doesn't emit a symbology ID unless prefix/suffix bytes are configured to include one."
]
},
{
"cell_type": "markdown",
"id": "e7a9907a",
"source": "## Integrated with the arm\n\nWhen you construct the arm with a `barcode_port`, the `KX2` owns the reader and manages its lifecycle: `kx2.setup()` opens the port and runs the handshake, `kx2.scan_barcode(read_time=8)` returns a {class}`~pylabrobot.resources.barcode.Barcode` (or `None` on a no-read), and `kx2.stop()` tears it down.\n\n```python\nfrom pylabrobot.paa.kx2 import KX2\n\nkx2 = KX2(barcode_port=\"/dev/tty.PL2303G-USBtoUART11240\")\nawait kx2.setup()\nbarcode = await kx2.scan_barcode(read_time=8)\nawait kx2.stop()\n```\n\nThe standalone {class}`~pylabrobot.paa.kx2.KX2BarcodeReader` below drives the reader on its own — useful when the arm is e-stopped, or when there's no arm at all.",
"metadata": {}
},
{
"cell_type": "markdown",
"id": "kx2-bcr-setup-header",
"metadata": {},
"source": [
"## Setup\n",
"\n",
"Pass the serial port path. `setup()` opens the port, does the `Z1` software-version handshake, and puts the reader into single-trigger mode."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "kx2-bcr-setup-code",
"metadata": {},
"outputs": [],
"source": [
"from pylabrobot.paa.kx2 import KX2BarcodeReader\n",
"\n",
"bcr = KX2BarcodeReader(port=\"/dev/tty.PL2303G-USBtoUART11240\")\n",
"await bcr.setup()\n"
]
},
{
"cell_type": "markdown",
"id": "kx2-bcr-scan-md",
"metadata": {},
"source": [
"`scan(read_time=...)` fires the trigger and waits up to `read_time + 1s` for the reader to decode something. Omit `read_time` to use whatever read window is currently programmed on the reader. Returns a {class}`~pylabrobot.resources.barcode.Barcode`."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "kx2-bcr-scan-code",
"metadata": {},
"outputs": [],
"source": [
"barcode = await bcr.barcode_scanning.scan(read_time=8)\n",
"print(barcode.data)"
]
},
{
"cell_type": "markdown",
"id": "kx2-bcr-config-md",
"metadata": {},
"source": [
"Change the trigger mode at runtime:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "kx2-bcr-config-code",
"metadata": {},
"outputs": [],
"source": [
"# \"single\" (default), \"multiple\" (two reads per trigger), or \"continuous\".\n",
"# await bcr.set_read_mode(\"continuous\")"
]
},
{
"cell_type": "markdown",
"id": "kx2-bcr-driver-md",
"metadata": {},
"source": [
"For raw vendor commands or the auto-trigger mode (reader fires on its own), go through `bcr.driver`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "kx2-bcr-driver-code",
"metadata": {},
"outputs": [],
"source": [
"print(await bcr.driver.get_software_version())\n",
"\n",
"# Full NVRAM dump — model, firmware, all symbology enables, prefixes/suffixes, length limits.\n",
"config = await bcr.driver.dump_config()\n",
"print(config[:200])\n",
"\n",
"# await bcr.driver.set_auto_trigger(True) # reader scans on its own\n",
"# await bcr.driver.set_auto_trigger(False)"
]
},
{
"cell_type": "markdown",
"id": "kx2-bcr-teardown-header",
"metadata": {},
"source": [
"## Teardown\n",
"\n",
"`stop()` sends `Y` (trigger off) and `Y2` (reset to a 2s read window), then closes the serial port — leaving the reader in a known state for the next session."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "kx2-bcr-teardown-code",
"metadata": {},
"outputs": [],
"source": [
"await bcr.stop()\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "env",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.15"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading
Loading