# SPDX-FileCopyrightText: 2026 Holden Oullette # # SPDX-License-Identifier: Apache-2.0 defmodule BB.Ufactory do @moduledoc """ Beam Bots integration for UFactory xArm robotic arms. `BB.Ufactory` provides controller, actuator, and sensor modules that plug into the [BB robotics framework](https://hex.pm/packages/bb) for UFactory xArm arms: **xArm5, xArm6, xArm7, Lite6, and xArm850**. ## Architecture The arm communicates over two independent TCP connections: | Socket | Port | Direction | Purpose | |--------|------|-----------|---------| | Command | 502 | Client → Arm | Send register commands; receive responses | | Report | 30003 | Arm → Client | Arm pushes joint state at ~100 Hz | `BB.Ufactory.Controller` owns both connections. An ETS table acts as a shared blackboard — joint actuators write target positions into it, and the controller's 100 Hz loop batches all pending positions into a single `MOVE_JOINT` frame each tick. Cartesian and accessory commands (gripper, linear track) bypass the ETS loop and are sent immediately via `BB.Process.call/3`. ``` Actuator.Joint ──writes set_position──▶ ETS table │ Controller loop (100 Hz) ──reads ETS──▶ cmd_move_joints frame ──▶ arm:502 arm:30003 ──pushes report──▶ Controller ──updates ETS current_position ──publishes JointState, CartesianPose ``` ## Quick Start The fastest path to a working xArm6 is to `use BB.Ufactory.Robots.XArm6`, which provides the complete six-joint topology and only requires a host address: defmodule MyRobot do use BB.Ufactory.Robots.XArm6 controllers do controller :xarm, {BB.Ufactory.Controller, host: "192.168.1.111", model: :xarm6, loop_hz: 100 } end end ## Full Robot Definition Example For custom topologies or to add accessories, define the robot with `use BB` directly. The example below combines all available components: defmodule MyRobot do use BB import BB.Unit controller :xarm, {BB.Ufactory.Controller, host: "192.168.1.111", model: :xarm6, loop_hz: 100 } topology do link :base do joint :j1 do type :revolute limit do lower ~u(-360 degree) upper ~u(360 degree) effort ~u(50 newton_meter) velocity ~u(180 degree_per_second) end actuator :j1_motor, {BB.Ufactory.Actuator.Joint, joint: 1, controller: :xarm} link :link1 do joint :j2 do type :revolute limit do lower ~u(-118 degree) upper ~u(120 degree) effort ~u(50 newton_meter) velocity ~u(180 degree_per_second) end actuator :j2_motor, {BB.Ufactory.Actuator.Joint, joint: 2, controller: :xarm} link :link2 do joint :j3 do type :revolute limit do lower ~u(-225 degree) upper ~u(11 degree) effort ~u(32 newton_meter) velocity ~u(180 degree_per_second) end actuator :j3_motor, {BB.Ufactory.Actuator.Joint, joint: 3, controller: :xarm} link :link3 do joint :j4 do type :revolute limit do lower ~u(-360 degree) upper ~u(360 degree) effort ~u(32 newton_meter) velocity ~u(180 degree_per_second) end actuator :j4_motor, {BB.Ufactory.Actuator.Joint, joint: 4, controller: :xarm} link :link4 do joint :j5 do type :revolute limit do lower ~u(-97 degree) upper ~u(180 degree) effort ~u(32 newton_meter) velocity ~u(180 degree_per_second) end actuator :j5_motor, {BB.Ufactory.Actuator.Joint, joint: 5, controller: :xarm} link :link5 do joint :j6 do type :revolute limit do lower ~u(-360 degree) upper ~u(360 degree) effort ~u(20 newton_meter) velocity ~u(180 degree_per_second) end actuator :j6_motor, {BB.Ufactory.Actuator.Joint, joint: 6, controller: :xarm} link :link6 do end end end end end end end end end end end end # Optional: Cartesian actuator — commands end-effector pose directly actuator :cartesian, {BB.Ufactory.Actuator.Cartesian, controller: :xarm, speed: 100.0, acceleration: 2000.0 } # Optional: Gripper G2 — position in pulse units (0–840) actuator :gripper, {BB.Ufactory.Actuator.Gripper, controller: :xarm, speed: 1500 } # Optional: Linear track — position in millimetres actuator :track, {BB.Ufactory.Actuator.LinearTrack, controller: :xarm, speed: 200 } end end sensors do # Optional: UFactory Force/Torque sensor — publishes BB.Ufactory.Message.Wrench sensor :wrench, {BB.Ufactory.Sensor.ForceTorque, controller: :xarm, poll_interval_ms: 20 } end end ## Module Inventory | Module | Role | |--------|------| | `BB.Ufactory.Controller` | `BB.Controller` — TCP sockets, ETS table, 100 Hz loop, heartbeat, pubsub | | `BB.Ufactory.Protocol` | Frame builder and parser for port 502 commands | | `BB.Ufactory.Report` | Report frame parser for port 30003 push data | | `BB.Ufactory.Registers` | Module-attribute constants for all register addresses | | `BB.Ufactory.Model` | Per-model joint counts and limits (xArm5/6/7, Lite6, xArm850) | | `BB.Ufactory.Actuator.Joint` | `BB.Actuator` — joint-space position via ETS + 100 Hz loop | | `BB.Ufactory.Actuator.Cartesian` | `BB.Actuator` — Cartesian end-effector pose via `MOVE_LINE` | | `BB.Ufactory.Actuator.Gripper` | `BB.Actuator` — Gripper G2 position (pulse units 0–840) | | `BB.Ufactory.Actuator.LinearTrack` | `BB.Actuator` — linear track position in mm (RS485 proxy) | | `BB.Ufactory.Sensor.ForceTorque` | `BB.Sensor` — polls register 0xC8, publishes `Wrench` | | `BB.Ufactory.Message.ArmStatus` | State, mode, error/warning codes from the report socket | | `BB.Ufactory.Message.CartesianPose` | TCP pose (x/y/z/roll/pitch/yaw) from report socket | | `BB.Ufactory.Message.Wrench` | Fx/Fy/Fz/Tx/Ty/Tz from the F/T sensor | | `BB.Ufactory.Robots.XArm6` | Pre-built `BB` robot definition for the xArm6 | | `BB.Error.Protocol.Ufactory.HardwareFault` | Structured error for xArm hardware fault codes | | `BB.Error.Protocol.Ufactory.ConnectionError` | TCP connection failure | | `BB.Error.Protocol.Ufactory.CommandRejected` | Non-zero status byte in a command response | ## Protocol Notes - **Angles on the wire are always radians.** BB also uses radians, so no conversion is needed. - **Mixed endianness:** u16 header fields are big-endian; fp32 payload fields are little-endian. The only exception is the linear track position, which is int32 big-endian. - **Heartbeat:** The controller sends `<<0, 0, 0, 1, 0, 2, 0, 0>>` every second on the command socket to keep the connection alive. - **Modbus-TCP variant:** The protocol identifier in the header is `0x0002` (not the standard Modbus `0x0000`). The developer manual is the authoritative spec. """ end