# python-can-candle `python-can-candle` lets Python access the SANPO SPINE CAN and CAN FD passthrough interfaces directly on Windows or Linux. It exchanges standard CAN/CAN FD frames, without a SANPO header or management-port protocol. A complete board contains two MCUs. Each MCU provides two CAN/CAN FD channels, so a complete board normally exposes two USB devices and four channels. USB enumeration order and channel numbers cannot be directly mapped to the connector silkscreen; verify the physical mapping one channel at a time on first use. When Linux already provides SocketCAN, [SocketCAN](socketcan) remains the preferred interface. `python-can-candle` is mainly useful for Windows Python programs and for applications that need one cross-platform `python-can` API. ## Installation This document uses `python-can-candle 1.2.4`: ```bash python -m pip install "python-can-candle==1.2.4" ``` SANPO SPINE V81 and later firmware uses Windows WCID to bind the system WinUSB driver automatically. Zadig and a vendor-specific driver are not required. V81 firmware reports the USB product name `SANPO gs_usb CDC Composite fdV8`; V82 and later firmware reports the shorter name `SANPO SPINE`. The complete demo supplied with this documentation accepts both names and uses the product string enumerated from each device, so the same script works before and after a firmware upgrade. If an application explicitly passes `product=`, the value must match the installed firmware. The basic snippets below omit that filter so they remain compatible with both V81 and V82. Check the installed versions: ```bash python -m pip show python-can-candle candle-api python-can ``` ## Detect Channels ```python import can for config in can.detect_available_configs("candle"): print(config) ``` A complete board normally prints four entries with channel values similar to: ```text SERIAL_0:0 SERIAL_0:1 SERIAL_1:0 SERIAL_1:1 ``` `SERIAL_0` and `SERIAL_1` are the USB serial numbers of the two MCUs. A serial number can reliably distinguish the two MCUs, but cannot identify which pair of physical board connectors it controls. ## Classic CAN The following example uses channel 0 of the selected MCU at 1 Mbit/s, sends a standard frame, and waits for a reply: ```python import can serial = "replace with the actual serial number" with can.Bus( interface="candle", channel=f"{serial}:0", vid=0x1209, pid=0x2323, manufacture="SANPO", bitrate=1_000_000, sample_point=80.0, ignore_config=True, ) as bus: message = can.Message( arbitration_id=0x123, is_extended_id=False, data=[0x11, 0x22, 0x33, 0x44], ) bus.send(message, timeout=1.0) reply = bus.recv(timeout=1.0) print(reply) ``` The example ID and data have no device-specific meaning. Replace them with the protocol required by the connected device. ## CAN FD The following example configures a 1 Mbit/s arbitration phase and a 5 Mbit/s data phase, then sends a CAN FD frame with BRS enabled: ```python import can serial = "replace with the actual serial number" with can.Bus( interface="candle", channel=f"{serial}:0", vid=0x1209, pid=0x2323, manufacture="SANPO", fd=True, bitrate=1_000_000, sample_point=80.0, data_bitrate=5_000_000, data_sample_point=75.0, ignore_config=True, ) as bus: message = can.Message( arbitration_id=0x123, is_extended_id=False, is_fd=True, bitrate_switch=True, data=bytes(range(16)), ) bus.send(message, timeout=1.0) ``` SANPO SPINE CAN FD data phase rates are 2/3/4/5 Mbit/s. We recommend `data_sample_point=80.0` at 2/3/4 Mbit/s and `data_sample_point=75.0` at 5 Mbit/s. ## Two Channels on the Same MCU Open the two channels managed by the same MCU through one `CandleBus` instance: ```python import can with can.Bus( interface="candle", channel=[0, 1], serial_number="replace with the actual serial number", bitrate=1_000_000, sample_point=80.0, ignore_config=True, ) as bus: message = can.Message( arbitration_id=0x123, is_extended_id=False, channel=1, data=[0x01, 0x02], ) bus.send(message, timeout=1.0) ``` `Message.channel` selects the physical CAN channel on the host. It is not written into the CAN data and is not a SANPO custom channel field. To access all four CAN channels, create one `CandleBus` for each MCU. ## Xiaomi CyberGear Example Example program: [python-can-candle CyberGear example](https://gitcode.com/sanpo/robot/blob/main/products/spine/v8/demo/pythoncan/pythoncan_cybergear_demo_v8.py) First enumerate devices without transmitting CAN frames: ```bash python pythoncan_cybergear_demo_v8.py --list-devices ``` After confirming the two MCU serial numbers and physical channel mapping, run an unloaded initialization test. This command configures CAN and sends motor initialization, enable, and stop commands: ```bash python pythoncan_cybergear_demo_v8.py \ --enable-motion \ --init-only \ --device-serials "SERIAL_0,SERIAL_1" \ --motor-map "0/0:1,2,3;0/1:4,5,6;1/0:7,8,9;1/1:10,11,12" ``` `0/1` means channel 1 of the first MCU in `--device-serials`. The first run must be unloaded; confirm the wiring and emergency stop, then adjust the motor IDs and channel mapping for the actual installation. ## Limitations - A single gs_usb interface cannot be opened simultaneously by CANgaroo, `python-can-candle`, or another WinUSB program. Close CANgaroo's measurement connection before running the Python example. - Do not transmit to one physical CAN/CAN FD channel simultaneously through USB passthrough, management-port protocol mode, SPI, or an offline task. - `python-can-candle` reconfigures the bit rate when opening channels. These settings are not permanently saved as user configuration. - On Linux, when the interface has already been attached by the kernel `gs_usb` driver and appears as `canX`, use SocketCAN directly to avoid competing host drivers on the same USB interface. ## Troubleshooting | Symptom | Action | | --- | --- | | `Device not found` | Confirm both MCUs run V81 or later, unplug and reconnect USB, then enumerate again | | Only two channels appear on a complete board | Usually only one MCU is recognized; check the other MCU's firmware, power, and USB enumeration | | `Access denied` or device busy | Close CANgaroo, other Python processes, and software using the WinUSB interface | | Frames transmit but the device does not reply | Check the channel-to-connector mapping, bit rate, termination, device ID, and power | | CAN FD communication errors | Confirm arbitration/data rates and sample points match every device on the bus |