sdwire

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 2, 2026 License: MIT Imports: 13 Imported by: 0

README

sdwire

Go Reference

A Go SDK, and a CLI built on it, for controlling SDWireC and SDWire3 devices — USB-controlled SD card multiplexers that switch a single SD card between a host computer (for flashing images) and a target device under test (for booting them), without physically re-seating the card.

The CLI is a drop-in replacement for the official python package, but includes:

  • hub-power fallback for SDWire3 (which otherwise can cause failures)
  • disk unmounting from the host before switching to target mode
  • support for power managing the target (currently using Meross WiFi power plugs)
  • one-shot command for updaing the target SD card image; the flow:
    1. switch off target
    2. flash SD image
    3. re-mount to target
    4. power on target

This was originally a fork of github.com/fcjr/sdwire.

Install

CLI
go install github.com/jphastings/sdwire/cmd/sdwire@latest

Or download a prebuilt binary from the releases page (built via GoReleaser/CI for darwin, linux, and windows, amd64 and arm64).

SDK
go get github.com/jphastings/sdwire

CLI usage

sdwire is a drop-in replacement for the list, state, and switch commands of the Python sdwire-cli (see Migrating from the Python CLI below), plus new commands for flashing, power control, and scripting.

Every command accepts -s/--serial to select which attached device to operate on: a plain USB serial, the port-suffixed identity form (20120501030900000.1.1.3), a USB location (1-1.1.3), or a device name from your config file. With no -s and no default_device configured, commands that need a single device use the sole attached device, or error out listing every attached device's identity if more than one is found.

sdwire list

List every SDWire this computer knows about: its identity, USB product info, the reader's resolved block device path (or None if it can't currently be found), and which side its card is switched to.

$ sdwire list
Serial                        Product Info		Block Dev		State
20120501030900000.1.1.3       [0bda::0316]		/dev/disk4		Host

Devices remembered in the hub-port cache are listed even when they aren't on the USB bus, since an SDWire3 in target mode is powered off and cannot be enumerated at all. For those, State is read from the hub port without powering anything on:

State Meaning
Host The card is connected to this computer.
Target The port is unpowered: the card is with the target board.
Unknown The port is powered but nothing is enumerated on it — an empty socket, or a reader that has crashed and been dropped from the bus. Recover the latter with sdwire revive.

The first three columns keep the Python CLI's exact widths and separators, so anything parsing that output by position still works; --json carries the same information plus an attached flag.

A remembered port whose status can't be read — the dock it belongs to has moved to a different USB port, say, which changes its bus number — is skipped, and list warns how many it skipped and which file to delete to forget them.

sdwire state

Print which side the selected device's SD card is currently connected to: Host, Target, or Unknown. When the device is attached and powered on, this is an honest live readback via the device — see SDWire3 state semantics below for how a powered-off (target mode) SDWire3 is handled instead.

$ sdwire state
Serial                        	State
20120501030900000.1.1.3       	Host
sdwire switch {dut|target|host|ts|off}

Switch the selected device's SD card. dut/target connect it to the target board; host/ts connect it to this computer. off is rejected with an explanation: SDWireC has no third state, and for SDWire3 "powering the port off" is literally how target mode is implemented — it does not mean disconnected from both sides. Prints nothing and exits 0 on success.

sdwire switch host              # connect the card to this computer
sdwire switch target -s bench   # connect the card to the "bench" device's target
sdwire revive

Power-cycle the selected device's upstream hub port and wait for it to come back: the software equivalent of unplugging the device and plugging it back in. Any volumes mounted from its reader are unmounted first.

sdwire revive                 # the default (or -s named) device
sdwire revive -s 1-1.1.3      # by location, straight from live USB topology

Use it when a reader has stopped answering and been dropped from the bus — sdwire list shows Unknown, or nothing at all. A USB port reset does not clear that state; only removing power does, and the port is held dark long enough for the reader's own supply to drain rather than merely reset.

Because such a device isn't enumerated, it can't be selected by serial alone. -s/--serial accepts a location (1-1.1.3, as shown by sdwire list), which is resolved from the live USB topology — the hub is still there even when the device on it isn't — so a revive works even when the hub-port cache is empty, stale, or names one serial at several ports.

sdwire flash <image>

Write an image to the selected device's SD card and boot the target from it: powers the target off (if a power plugin is configured for the device), switches to host mode, raw-writes the image with progress on stderr, switches back to target mode, then powers the target back on. Raw disk writes need elevated privileges — run with sudo on macOS/Linux, or as Administrator on Windows.

sudo sdwire flash ./ubuntu-24.04-preinstalled.img.xz -s bench
flashed 1234 / 3800 MiB (32%)

If the reader stops responding part-way through the write — see the reader-wedge entry below — the flash does not fail. Its hub port is power-cycled, the reader is waited for at the same USB location, and the write resumes from the last completed chunk, up to three times:

flashed 96 / 272 MiB (35%)
warning: the reader stopped responding 96 MiB into the write; power-cycling its hub port and resuming (attempt 1 of 3)
flashed 272 / 272 MiB (100%)

The warning is deliberately loud: a flash that needed several power cycles should never look like a clean one. Errors that a power cycle cannot fix — a full card, an unreadable image — still fail immediately rather than retrying. WithWriteRetries in the SDK tunes or disables the recovery.

Without a power plugin configured for the device the flash still happens, but nothing power-cycles the target afterwards: it carries on running whatever it was before, which is indistinguishable from a board that won't boot the image you just wrote. flash says so on stderr when it finishes, and prints the config snippet that would fix it. --require-power turns that into an error before any writing starts — worth it in scripts, where a warning nobody reads is no use.

sdwire power {on|off|cycle}

Drive the power plugin configured for the selected device. on/off set target power directly; cycle powers off, waits at least min_off_seconds, then powers back on. This never touches the SDWire's USB connection — it's the normal way to boot a target whose SD card is already switched to it. If the device has no power plugin configured, this prints a ready-to-copy YAML snippet for your config file and exits 1.

sdwire disk

Print just the selected device's resolved block device path — nothing else — for use in scripts:

sudo dd if=image.img of=$(sdwire disk -s bench) bs=4M status=progress

Exits non-zero if the block device can't currently be found.

--json

list, state, and disk accept --json for machine-readable output:

$ sdwire list --json
[
  {
    "serial": "20120501030900000",
    "identity": "20120501030900000.1.1.3",
    "location": "1-1.1.3",
    "product": "USB3.0-CRW",
    "generation": "SDWire3",
    "state": "Host",
    "attached": true,
    "block_dev": "/dev/disk4"
  }
]

$ sdwire state --json
{ "identity": "20120501030900000.1.1.3", "state": "Host" }

$ sdwire disk --json
{ "block_dev": "/dev/disk4" }
--debug and warnings

SDK warnings (for example, an SDWire3 sitting behind a hub that only switches port power in a ganged fashion, affecting sibling ports) always print to stderr, whether or not --debug is set. --debug adds further diagnostics — the resolved config path, which device a selector matched, and so on.

During a flash, --debug also prints how long each chunk's write took and its throughput:

debug: wrote 4096 KiB at offset 84 MiB in 121ms (33.1 MiB/s)

That is the number to watch when a reader is misbehaving: a healthy one holds a steady rate, while one whose internal write buffer is backing up slows chunk by chunk before it stalls outright. WithWriteTiming exposes the same data to SDK callers.

Shell completion
sdwire completion bash|zsh|fish|powershell

See sdwire completion --help (and each shell's subcommand --help) for how to load the generated script.

Exit codes

0 on success, 1 for an operational failure (device not found, config error, flash failure, ...), 2 for a CLI usage error (bad flags, unknown subcommand or argument).

Config file

Path: ~/.config/sdwire/config.yaml on every OS (not os.UserConfigDir()'s platform-specific location — macOS's ~/Library/ Application Support in particular — this project deliberately uses one fixed XDG-style path everywhere). Override with --config <path> or the SDWIRE_CONFIG environment variable. Individual keys are also overridable via SDWIRE_-prefixed environment variables (e.g. SDWIRE_DEFAULT_DEVICE, with . replaced by _ for nested keys).

default_device: bench
devices:
  bench:
    serial: "20120501030900000"        # or the port-suffixed identity form
    location: "1-1.1.3"                # optional; disambiguates identical Realtek serials
    power:
      type: meross
      ip: 192.0.2.10
      key: "<meross account key>"
      # channel: 0                     # optional
min_off_seconds: 8
  • default_device is used whenever -s/--serial isn't given.
  • Each entry under devices names a device you can pass to -s; either key alone is enough. Given both, location is tried first (more specific — needed because every Realtek SDWire3 reader shares the same hardcoded USB serial number) and serial is the fallback: a location names the socket the device is plugged into, so moving it to another port makes the location stale. Commands then find the device by serial and warn that the location: line wants updating, rather than reporting the device missing.
  • power configures a power plugin for that device — see below. A device with no power section works fine for list/state/switch/flash; only sdwire power requires one.
  • min_off_seconds (default 8) is the minimum dark time sdwire power cycle and sdwire flash hold target power off for.

No config file at all is a fully supported setup: with exactly one SDWire attached, list, state, switch, flash, and disk all work without any configuration; power will explain what to add.

Power plugins

The CLI ships one registered power plugin type, meross (for Meross smart plugs — see power/meross/README.md for how to get your Meross account key). The SDK itself (sdwire.PowerFunc) is plugin-agnostic; this registry, and the config wiring around it, lives in the CLI. sdwire power on|off|cycle with an unrecognized power.type lists the registered types in its error.

Migrating from the Python CLI

sdwire state and sdwire switch match the Python sdwire-cli v0.3.1's output byte-for-byte, and sdwire list keeps its first three columns at the same widths and separators — with a State column appended, and rows for devices remembered but not currently on the bus. Scripts reading those three columns by position work unchanged once this binary is what sdwire on your PATH resolves to. flash, power, disk, revive, --json, and --debug are new.

PATH precedence. If you already have the Python CLI installed (e.g. via pip), check which one PATH finds first:

which -a sdwire

go install puts this binary at $(go env GOPATH)/bin/sdwire (typically ~/go/bin/sdwire); make sure that directory precedes wherever the Python version lives (often an Anaconda/Miniconda bin directory, or a pip --user install path) in PATH, or pip uninstall sdwire the Python one.

SDWire3 state semantics

For SDWire3, a device currently in target mode is, physically, powered off — it isn't enumerable on USB at all. sdwire state never powers a device on to answer: when there's nothing live to read, it falls back to the on-disk hub-port cache and a direct hub port-status read, which is enough to report Target honestly without side effects. sdwire switch dut/target does the same check before doing any work, so switching a device that's already in target mode is also a no-op. Only commands that need the SD card to actually move data — switch host/ts and flash — revive a powered-off SDWire3 via the cache, since restoring power is inherent to what those commands do.

That cache is a separate file from your config, at <user cache dir>/sdwire/hubports.json~/Library/Caches/sdwire/hubports.json on macOS, ~/.cache/sdwire/hubports.json on Linux, %LocalAppData%\sdwire\hubports.json on Windows. Any command that finds a device live rewrites its entry, so deleting the file is safe: the worst that happens is a device sitting in target mode can't be found again until it's powered back on some other way.

A device moved between sockets over its life leaves one entry per socket, and nothing removes the old ones — every SDWire3 shares one Realtek serial, so a bare serial can name several remembered ports. Each entry records when the device was last seen there, and a device that isn't on the bus is taken to be at the port it was most recently seen at: state, switch dut's already-in-target check and revive by serial answer for that one, and switch host powers it on first. Entries written before this was recorded have no sighting to compare, so they still report the ambiguity until the device is next seen live.

Permissions

Flashing needs raw block device access: run sdwire flash (or any direct write to the path from sdwire disk) with sudo on macOS/Linux, or as Administrator on Windows.

Linux udev rules. Create /etc/udev/rules.d/99-sdwire.rules:

# SDWireC (FTDI)
SUBSYSTEM=="usb", ATTR{idVendor}=="04e8", ATTR{idProduct}=="6001", MODE="0666"
# SDWire3 (Realtek reader)
SUBSYSTEM=="usb", ATTR{idVendor}=="0bda", ATTR{idProduct}=="0316", MODE="0666"
# SDWire3 switching also needs write access to its upstream hub, since the
# card is handed to the target by cutting that hub port's power (VBUS).
# Either run as root, or grant access to hub devices:
SUBSYSTEM=="usb", ATTR{bDeviceClass}=="09", MODE="0666"

Then reload rules and add your user to plugdev:

sudo udevadm control --reload-rules
sudo udevadm trigger
sudo usermod -a -G plugdev $USER

Windows. SDWire3 switching needs libusb access to the upstream hub, not just the reader — install UsbDk, or bind the hub to WinUSB with Zadig.

SDK quick-start

package main

import (
	"context"
	"log"

	"github.com/jphastings/sdwire"
	"github.com/jphastings/sdwire/power/meross"
)

func main() {
	powerFunc, err := meross.New("192.0.2.10", "your-meross-account-key")
	if err != nil {
		log.Fatal(err)
	}

	dev, err := sdwire.New(
		sdwire.WithTargetPower(powerFunc),
		sdwire.WithWarningHandler(func(msg string) { log.Println("warning:", msg) }),
	)
	if err != nil {
		log.Fatal(err)
	}
	defer dev.Close()

	if err := dev.FlashAndBoot(context.Background(), "./image.img",
		sdwire.WithFlashProgress(func(written, total int64) {
			log.Printf("flashed %d / %d bytes", written, total)
		}),
	); err != nil {
		log.Fatal(err)
	}

	mode, err := dev.Mode()
	if err != nil {
		log.Fatal(err)
	}
	log.Println("now in mode:", mode)
}

Other entry points: ListDevices() enumerates every attached device; NewWithSerial/NewWithIdentity connect to a specific one (by bare serial, or by the port-suffixed identity/location forms respectively — identical to what the CLI's -s flag accepts); SetMode/Mode switch and read back the card's side; PowerCycle and TargetPower drive a configured PowerFunc directly, independent of flashing; WithoutRevive() disables the hub-cache power-on fallback for callers that must not risk switching a target-mode SDWire3 back to host mode; CachedPortState reads an SDWire3's mode from the on-disk hub cache without powering anything on or off at all, for exactly that case; WithWriteRetries and WithWriteTiming tune a flash's recovery from a reader that drops off the bus mid-write, and expose its per-chunk write timings; ListDeviceStates() is the same idea for the whole inventory, returning every attached device plus every remembered-but-absent one with the mode each is in; and Revive power-cycles a device's hub port to recover a reader that has been dropped from the bus, addressable by location when no other selector can reach it.

Supported operating systems

  • Linux — tested on Ubuntu, Debian
  • macOS — tested on macOS 10.15+ (uses ioreg/diskutil for block device discovery)
  • Windows — tested on Windows 10+ (needs libusb access to the SDWire3's hub — see Permissions)

Troubleshooting

Device not found. Confirm it's actually attached and enumerating: SDWireC shows up as USB 04e8:6001, SDWire3 as 0bda:0316. On Linux, check the udev rules above; on Windows, check libusb/UsbDk binding.

SDWire3 stuck in target mode. This is expected, not broken — see SDWire3 state semantics. sdwire switch host/ts and sdwire flash (and, in the SDK, New/NewWithSerial/ NewWithIdentity by default) revive it via the on-disk hub-port cache, powering it back on into host mode; state, list and power never do. sdwire revive forces the same thing explicitly, for a device too broken to be selected the usual way.

The reader vanishes mid-flash and only comes back on a physical replug. The SD reader inside an SDWire3 can stop answering under sustained writes. The OS asks its hub for a port reset, the reset fails to bring the port back up, and the device is torn off the bus — after which nothing re-probes that port, so sdwire list reports it as Unknown (or, if the port was powered down too, Target). On macOS the sequence appears in log show as:

USB3.0-CRW@01113000 endpoint 0x01: status 0xe0005000 (pipe stalled): 65536 bytes transferred
USB3.0-CRW@01113000 endpoint 0x01: status 0xe00002ed (transaction error): 0 bytes transferred
IOUSBMassStorageDriver: USB device 0BDA031601113000 - will be reset!
AppleUSB20HubPort::resetAndCreateDevice: reset did not enable port
AppleUSBHostPort::terminateDevice: destroying 0x0bda/0316/0204 (USB3.0-CRW): reset API call

Endpoint 0x01 is the reader's bulk-OUT pipe, so this is the reader crashing mid-write — not a fault in this tool, and not something a port reset clears. Nothing in software can stop the reader stalling; what software can do is not lose the work.

sdwire flash recovers from this automatically, power-cycling the port and resuming where it stopped. Outside a flash, use sdwire revive instead of reaching for the cable; sdwire revive -s <location> works even when the device can no longer be selected by serial.

Two things make it worse and are worth ruling out: a reader on a USB 2.0 port writes at a tenth of the speed and so spends ten times as long in the window where it can fail, and other software grabbing the card (Spotlight indexing a freshly mounted volume, or anything enumerating USB) adds load at exactly the wrong moment.

"device is attached to a root hub port" / a hubpower.ErrRootPort-shaped error. SDWire3 switching works by cutting power to the device's upstream hub port; a device plugged directly into a computer's built-in (root) USB port has no controllable parent to do that through — see the VBUS story above. Attach it behind an external hub with independent per-port power switching instead.

Contributing

Contributions are welcome — please open an issue first for anything more than a small fix.

License

MIT License — see LICENSE.


Forked and extended by @jphastings. Originally made with ❤️ at the Recurse Center.

Documentation

Overview

Package sdwire provides a Go SDK for controlling SDWireC and SDWire3 devices. SDWire devices are USB-controlled SD card multiplexers that allow switching an SD card between a Device Under Test (DUT) and a Test System (TS).

This is a fork of github.com/fcjr/sdwire with SDWire3 support and fixes.

Index

Constants

View Source
const (
	SDWireCVID         = 0x04E8
	SDWireCPID         = 0x6001
	SDWireCProductName = "sd-wire"

	SDWire3VID = 0x0BDA
	SDWire3PID = 0x0316
)
View Source
const DefaultMinDarkTime = 8 * time.Second

DefaultMinDarkTime is the minimum time PowerCycle keeps the target board's power off when no explicit minimum is given. Small DUT boards can ride through a mains interruption of a couple of seconds on PSU bulk capacitance without actually resetting, so a genuine power cycle needs a longer guaranteed dark time.

Variables

View Source
var ErrNoDeviceFound = errors.New("no matching SDWire device found")

ErrNoDeviceFound is wrapped into every "not found" error returned by the selectBy* functions (and used directly by New()). It lets connect() distinguish "nothing matched" — worth trying the hubpower cache fallback for — from an ambiguous-match error, which should be returned as-is rather than silently resolved by a cache lookup. It is exported so callers (e.g. the sdwire CLI) can tell "device isn't there right now" apart from other connection failures.

Functions

This section is empty.

Types

type DeviceController

type DeviceController interface {
	// SetMode switches the SD card between the target device and the host computer.
	SetMode(mode SwitchMode) error
	// Mode reads back which side the SD card is currently connected to, where the
	// underlying mechanism allows an honest readback.
	Mode() (SwitchMode, error)
	// Close releases any USB device handles the controller owns.
	Close() error
}

DeviceController defines the interface for controlling different SDWire device generations.

type DeviceGeneration

type DeviceGeneration int

DeviceGeneration represents the generation/type of SDWire device.

const (
	// GenerationSDWireC represents the original SDWireC device using FTDI control.
	GenerationSDWireC DeviceGeneration = iota
	// GenerationSDWire3 represents the SDWire3 device using kernel driver attach/detach.
	GenerationSDWire3
)

func (DeviceGeneration) String

func (g DeviceGeneration) String() string

String returns a human-readable description of the device generation.

type DeviceInfo

type DeviceInfo struct {
	Serial       string
	Product      string
	Manufacturer string
	Generation   DeviceGeneration
	// Bus is the USB bus the device was enumerated on.
	Bus int
	// PortPath is the physical path of parent hub ports leading to the
	// device, as reported by gousb's DeviceDesc.Path.
	PortPath []int
}

DeviceInfo contains identifying information about an SDWire device.

func ListDevices

func ListDevices() ([]*DeviceInfo, error)

ListDevices discovers all connected SDWire devices and returns their information. This is useful for device enumeration before connecting to a specific device.

func Revive added in v0.3.0

func Revive(selector string, opts ...Option) (DeviceInfo, error)

Revive power-cycles the hub port an SDWire3 is (or was) attached to and waits for it to re-enumerate: the software equivalent of unplugging the device and plugging it back in.

It exists for a reader that has stopped answering and been torn off the bus by the OS — a state a USB port reset does not clear, and which leaves the device invisible to ListDevices, so no ordinary selector can reach it. The port is held dark for readerRevivePause rather than merely reset, since a crashed reader can latch up through a shorter interruption.

selector may be:

  • a location ("1-1.1.3"), resolved from live USB topology — the hub is still there even when the device on it isn't, so this works with no cache entry at all, or an ambiguous one;
  • a serial or port-suffixed identity, matched against the hub-port cache;
  • "", which requires the cache to hold exactly one entry.

The revived device's info is returned, and its hub port re-cached under the identity it enumerated with.

func (DeviceInfo) Identity

func (d DeviceInfo) Identity() string

Identity returns the device's serial number, suffixed with its USB port path, e.g. "20120501030900000.1.1.3". This matches the identity format used by the Badger-Embedded Python sdwire CLI, and is needed because all Realtek SDWire3 devices share the same hardcoded USB serial number. A device with an empty PortPath returns its bare serial number.

func (DeviceInfo) Location

func (d DeviceInfo) Location() string

Location returns the device's USB topology location in Linux sysfs style, e.g. "1-1.1.3". Devices with no parent hub ports in their path (an empty PortPath) return just the bus number, e.g. "1".

type DeviceState added in v0.3.0

type DeviceState struct {
	Info DeviceInfo
	Mode SwitchMode
	// Attached reports whether the device was enumerated on USB. False
	// means it is known only from the hub-port cache: its remembered port
	// is either unpowered (an SDWire3 in target mode) or powered with
	// nothing enumerated on it — an empty socket, or a reader that has
	// stopped answering and been torn off the bus by the OS.
	Attached bool
}

DeviceState is an SDWire and the mode its card is currently switched to.

func ListDeviceStates added in v0.3.0

func ListDeviceStates(opts ...Option) ([]DeviceState, error)

ListDeviceStates returns every SDWire this host knows about, with the mode each one's card is switched to: the devices currently enumerated on USB, plus every hub-port cache entry that is not currently producing one.

It never powers a port on or off — an SDWire3 sitting in target mode stays there — so unlike the New-family constructors it is safe to call just to see what is around. Cache-derived entries are a claim about a remembered location rather than a device seen now: see DeviceState.Attached.

type FlashOption

type FlashOption func(*flashOptions)

FlashOption customizes a FlashAndBoot call.

func WithBlockDevTimeout

func WithBlockDevTimeout(d time.Duration) FlashOption

WithBlockDevTimeout sets how long FlashAndBoot waits for the reader's block device to appear after switching to host mode. The default is 30 seconds.

func WithFlashMinDarkTime

func WithFlashMinDarkTime(d time.Duration) FlashOption

WithFlashMinDarkTime sets the minimum time FlashAndBoot keeps the target board's power off before powering it back on, matching (*SDWire). PowerCycle's minOff semantics. The default is DefaultMinDarkTime.

func WithFlashProgress

func WithFlashProgress(fn func(written, total int64)) FlashOption

WithFlashProgress configures a callback invoked after each chunk is written during FlashAndBoot, reporting bytes written so far and the total image size.

func WithMaxDeviceSize

func WithMaxDeviceSize(bytes int64) FlashOption

WithMaxDeviceSize sets the sanity cap FlashAndBoot refuses to write past, guarding against a mis-mapped block device. The default is 2TiB.

func WithWriteRetries added in v0.3.0

func WithWriteRetries(n int) FlashOption

WithWriteRetries sets how many times a flash will recover from the reader dropping off the bus mid-write — power-cycling its hub port and resuming from the last completed chunk — before giving up. Zero disables the recovery, failing on the first such error as a plain write error.

func WithWriteTiming added in v0.3.0

func WithWriteTiming(fn func(offset int64, size int, took time.Duration)) FlashOption

WithWriteTiming registers a callback invoked after each chunk is written to the card, with the chunk's offset within the image, its size in bytes, and how long the write itself took (excluding reading the image).

It exists to make the shape of a flash measurable: a reader whose internal buffer is backing up shows up here as chunk times climbing well before it stalls outright.

type ModeOption

type ModeOption func(*modeOptions)

ModeOption customizes a single SetMode call.

func WithoutUnmount

func WithoutUnmount() ModeOption

WithoutUnmount skips the automatic unmount of the reader's mounted volumes that SetMode(ModeTarget) performs by default.

type Option

type Option func(*options)

Option customizes a newly constructed SDWire.

func WithHostWaitTimeout

func WithHostWaitTimeout(d time.Duration) Option

WithHostWaitTimeout sets how long to wait for an SDWire3 to re-enumerate after switching to ModeHost (or while powering one on from the hub cache fallback) before giving up. The default is 20 seconds.

func WithHubCachePath

func WithHubCachePath(path string) Option

WithHubCachePath overrides where the on-disk hub-port cache is read from and written to, in place of hubpower.DefaultCachePath().

func WithLegacySDWire3Switching

func WithLegacySDWire3Switching() Option

WithLegacySDWire3Switching makes SDWire3 devices use the legacy kernel-driver detach/reset controller instead of the default VBUS port-power controller. That mechanism is not known to reliably move the SD card mux (see sdwire3Controller's doc comment), but may work on some native Linux setups; it is never the default.

func WithTargetPower

func WithTargetPower(fn PowerFunc) Option

WithTargetPower configures the PowerFunc used to control power to the target board (the Device Under Test) attached via this SDWire.

func WithWarningHandler

func WithWarningHandler(fn func(msg string)) Option

WithWarningHandler configures a callback that receives non-fatal warning messages generated while operating a device, such as an SDWire3 sitting behind a ganged-power-switching hub. The default silently discards them.

func WithoutRevive

func WithoutRevive() Option

WithoutRevive disables the hub-cache revive fallback: when no attached device matches, connect() returns the not-found error as-is instead of powering on a cached hub port and waiting for the device to reappear. Read-only callers (e.g. the sdwire CLI's `state` command) use this so they never have the side effect of switching an SDWire3 that is intentionally powered off in target mode back to host mode.

type PowerFunc

type PowerFunc func(shouldBeOn bool) error

PowerFunc controls power delivery to the target board (the Device Under Test) — not to the SDWire itself. Implementations should block until the requested power state has taken effect.

type SDWire

type SDWire struct {
	// contains filtered or unexported fields
}

SDWire represents a connected SDWire device that can switch an SD card between a target device and host computer.

func New

func New(opts ...Option) (*SDWire, error)

New connects to the first available SDWire device. This is a convenience function for single-device setups. The returned SDWire must be closed with Close() when done.

func NewWithIdentity

func NewWithIdentity(id string, opts ...Option) (*SDWire, error)

NewWithIdentity connects to a specific SDWire device using either the suffixed form returned by DeviceInfo.Identity() (e.g. "20120501030900000.1.1.3") or the form returned by DeviceInfo.Location() (e.g. "1-1.1.3"). Use NewWithSerial instead to match on a bare serial number. The returned SDWire must be closed with Close() when done.

func NewWithSerial

func NewWithSerial(serial string, opts ...Option) (*SDWire, error)

NewWithSerial connects to a specific SDWire device by its serial number. serial may be a plain USB serial number, or the suffixed form returned by DeviceInfo.Identity() (e.g. "20120501030900000.1.1.3") to disambiguate devices that share a serial number, such as SDWire3s. If a plain serial matches more than one attached device, an error is returned listing the Identity() of each candidate. The returned SDWire must be closed with Close() when done.

func (*SDWire) Close

func (s *SDWire) Close() error

Close releases the SDWire's controller (and any USB device handle(s) it holds) and its gousb.Context. Always call this when done with the device.

func (*SDWire) FlashAndBoot

func (s *SDWire) FlashAndBoot(ctx context.Context, imagePath string, opts ...FlashOption) error

FlashAndBoot writes the image at imagePath to this SDWire's SD card and boots the target from it:

  1. Powers off the target (a no-op if no PowerFunc is configured; see WithTargetPower), recording when power was cut.
  2. Switches to host mode and waits for the reader's block device to appear.
  3. Checks the image fits the device (and the device fits a sanity cap), then unmounts any mounted volumes on it.
  4. Raw-writes the image to the device in chunks, reporting progress. If the reader drops off the bus mid-write — see WithWriteRetries — its hub port is power-cycled and the write resumes where it stopped.
  5. Switches back to target mode.
  6. Tops up the dark time begun in step 1 to at least the configured minimum, then powers the target back on.

Because a target board normally only probes its SD slot at boot (see (*SDWire).SetMode), step 6's power-on is what actually makes the newly written card visible to it. If no PowerFunc is configured, step 6 is skipped entirely and the caller is responsible for booting the target themselves.

Each step checks ctx before proceeding, so a cancelled context stops the operation at the next opportunity (including between write chunks).

func (*SDWire) GetManufacturer

func (s *SDWire) GetManufacturer() string

GetManufacturer returns the device's USB manufacturer name.

func (*SDWire) GetProduct

func (s *SDWire) GetProduct() string

GetProduct returns the device's USB product name.

func (*SDWire) GetSerial

func (s *SDWire) GetSerial() string

GetSerial returns the device's USB serial number.

func (*SDWire) HasTargetPower

func (s *SDWire) HasTargetPower() bool

HasTargetPower reports whether a PowerFunc has been configured.

func (*SDWire) Info

func (s *SDWire) Info() DeviceInfo

Info returns the DeviceInfo this SDWire was connected with, including its USB topology (Bus, PortPath) and the Identity()/Location() helpers derived from them. Useful for callers (e.g. the sdwire CLI) that connected via a partial selector — a bare serial, or a configured device name — and need the fully-resolved identity of the device they ended up with.

func (*SDWire) Mode

func (s *SDWire) Mode() (SwitchMode, error)

Mode reads back which side the SD card is currently connected to. Not all controllers can answer honestly — see the relevant DeviceController's Mode doc comment (in particular, WithLegacySDWire3Switching's controller cannot).

func (*SDWire) PowerCycle

func (s *SDWire) PowerCycle(minOff time.Duration) error

PowerCycle power-cycles the target board: power off, a guaranteed dark time, then power on. minOff sets the minimum time power stays off; values <= 0 fall back to DefaultMinDarkTime. PowerCycle never sleeps for less than the requested dark time. If no PowerFunc is configured, PowerCycle is a no-op that returns nil without sleeping.

func (*SDWire) SetMode

func (s *SDWire) SetMode(mode SwitchMode, opts ...ModeOption) error

SetMode switches the SD card between the target device and the host computer.

For SDWire3 devices (the default; see WithLegacySDWire3Switching for the legacy kernel-driver-based mechanism), this works by cutting or restoring VBUS on the SDWire3's upstream USB hub port, rather than by any command understood by the SDWire3 itself:

  • ModeTarget cuts power to the SDWire3 reader. Losing power drops it off the bus entirely, and the now-unpowered mux passes the SD card through to the target — typically within about a second.
  • ModeHost restores power. The reader re-enumerates at its USB power-on default (card connected to the host); the resulting block device typically appears roughly 6 seconds later.

Because a target board normally only probes its SD slot at boot or on a card-detect edge, it will not notice a card that arrived via ModeTarget until it is rebooted or power-cycled — see (*SDWire).PowerCycle.

SDWireC devices switch instantly via FTDI CBUS bits and have no such caveat.

Before switching to ModeTarget, any volumes mounted from this SDWire's reader are unmounted (see Unmount in internal/blockdev; on macOS a politely-dissented unmount is retried with force, since the data is flushed either way and the card is leaving the host regardless). Pass WithoutUnmount to skip this. If the reader's block device cannot be located at all, the switch proceeds — there is nothing mounted to lose — but an actual failed unmount aborts the switch rather than yanking a mounted filesystem away.

func (*SDWire) SetTargetPower

func (s *SDWire) SetTargetPower(fn PowerFunc)

SetTargetPower configures the function used to control power to the target board. Pass nil to remove any previously configured PowerFunc.

func (*SDWire) String

func (s *SDWire) String() string

String returns a formatted string with device information.

func (*SDWire) TargetPower

func (s *SDWire) TargetPower(on bool) error

TargetPower turns power to the target board on or off. If no PowerFunc has been configured, this is a documented no-op that returns nil.

type SwitchMode

type SwitchMode int

SwitchMode represents the SD card connection mode.

const (
	// ModeTarget connects the SD card to the target device being tested.
	ModeTarget SwitchMode = iota
	// ModeHost connects the SD card to the host computer for flashing/access.
	ModeHost
)
const ModeUnknown SwitchMode = -1

ModeUnknown indicates a DeviceController could not determine which side the SD card is currently connected to (for example, a hub port that is powered but whose device has not finished re-enumerating yet).

func CachedPortState

func CachedPortState(selector string, opts ...Option) (SwitchMode, string, error)

CachedPortState reports an SDWire3's mode purely from the on-disk hub cache and a hub port-status read, WITHOUT powering anything on or off — safe for a device that is currently powered off (in target mode). This is the read-only counterpart to New's cache-revive fallback: it never causes an SDWire3 sitting in target mode to switch to host mode as a side effect of being asked about it.

selector may be "" (which requires exactly one cached entry, erroring and listing candidates otherwise), or a serial / port-suffixed identity / location matched with the same rules as NewWithSerial / NewWithIdentity.

Alongside the mode, CachedPortState returns the matched cache entry's identity (its DeviceInfo.Identity() form) — useful for callers (e.g. the sdwire CLI) that need to display which device they read state for. The returned error is nil unless the cache itself couldn't be read, no entry matched selector, or opening the hub / reading its port status failed (which is expected for a genuinely stale or unreachable cache entry).

func (SwitchMode) String

func (m SwitchMode) String() string

String returns a human-readable description of the switch mode.

Directories

Path Synopsis
cmd
sdwire command
Command sdwire is a CLI for controlling SDWireC and SDWire3 USB SD-card multiplexers, and a drop-in replacement for the Python sdwire CLI's list/state/switch commands.
Command sdwire is a CLI for controlling SDWireC and SDWire3 USB SD-card multiplexers, and a drop-in replacement for the Python sdwire CLI's list/state/switch commands.
Package hubpower controls USB hub downstream port power (VBUS) over gousb.
Package hubpower controls USB hub downstream port power (VBUS) over gousb.
internal
blockdev
Package blockdev locates, sizes, and prepares for writing the whole-disk block device backing a specific USB mass-storage device — typically an SDWire reader switched into host mode — identified by its USB vendor/product ID and its physical bus/port location.
Package blockdev locates, sizes, and prepares for writing the whole-disk block device backing a specific USB mass-storage device — typically an SDWire reader switched into host mode — identified by its USB vendor/product ID and its physical bus/port location.
Package power documents the contract for SDWire target-power plugins.
Package power documents the contract for SDWire target-power plugins.
meross
Package meross drives Meross smart plugs (MSS315 and compatible models) over their local HTTP API, with no dependency on the Meross cloud at runtime.
Package meross drives Meross smart plugs (MSS315 and compatible models) over their local HTTP API, with no dependency on the Meross cloud at runtime.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL