flipper

package
v0.167.0 Latest Latest
Warning

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

Go to latest
Published: May 11, 2026 License: AGPL-3.0 Imports: 19 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrCommandRequiresUSB = errors.New("this Flipper command has no RPC equivalent in firmware and is only available over USB")

ErrCommandRequiresUSB is returned by Flipper command methods when the underlying transport is BLE and the requested operation has no equivalent RPC verb in the firmware. Sub-GHz, NFC, IR, RFID, iButton, and BadUSB are CLI-only on every Flipper firmware (stock + Momentum) because the firmware exposes only RPC over BLE Serial — see flipperdevices/flipperzero-firmware applications/services/bt and applications/services/rpc/. Surface this error to operators with the command name + the suggestion to attach the Flipper via USB.

View Source
var ErrConnectTimeout = errors.New("timeout waiting for Flipper CLI prompt")

ErrConnectTimeout is returned when the Flipper does not produce a CLI prompt within the connect timeout. The Flipper is likely inside an app or on a dialog that has taken over the CLI — press Back on the device and retry.

View Source
var ErrFlipperSuspended = errors.New("flipper offline (UART bridge active)")

ErrFlipperSuspended is returned by CLI methods when the Flipper handle is suspended (typically because the Flipper firmware is in USB-UART bridge mode and the CLI is unreachable by design). The wording is public-facing — it surfaces in agent tool errors and the web UI banner.

View Source
var ErrInRPCMode = errors.New("flipper is in RPC (mirror) mode; release the mirror to use CLI commands")

ErrInRPCMode is returned by CLI methods when the Flipper is held by a concurrent RPC session. Release the mirror (call the closure returned by EnterRPC) before issuing CLI commands.

View Source
var ErrResponseTruncated = errors.New("flipper response truncated: exceeded max accumulator size")

ErrResponseTruncated is returned by read paths when accumulated output exceeds the configured cap. Partial output is still returned alongside the error so callers can inspect what arrived before the overflow.

Functions

func ConnectURL

func ConnectURL(ctx context.Context, rawURL string, timeout time.Duration) (*Flipper, *ConnectionReport, error)

ConnectURL opens the transport identified by rawURL and performs the CLI handshake. rawURL is any scheme registered with the transport package; a bare device path (e.g. "/dev/ttyACM0") is accepted as shorthand for serial://.

Returns the connected handle, a populated *ConnectionReport detailing each step's outcome and timing (always non-nil — even on error so operators can see which step actually failed), and any terminal error. On error the transport is already closed and the *Flipper is nil. On success the report is also stashed on the returned handle (Flipper.ConnectionReport) so /api/device can surface it.

The handshake is cancelable: if ctx is cancelled or timeout elapses before the prompt arrives, ConnectURL closes the transport and returns ctx.Err() or ErrConnectTimeout respectively.

func SanitizeArg

func SanitizeArg(s string) string

SanitizeArg is the exported wrapper for callers outside this package that build Flipper CLI commands directly (e.g. the agent's inline bruteforce dispatch). Prefer the typed wrapper functions when one exists. Delegates to clisafe.SanitizeArg.

Types

type Capabilities

type Capabilities struct {
	// ===== Identity (existing, preserved) =====
	FirmwareFork    string // "" (stock/OFW), "Unleashed", "Momentum", "Xtreme", "RogueMaster", …
	FirmwareVersion string // fork-specific version string from firmware_version
	FirmwareCommit  string // short git SHA from firmware_commit
	FirmwareDate    string // build date string from firmware_build_date (DD-MM-YYYY)
	HardwareUID     string // STM32 unique ID (16 hex chars) from hardware_uid
	HardwareName    string // user-settable dolphin name from hardware_name

	// ===== Identity (new) =====
	// FirmwareBand is the resolved fork+version band, e.g. "momentum/mntm-dev",
	// "unleashed/unlshd-086", "stock/1.0.x". See resolveBand() for the full
	// value set (firmware-matrix.md §3.6).
	FirmwareBand        string
	FirmwareAPIMajor    int    // from firmware_api_major (numeric); 80-83=OFW 0.10x, 85+=OFW 1.x, 77-79=Momentum, 70-72=Xtreme, 86-87=Unleashed/RM
	FirmwareAPIMinor    int    // from firmware_api_minor
	FirmwareCommitDirty bool   // from firmware_commit_dirty ("1" → true); nightly-build signal
	FirmwareOriginGit   string // from firmware_origin_git (upstream repo URL)
	HardwareRegion      string // from hardware_region — string form (EU/US/JP/WW or numeric)
	HardwareVer         int    // from hardware_ver (board revision; F7 production = 13)
	// DeviceInfoKeyStyle indicates which key separator the parser saw.
	// Always "underscore" when populated via device_info (the standard path);
	// set to "dotted" if a future caller uses `info device` output instead.
	DeviceInfoKeyStyle string

	// ===== CLI surface (existing, preserved) =====
	// PowerInfoCmd is the CLI verb that returns power/battery information.
	// All modern forks use "info power"; empty means unavailable.
	PowerInfoCmd           string // "info power" on all modern forks
	HasNFCSubshell         bool   // `nfc` subshell present; false only on Xtreme
	SubGHzNeedsDev         bool   // `subghz tx/rx` requires a trailing `<device>` arg (0=INT, 1=EXT)
	NFCFlaggedArgs         bool   // NFC subshell uses flag-based args (-p, -d, -b) rather than positional
	SubGHzRxRawHasFilePath bool   // `subghz rx_raw` accepts a file-path arg (false on all modern forks — streams to stdout)

	// ===== CLI surface (new — architect additions §C.1) =====
	// JSEngineKind is the CLI verb prefix for the JS engine ("mjs" on all
	// four active forks including archived Xtreme; firmware-matrix.md §4.1
	// found no divergence despite the runbook's earlier claim).
	JSEngineKind           string // "mjs" universally; "" if JS is absent
	HasBLESpam             bool   // BLE Spam FAP present (Momentum/Xtreme in-tree; RM optimistic default)
	HasSubGHzBruteforcer   bool   // Sub-GHz Bruteforcer FAP available on this fork
	HasMouseJackerFAP      bool   // NRF24 Mousejacker FAP (NRF24 add-on required)
	HasSeaderFAP           bool   // HID iCLASS Seader FAP (Momentum/Unleashed/RM)
	HasPicopassFAP         bool   // PicoPass FAP (all custom forks)
	HasNFCMagicFAP         bool   // NFC Magic card-writer FAP (all custom forks)
	HasMFKeyFAP            bool   // MFKey32 FAP (all custom forks; Unleashed ships it in-tree)
	HasMifareNestedFAP     bool   // Mifare Nested attack FAP (all custom forks)
	UniversalIRLibraryName string // SD path for the universal IR library; "assets/infrared/assets" (stock/Unleashed/RM) vs "infrared/assets" (Momentum/Xtreme)

	// ===== CLI surface (new — research additions §4.2) =====
	HasStorageFormatExt    bool // `storage format_ext` verb present (all custom forks; absent on stock OFW)
	HasSubGHzEncryptKeeloq bool // `subghz encrypt_keeloq` verb present (all custom forks)
	HasSubGHzChat          bool // `subghz chat` verb present (universal — all five forks)
	HasPsCmd               bool // `ps` alias for `top` present (Momentum + Xtreme)
	HasClearCmd            bool // `clear` terminal-clear command present (Momentum only)

	// ===== Storage quirks (new) =====
	// StorageExtFatLabel is the FAT volume label of the SD card.
	// Defaults to "Flipper SD"; Momentum freshly formats with "MOMENTUM".
	StorageExtFatLabel string
	// SnapshotPrefix is the root path for pre-write SD snapshots used by /rewind.
	SnapshotPrefix string

	// ===== Marauder-side (probed separately; not set by device_info) =====
	// MarauderDetected and MarauderCompatBand are set by the Marauder
	// connect path, not by DetectCapabilities. Left as zero-value TODOs
	// here for a follow-up v0.5.1 research task (firmware-matrix.md §6 Q6).
	MarauderDetected   bool
	MarauderCompatBand string
}

Capabilities captures the firmware-specific CLI surface of the connected Flipper, detected from `device_info` at connect time. Different custom firmwares (stock, Unleashed, RogueMaster, Xtreme, Momentum, ...) expose different CLI commands and behaviours; wrappers branch on these flags to stay portable across all five active forks.

Field ordering: identity → CLI surface → RF/NFC quirks → apps/FAPs → storage → marauder. Existing fields are preserved byte-for-byte; new fields are appended in their sections so callers that embed Capabilities by value are not broken.

func (Capabilities) FriendlyFork

func (c Capabilities) FriendlyFork() string

FriendlyFork returns a display-ready fork name, falling back to "stock" when the fork field is empty (OFW omits firmware_origin_fork entirely).

type Check added in v0.13.0

type Check struct {
	Name    string        `json:"name"`
	Level   CheckLevel    `json:"level"`
	Detail  string        `json:"detail,omitempty"`
	Elapsed time.Duration `json:"elapsed_ns"`
}

Check is one step's outcome inside a ConnectionReport.

Name is a stable, machine-readable identifier (snake_case with dotted namespacing — e.g. "transport.dial", "handshake", "detect_capabilities"). Detail is operator-facing free text — kept short, no ANSI. Elapsed is the wall-clock time the step took.

type CheckLevel added in v0.13.0

type CheckLevel string

CheckLevel classifies a single ConnectionReport step's outcome.

LevelPass: the step completed cleanly. LevelWarn: the step succeeded after a recovery, or completed with a non-fatal degradation that the operator should know about. LevelFail: the step failed terminally; downstream steps were skipped. LevelSkipped: the step did not run on this transport / firmware (e.g. CLI handshake on a BLE link).

const (
	LevelPass    CheckLevel = "pass"
	LevelWarn    CheckLevel = "warn"
	LevelFail    CheckLevel = "fail"
	LevelSkipped CheckLevel = "skipped"
)

type CommandRoute added in v0.13.0

type CommandRoute int

CommandRoute enumerates the dispatch paths a Flipper command can take. The set is closed: every (transport, support) pair in RouteFor must resolve to one of these values.

const (
	// RouteTextCLI sends the command as plain text down the serial
	// channel and parses the firmware's `>: ` prompt response. This
	// is the default route for USB transports where the firmware
	// exposes a text CLI.
	RouteTextCLI CommandRoute = iota
	// RouteRPC streams a protobuf request through the persistent
	// rpc.Client. This is the default route for BLE transports — the
	// firmware exposes ONLY protobuf RPC over BLE Serial, never a
	// text CLI — and is also valid on USB when the caller prefers
	// the structured response shape.
	RouteRPC
	// RouteUSBOnly indicates the requested operation cannot be
	// serviced on the live transport. Most often this means the
	// firmware has no RPC verb for the operation and the caller is
	// on BLE; the wrapper must surface ErrCommandRequiresUSB.
	RouteUSBOnly
)

func (CommandRoute) String added in v0.13.0

func (r CommandRoute) String() string

String returns a stable, lower-case route name suitable for log lines and error reasons.

type CommandSupport added in v0.13.0

type CommandSupport struct {
	HasRPCVerb           bool
	HasCLI               bool
	FirmwareForkRequired string
}

CommandSupport describes the capability surface a wrapper expects from the firmware. Each field captures a single yes/no fact about the command:

  • HasRPCVerb — true when the firmware exposes a protobuf RPC request for this operation. Default false (most CLI verbs do NOT have an RPC equivalent — Sub-GHz, NFC, IR, RFID, iButton, BadUSB are CLI-only on every fork).
  • HasCLI — true when the firmware exposes a text-CLI verb for this operation. Default true (almost every command this codebase wraps is a CLI verb; the exceptions are pure-RPC ops like the gpio/loader pairs that some forks dropped from CLI).
  • FirmwareForkRequired — when non-empty, the operation is only supported on the named fork (case-insensitive). Used for Momentum-only or Xtreme-only features.

The zero value (HasRPCVerb=false, HasCLI=false, fork="") is intentionally "broken" — every wrapper must explicitly state at least one supported transport. RouteFor returns RouteUSBOnly with a clear reason when the zero value is passed, which gives the migration script a loud failure if a wrapper forgot to fill the struct.

type ConnectionReport added in v0.13.0

type ConnectionReport struct {
	StartedAt   time.Time
	CompletedAt time.Time
	// contains filtered or unexported fields
}

ConnectionReport is the structured trail of every step ConnectURL took to bring a Flipper online. It is appended to in-order and never re-shuffled; the JSON shape is the operator-facing contract surfaced in /api/device.

func NewConnectionReport added in v0.13.0

func NewConnectionReport() *ConnectionReport

NewConnectionReport stamps StartedAt and returns an empty report ready for Add. The zero-value ConnectionReport is also usable; this helper just records the start time consistently.

func (*ConnectionReport) Add added in v0.13.0

func (r *ConnectionReport) Add(c Check)

Add appends a check to the report. Safe for concurrent use — although ConnectURL drives steps sequentially today, /api/device may read the report from a different goroutine.

func (*ConnectionReport) Checks added in v0.13.0

func (r *ConnectionReport) Checks() []Check

Checks returns a copy of the recorded checks. The slice is detached so callers can range over it without holding the report lock.

func (*ConnectionReport) Complete added in v0.13.0

func (r *ConnectionReport) Complete()

Complete stamps CompletedAt. Idempotent on the assumption ConnectURL calls it once, but a second call simply overwrites with a fresher timestamp.

func (*ConnectionReport) Duration added in v0.13.0

func (r *ConnectionReport) Duration() time.Duration

Duration returns CompletedAt - StartedAt when both are set, otherwise the time since StartedAt. Zero when the report has not started.

func (*ConnectionReport) FailedCount added in v0.13.0

func (r *ConnectionReport) FailedCount() int

FailedCount returns the number of checks at LevelFail.

func (*ConnectionReport) MarshalJSON added in v0.13.0

func (r *ConnectionReport) MarshalJSON() ([]byte, error)

MarshalJSON produces operator-readable JSON. The rendered shape is the stable contract surfaced via /api/device.connection_report.

func (*ConnectionReport) PassedCount added in v0.13.0

func (r *ConnectionReport) PassedCount() int

PassedCount returns the number of checks at LevelPass.

func (*ConnectionReport) SkippedCount added in v0.13.0

func (r *ConnectionReport) SkippedCount() int

SkippedCount returns the number of checks at LevelSkipped.

func (*ConnectionReport) Summary added in v0.13.0

func (r *ConnectionReport) Summary() string

Summary renders a one-line operator summary of the report's terminal state, e.g. "3 passed, 1 warning". Used by --verbose mode and any caller that wants a banner-friendly digest without iterating Checks.

func (*ConnectionReport) ToJSON added in v0.13.0

func (r *ConnectionReport) ToJSON() any

ToJSON returns the report rendered as an interface{} suitable for embedding in another JSON response. Convenience wrapper around MarshalJSON for /api/device, which assembles a single map[string]any payload.

func (*ConnectionReport) WarningCount added in v0.13.0

func (r *ConnectionReport) WarningCount() int

WarningCount returns the number of checks at LevelWarn.

type Flipper

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

Flipper is the command-layer handle for a connected device. It wraps a transport.Transport and layers prompt framing, capability detection, and hot-plug reconnect on top. All command wrappers (commands.go) go through Exec / ExecLong / StreamCtx / WriteFile — none touch the transport directly.

func Connect

func Connect(ctx context.Context, portName string, baudRate int, timeout time.Duration) (*Flipper, error)

Connect opens a serial port and performs the CLI handshake.

Preserves the pre-Phase-6 signature — the tree has ~5 callers (cmd main, tests in flipper_mock_test.go / primitives_mock_test.go) that pass a path + baud + timeout directly. Internally, Connect builds a serial:// URL and hands it to ConnectURL; new callers that want to pick a non-serial transport should use ConnectURL directly.

func NewForTest added in v0.3.0

func NewForTest(caps Capabilities) *Flipper

NewForTest returns a Flipper with preloaded capabilities and no transport. Intended for unit tests in other packages that need to exercise State-consuming code paths without wiring up a mock serial port. The returned Flipper will never successfully Exec — only capability-derived and transport-less-safe methods should be called.

func (*Flipper) BLEClient added in v0.12.0

func (f *Flipper) BLEClient() *rpc.Client

BLEClient returns the persistent RPC client opened at connect time when the transport is BLE, or nil otherwise. Callers must check IsBLE first; on USB the client is constructed on demand via EnterRPC and is not held on the Flipper handle.

func (*Flipper) BTHCIInfo

func (f *Flipper) BTHCIInfo() (string, error)

BTHCIInfo returns local Bluetooth controller info (chip, firmware version, MAC). Read-only; does not bring up a BLE stack — native BLE operations still require an external devboard. CLI: bt hci_info

func (*Flipper) BackupCreate added in v0.16.0

func (f *Flipper) BackupCreate(path string) (string, error)

BackupCreate writes a tar archive of the Flipper's internal flash (/int) to the given SD-card path. Uses a 5-minute deadline — same budget as UpdateInstall. CLI: update backup <path>

func (*Flipper) BackupRestore added in v0.16.0

func (f *Flipper) BackupRestore(path string) (string, error)

BackupRestore restores a previously created backup archive. Destructive — overwrites current /int contents. The Spec risk band enforces confirmation. CLI: update restore <path>

func (*Flipper) BadUSBRun

func (f *Flipper) BadUSBRun(scriptPath string) (string, error)

BadUSBRun launches a BadUSB script via the app loader. CLI: loader open "Bad USB" <script_path>

func (*Flipper) Capabilities

func (f *Flipper) Capabilities() Capabilities

Capabilities returns the detected firmware capability map. If DetectCapabilities has not yet been called (or failed), returns a zero-valued struct with the conservative defaults (power_info, nfc subshell, no subghz device arg).

func (*Flipper) Close

func (f *Flipper) Close() error

func (*Flipper) ConnectionReport added in v0.13.0

func (f *Flipper) ConnectionReport() *ConnectionReport

ConnectionReport returns the most recently attached ConnectionReport, or nil when none has been set. Callers must treat the returned value as read-only.

func (*Flipper) CryptoDecrypt added in v0.16.0

func (f *Flipper) CryptoDecrypt(slot string, data string) (string, error)

CryptoDecrypt decrypts hex-encoded ciphertext using the key in the named slot. CLI: crypto decrypt <slot> <hex-data>

func (*Flipper) CryptoEncrypt added in v0.16.0

func (f *Flipper) CryptoEncrypt(slot string, data string) (string, error)

CryptoEncrypt encrypts hex-encoded data using the key in the named slot. The slot argument is the string slot identifier used by the firmware. CLI: crypto encrypt <slot> <hex-data>

func (*Flipper) CryptoHasKey added in v0.16.0

func (f *Flipper) CryptoHasKey(slot string) (string, error)

CryptoHasKey reports whether a key is stored in the named slot. CLI: crypto has_key <slot>

func (*Flipper) CryptoStoreKey

func (f *Flipper) CryptoStoreKey(slot int, keyType string, keySize int, keyHex string) (string, error)

CryptoStoreKey stores a key in one of the Flipper's secure-storage slots. Overwrites whatever was in that slot. keyType: "master", "simple", or "encrypted" keySize: 128 or 256 (bits); keyHex must be exactly keySize/8 bytes as hex. CLI: crypto store_key <slot> <keyType> <keySize> <keyHex>

func (*Flipper) DateGet added in v0.16.0

func (f *Flipper) DateGet() (string, error)

DateGet returns the current device time as reported by the RTC. CLI: date

func (*Flipper) DateSet added in v0.16.0

func (f *Flipper) DateSet(unix int64) (string, error)

DateSet synchronises the Flipper's RTC to the given Unix timestamp. The OFW CLI form is: date YYYY-MM-DD HH:MM:SS WD where WD is the ISO-8601 weekday (1=Monday … 7=Sunday). The timestamp is interpreted in UTC — the Flipper firmware stores UTC. CLI: date <YYYY-MM-DD> <HH:MM:SS> <1-7>

func (*Flipper) DesktopIsLocked added in v0.12.0

func (f *Flipper) DesktopIsLocked() (bool, error)

DesktopIsLocked reports whether the device's home screen is currently pin-locked. Returns (true, nil) when locked, (false, nil) when unlocked. Errors are reserved for transport / protocol failures — "unlocked" is a legitimate state the firmware signals via CommandStatus_ERROR on the response Empty, which DesktopIsLocked translates to (false, nil).

USB transports: returns ErrCommandRequiresUSB-wrapped error. The firmware exposes no equivalent CLI verb, so this is structurally a BLE-only operation today.

func (*Flipper) DesktopUnlock added in v0.12.0

func (f *Flipper) DesktopUnlock() error

DesktopUnlock dismisses the pin-lock screen if one is active. Safe to call when the device is already unlocked — the firmware returns success either way.

USB transports: returns ErrCommandRequiresUSB-wrapped error (no equivalent CLI verb on any current firmware fork).

func (*Flipper) DetectApps added in v0.5.0

func (f *Flipper) DetectApps()

DetectApps queries the connected Flipper's FAP (Flipper App Package) list via `loader list` and updates the capability flags for app-presence fields (HasBLESpam, HasSubGHzBruteforcer, HasMouseJackerFAP, etc.).

This is a best-effort probe: if `loader list` fails or times out, the FAP flags retain their fork-typical static defaults from detectCapabilities (set conservatively for Unleashed and optimistically for RogueMaster per firmware-matrix.md §6 Q8). A failed probe never propagates an error — the device_info parse is the authoritative signal for fork/version detection.

Integration note: DetectApps should be called from DetectCapabilities (serial.go) after the device_info parse and initial caps.Store. Since serial.go is managed by the transport layer (not in this engineer's write scope for v0.5), callers may invoke DetectApps() independently after DetectCapabilities() when FAP-presence accuracy is required. The firmware_introspect Handler (internal/tools/firmware.go) calls it when refresh=true.

Name-to-flag mapping (firmware-matrix.md §4.3):

"BLE Spam"           → HasBLESpam
"SubGHz Bruteforcer" → HasSubGHzBruteforcer
"Sub-GHz BF"         → HasSubGHzBruteforcer (alternate name on some forks)
"NRF24 Mousejacker"  → HasMouseJackerFAP
"Seader"             → HasSeaderFAP
"PicoPass"           → HasPicopassFAP
"NFC Magic"          → HasNFCMagicFAP
"MFKey"              → HasMFKeyFAP   (Unleashed in-tree path)
"MFKey32"            → HasMFKeyFAP   (RM/Momentum external FAP)
"Mifare Nested"      → HasMifareNestedFAP

func (*Flipper) DetectCapabilities

func (f *Flipper) DetectCapabilities() (Capabilities, error)

DetectCapabilities queries device_info and caches the parsed capability map. Best-effort: on error, leaves prior caps (or defaults) in place.

func (*Flipper) DeviceInfo

func (f *Flipper) DeviceInfo() (string, error)

DeviceInfo returns device information.

CLI transport: `device_info` text command, response parsed by the caller. RPC transport (BLE — text CLI is not available there): a SystemDeviceInfoRequest streamed via the persistent rpc.Client; the (key, value) pairs are reformatted as the same `key: value\n` block the CLI emits so downstream parsing in DeviceInfoMap / parseKVBlock is transport-agnostic.

Migrated to the compat-layer dispatch (Phase A). The viaCLI/viaRPC closures wrap the same code paths the old inline `if f.IsBLE()` branch used, so the public behaviour is identical.

func (*Flipper) DeviceInfoMap added in v0.2.0

func (f *Flipper) DeviceInfoMap() (map[string]string, error)

DeviceInfoMap runs device_info and parses the output into a flat key→value map. Blank lines and lines missing a colon are skipped. The full surface is preserved — callers wanting one field (e.g. the dolphin name) should look up the key directly. Numeric-looking values stay as strings so consumers decide the typing (some fields like storage_sdcard_totalSpace are huge int64s that don't survive JavaScript number coercion without care).

func (*Flipper) EnterRPC added in v0.9.2

func (f *Flipper) EnterRPC(ctx context.Context) (*rpc.Client, func(), error)

EnterRPC transitions the Flipper into RPC mode and returns a typed client along with a release closure.

Semantics on USB CDC:

  1. Acquires f.mu for the entire RPC session.
  2. Attempts reconnect if the transport is marked disconnected.
  3. Drains any pending CLI output from the buffer.
  4. Constructs rpc.NewClient, calls Open. On error: unlocks and returns.
  5. Sets rpcMode to true.
  6. Returns the client and a release closure that: clears rpcMode, closes the client, re-handshakes the CLI prompt, and unlocks the mutex.

Semantics on BLE:

The Flipper firmware has no text CLI on its BLE Serial endpoint — RPC is permanent for the lifetime of the connection. ConnectURL already opened the persistent client (f.bleClient) at handshake time and latched rpcMode=true, so EnterRPC returns that client with a no-op release closure. The caller's release() call is therefore safe and idempotent on both transports, but on BLE no CLI re-handshake runs (there is no CLI to return to).

The release closure is safe to call exactly once; subsequent calls are no-ops.

func (*Flipper) Exec

func (f *Flipper) Exec(command string) (string, error)

Exec sends a CLI command and returns the full response. Preserved for backward compatibility; new callers should use ExecCtx so cancellation propagates through to the reconnect path.

func (*Flipper) ExecCtx

func (f *Flipper) ExecCtx(ctx context.Context, command string) (string, error)

ExecCtx is the context-aware variant of Exec. The ctx is honoured during reconnect polling and during the 10 s per-command read deadline.

func (*Flipper) ExecLong

func (f *Flipper) ExecLong(command string, timeout time.Duration) (string, error)

ExecLong sends a command that may take a while (captures, brute force, etc). Preserved for backward compatibility; new callers should use ExecLongCtx so cancellation propagates through to the reconnect path.

func (*Flipper) ExecLongCtx

func (f *Flipper) ExecLongCtx(ctx context.Context, command string, timeout time.Duration) (string, error)

ExecLongCtx is the context-aware variant of ExecLong. A non-positive timeout is floored to 60 s so a caller passing a zero duration still gets a sane per-command deadline.

func (*Flipper) FactoryReset added in v0.16.0

func (f *Flipper) FactoryReset() (string, error)

FactoryReset schedules a factory reset that takes effect on the next reboot. Destructive — all user data and settings are erased. The Spec risk band enforces confirmation; this method is a plain wire wrapper. CLI: factory_reset

func (*Flipper) GPIORead

func (f *Flipper) GPIORead(pin string) (string, error)

GPIORead reads the current value of a GPIO pin.

CLI transport: `gpio read <pin>` text command. Output format from firmware is "Pin <name> = <0|1>" (with mild fork-to-fork variation). RPC transport (BLE): gpio_read_pin streamed via the persistent rpc.Client. The numeric value is reformatted as the same single-line "Pin <name> = <0|1>\n" string the CLI emits so downstream parsers (workflows.gpioValueFromOutput) work without knowing which transport produced the output. CLI: gpio read <pin>

func (*Flipper) GPIOSet

func (f *Flipper) GPIOSet(pin string, value int) (string, error)

GPIOSet sets a GPIO pin to a value.

CLI transport: `gpio set <pin> <value>` text command. RPC transport (BLE — text CLI is not available there): selected on the value:

  • 0 or 1: gpio_write_pin (output mode + drive level).
  • anything else: gpio_set_pin_mode with mode=INPUT, treated as "switch this pin to read mode before a subsequent gpio_read". The CLI has no in-band equivalent for this — it's a transport- specific hook for callers who need to flip a pin to input via RPC before reading.

CLI emits no output on success, so the RPC branch returns an empty string to match. CLI: gpio set <pin> <value>

func (*Flipper) GuiScreenStream added in v0.16.0

func (f *Flipper) GuiScreenStream(duration time.Duration) (string, error)

GuiScreenStream collects display frames from the Flipper for the given duration via the Protobuf RPC screen-stream path and returns them as base64-encoded PBM (P4 binary, 128×64) frames, one per line.

RPC is available only when the underlying transport is BLE (f.bleClient is non-nil). On USB the web-UI mirror owns the screen-stream lifecycle via EnterRPC; calling this method on USB returns a descriptive error so the caller can surface the correct user prompt.

RPC: Gui.StartScreenStreamRequest → collect frames → StopScreenStreamRequest

func (*Flipper) I2CScan

func (f *Flipper) I2CScan() (string, error)

I2CScan scans the I²C bus for connected devices. Tries the built-in `i2c scan` CLI first (available on Xtreme and forks that ship it); if the firmware rejects the command, falls back to launching the "I2C Scanner" FAP via loader_open. Buzzes on success. CLI: i2c scan → loader open "I2C Scanner"

func (*Flipper) IButtonEmulate

func (f *Flipper) IButtonEmulate(protocol string, hexData string, duration time.Duration) (string, error)

IButtonEmulate emulates an iButton key for the given duration. The firmware command is streaming (prints "Emulating key ..." then waits for Ctrl+C), so the wrapper uses ExecLong with streaming semantics. Supported protocols: Dallas, Cyfral, Metakom. A reader must be in contact with the iButton contacts during the emulation window. CLI: ikey emulate <protocol> <hex_data>

func (*Flipper) IButtonEmulateCtx added in v0.62.0

func (f *Flipper) IButtonEmulateCtx(ctx context.Context, protocol string, hexData string, duration time.Duration) (string, error)

IButtonEmulateCtx is the context-aware variant of IButtonEmulate.

func (*Flipper) IButtonRead

func (f *Flipper) IButtonRead(timeout time.Duration) (string, error)

IButtonRead reads an iButton key. CLI: ikey read

func (*Flipper) IButtonReadCtx added in v0.62.0

func (f *Flipper) IButtonReadCtx(ctx context.Context, timeout time.Duration) (string, error)

IButtonReadCtx is the context-aware variant of IButtonRead. Preserves the 120 ms success-buzz wrapper.

func (*Flipper) IButtonWrite

func (f *Flipper) IButtonWrite(hexData string) (string, error)

IButtonWrite writes an iButton key (Dallas only). CLI: ikey write Dallas <hex_data>

func (*Flipper) IRDecodeFile

func (f *Flipper) IRDecodeFile(path string) (string, error)

IRDecodeFile parses a saved .ir file and returns the decoded entries. Read-only and local to the SD card — no transmit. CLI: ir decode <path>

func (*Flipper) IRRx

func (f *Flipper) IRRx(timeout time.Duration) (string, error)

IRRx listens for an incoming infrared signal. CLI: ir rx

func (*Flipper) IRRxCtx added in v0.62.0

func (f *Flipper) IRRxCtx(ctx context.Context, timeout time.Duration) (string, error)

IRRxCtx is the context-aware variant of IRRx. Preserves the 120 ms success-buzz wrapper.

func (*Flipper) IRRxRaw

func (f *Flipper) IRRxRaw(timeout time.Duration) (string, error)

IRRxRaw listens for a raw infrared signal. CLI: ir rx raw

func (*Flipper) IRRxRawCtx added in v0.62.0

func (f *Flipper) IRRxRawCtx(ctx context.Context, timeout time.Duration) (string, error)

IRRxRawCtx is the context-aware variant of IRRxRaw.

func (*Flipper) IRRxRawStream added in v0.57.0

func (f *Flipper) IRRxRawStream(ctx context.Context, timeout time.Duration, onLine func(line string) (stop bool)) (string, error)

IRRxRawStream is the line-streaming variant of IRRxRaw. Each pulse line emitted while `ir rx raw` is running lands at onLine; stop=true ends the capture early. No success buzz — the raw stream typically runs to completion via a duration budget rather than a discrete "captured" moment. CLI: ir rx raw

func (*Flipper) IRRxStream added in v0.57.0

func (f *Flipper) IRRxStream(ctx context.Context, timeout time.Duration, onLine func(line string) (stop bool)) (string, error)

IRRxStream is the line-streaming variant of IRRx. Each line emitted by `ir rx` (typically the decoded signal once a remote button is pressed) lands at onLine; stop=true ends the capture. Wraps the streaming call in withSuccessBuzz so a successful capture still triggers the 120 ms vibration on completion — operators rely on the buzz to confirm the IR signal was caught without looking at the screen. CLI: ir rx

func (*Flipper) IRTxParsed

func (f *Flipper) IRTxParsed(protocol string, address string, command string) (string, error)

IRTxParsed transmits a decoded infrared signal. CLI: ir tx <protocol> <address_hex> <command_hex>

func (*Flipper) IRTxRaw

func (f *Flipper) IRTxRaw(frequency uint32, dutyCycle float64, data string) (string, error)

IRTxRaw transmits a raw infrared signal. CLI: ir tx RAW F:<freq> DC:<duty_cycle> <data>

func (*Flipper) IRUniversal

func (f *Flipper) IRUniversal(remoteName string, signalName string) (string, error)

IRUniversal brute-forces every variant of the named signal category across all manufacturer codes in the specified universal remote library. It is NOT a single-shot transmission — the firmware sweeps all frames of that signal type (e.g. "Power" fires every known power-off frame). CLI: ir universal <remote_name> <signal_name>

func (*Flipper) IRUniversalList

func (f *Flipper) IRUniversalList(library string) (string, error)

IRUniversalList lists entries in a universal remote library file so the agent can see which buttons are available before calling IRUniversal. CLI: ir universal list <library>

func (*Flipper) InputSend

func (f *Flipper) InputSend(button string, eventType string) (string, error)

InputSend sends a synthetic button input event.

CLI transport: `input send <button> <type>`. RPC transport (BLE): a gui_send_input_event_request dispatched via the persistent rpc.Client. The RPC produces no response body, so on success both transports return an empty string — preserving the (string, error) contract.

CLI: input send <button> <type> button: up, down, left, right, ok, back eventType: press, release, short, long

func (*Flipper) InvalidateState added in v0.3.0

func (f *Flipper) InvalidateState()

InvalidateState drops the cached snapshot so the next State() call forces a re-query. Intended for use after operations that might change the observable state materially (firmware updates, power reboots, storage format), not for every write — the 2 s TTL already covers ordinary drift.

func (*Flipper) IsBLE added in v0.12.0

func (f *Flipper) IsBLE() bool

IsBLE reports whether the underlying transport is BLE. BLE transports can only speak Protobuf RPC (not text CLI), so command dispatchers branch on this to either route through the persistent RPC client (f.bleClient) or — for commands without an RPC equivalent — return ErrCommandRequiresUSB.

func (*Flipper) IsSuspended added in v0.10.0

func (f *Flipper) IsSuspended() bool

IsSuspended reports whether Suspend has been called.

func (*Flipper) JSRun

func (f *Flipper) JSRun(path string, duration time.Duration) (string, error)

JSRun executes a saved JavaScript file on the Flipper's JS runtime. Fork-gated: only the Xtreme, Momentum, and RogueMaster forks ship a JS engine. On stock the call returns a friendly-fork error rather than issuing a no-op CLI command that hangs. CLI: js <path>

func (*Flipper) LED

func (f *Flipper) LED(channel string, value int) (string, error)

LED sets a single LED channel to a brightness value (0-255). CLI: led <r|g|b|bl> <0-255> channel: "r" (red), "g" (green), "b" (blue), "bl" (backlight)

func (*Flipper) LaunchBridge added in v0.12.0

func (f *Flipper) LaunchBridge(ctx context.Context, command string) (string, error)

LaunchBridge launches the Flipper's USB-UART Bridge and returns the firmware's response text (empty on BLE).

USB transport: sends the literal command string (default `loader open "USB-UART Bridge"`) — preserved for older Flipper firmware builds where that name was a registered launchable. On modern Momentum the bridge cannot be entered while USB CDC is locked for CLI/RPC anyway (gpio_scene_start.c:109), so this path will surface "Application not found" via classifyBridgeRejection.

BLE transport: ignores the command string and runs the canonical Momentum-compatible sequence directly:

  1. app_start_request(name="GPIO") — opens the GPIO app (the ContainingApp for the USB-UART Bridge scene per applications/main/gpio/gpio_scene_start.c).
  2. brief settle so the scene-manager renders GpioSceneStart with "USB-UART Bridge" highlighted at index 0 (the default menu item).
  3. gui_send_input_event(OK, SHORT) — fires GpioStartEventUsbUart; because USB CDC isn't locked on a BLE-only session, the firmware navigates to GpioSceneUsbUart whose on_enter unconditionally calls usb_uart_enable() with the default config (vcp_ch=0, uart_ch=0, baudrate default — Marauder's 115200 baud).

This path is the only one that actually starts the bridge on Momentum: the loader-open shortcut "USB-UART Bridge" was never a registered application, only a menu label. The firmware's gpio_scene_usb_uart.c on_enter is what actually flips USB CDC into UART pass-through mode.

func (*Flipper) LoaderClose

func (f *Flipper) LoaderClose() (string, error)

LoaderClose closes the currently running application.

CLI transport: `loader close`. RPC transport (BLE): an AppExitRequest dispatched via the persistent rpc.Client. Both paths return an empty success string on the happy path; non-OK firmware status (e.g. ERROR_APP_NOT_RUNNING when no app is open) surfaces via the wrapped error.

CLI: loader close

func (*Flipper) LoaderInfo

func (f *Flipper) LoaderInfo() (string, error)

LoaderInfo returns metadata about the currently running app (name, flags). CLI: loader info

USB-only: the Flipper firmware exposes no RPC verb that returns the currently-running app's metadata. (app_lock_status_request only reports a boolean lock state.) On BLE this returns ErrCommandRequiresUSB.

func (*Flipper) LoaderList

func (f *Flipper) LoaderList() (string, error)

LoaderList lists all available applications. CLI: loader list

USB-only: the Flipper firmware exposes no RPC verb for enumerating the FAP registry. On BLE this returns ErrCommandRequiresUSB so callers can surface a clear "connect via USB" message instead of an opaque transport-mode error from Exec.

func (*Flipper) LoaderListParsed

func (f *Flipper) LoaderListParsed() (LoaderApps, error)

LoaderListParsed returns the app/settings lists as structured data so the agent can decide whether a target app is installed before calling loader_open. Returned fields are empty slices (not nil) when a section is missing from the output.

USB-only: depends on `loader list`, which has no firmware RPC verb. On BLE this returns ErrCommandRequiresUSB directly so callers see a clear error from the parser layer rather than a transport-level one.

func (*Flipper) LoaderMFKey

func (f *Flipper) LoaderMFKey() (string, error)

LoaderMFKey launches the "MFKey32" FAP for MIFARE Classic key recovery.

func (*Flipper) LoaderMifareNested

func (f *Flipper) LoaderMifareNested() (string, error)

LoaderMifareNested launches the "Mifare Nested" FAP (nested attack recovery).

func (*Flipper) LoaderNFCMagic

func (f *Flipper) LoaderNFCMagic() (string, error)

LoaderNFCMagic launches the "NFC Magic" FAP used to write MIFARE magic tags.

func (*Flipper) LoaderNRF24Mousejacker

func (f *Flipper) LoaderNRF24Mousejacker() (string, error)

LoaderNRF24Mousejacker launches the "NRF24 Mousejacker" FAP. Requires an external NRF24L01+ devboard wired to the Flipper's GPIO header. The FAP takes over the screen and reads target addresses from /ext/apps_data/nrfsniff/addresses.txt plus DuckyScript payloads from /ext/mousejacker/*.txt. Momentum firmware exposes no nrf24 CLI, so all run-time interaction happens through the FAP UI (navigate via input_send; back-button to exit).

func (*Flipper) LoaderNRF24Sniffer added in v0.3.1

func (f *Flipper) LoaderNRF24Sniffer() (string, error)

LoaderNRF24Sniffer launches the companion "NRF24 Sniffer" FAP. The FAP scans 2.4 GHz bands for active wireless-peripheral addresses and writes hits to /ext/apps_data/nrfsniff/addresses.txt (comma-separated address,rate lines). Prerequisite for any Mousejack flow — the FAP UI is operator-driven; there is no CLI equivalent.

func (*Flipper) LoaderOpen

func (f *Flipper) LoaderOpen(appName string, args string) (string, error)

LoaderOpen opens a Flipper application by name with optional arguments. The app name is always double-quoted so multi-word names (e.g. "Bad USB", "Sub-GHz BF") are parsed as a single token by the firmware's args_read_probably_quoted_string_and_trim.

CLI transport: `loader open "<app_name>" [args]`. RPC transport (BLE): an AppStartRequest dispatched via the persistent rpc.Client. Both paths return an empty success string on success — `loader open` produces no CLI output when the launch succeeds, and the RPC ack carries no body either, so the (string, error) contract is identical across transports.

CLI: loader open "<app_name>" [args]

func (*Flipper) LoaderPicopass

func (f *Flipper) LoaderPicopass() (string, error)

LoaderPicopass launches the "PicoPass" FAP (HID iClass/Picopass tooling).

func (*Flipper) LoaderProtoView

func (f *Flipper) LoaderProtoView() (string, error)

LoaderProtoView launches the "ProtoView" FAP for raw Sub-GHz signal visualisation.

func (*Flipper) LoaderSPIMemManager

func (f *Flipper) LoaderSPIMemManager() (string, error)

LoaderSPIMemManager launches the "SPI Mem Manager" FAP for reading and writing SPI flash chips via the GPIO header.

func (*Flipper) LoaderSeader

func (f *Flipper) LoaderSeader() (string, error)

LoaderSeader launches the "SEADER" FAP (HID iClass SE advanced tooling).

func (*Flipper) LoaderSignal

func (f *Flipper) LoaderSignal(signal int, argHex string) (string, error)

LoaderSignal sends a numeric signal to the currently running app with an optional hex argument (many apps document custom opcodes that consume argHex). Pass "" to omit the argument. CLI: loader signal <n> [<hex>]

USB-only: the firmware exposes app_button_press / app_button_release / app_data_exchange RPC verbs but no generic "send numeric signal" equivalent that matches the CLI's free-form (signal, hex) shape, so this remains CLI-only. On BLE returns ErrCommandRequiresUSB.

func (*Flipper) LoaderSignalGenerator

func (f *Flipper) LoaderSignalGenerator() (string, error)

LoaderSignalGenerator launches the "Signal Generator" FAP.

func (*Flipper) LoaderSpectrumAnalyzer

func (f *Flipper) LoaderSpectrumAnalyzer() (string, error)

LoaderSpectrumAnalyzer launches the "Spectrum Analyzer" FAP.

func (*Flipper) LoaderSubGHzBruteforcer

func (f *Flipper) LoaderSubGHzBruteforcer() (string, error)

LoaderSubGHzBruteforcer launches the "Sub-GHz BF" brute-force FAP. Destructive by design — runs enormous code sweeps.

func (*Flipper) LoaderSubGHzPlaylist

func (f *Flipper) LoaderSubGHzPlaylist() (string, error)

LoaderSubGHzPlaylist launches the "Playlist" FAP that replays a sequence of .sub captures.

func (*Flipper) LoaderT5577MultiWriter

func (f *Flipper) LoaderT5577MultiWriter() (string, error)

LoaderT5577MultiWriter launches the "T5577 Multiwriter" FAP for batch writing of 125 kHz T5577 tags.

func (*Flipper) LoaderUARTTerminal

func (f *Flipper) LoaderUARTTerminal() (string, error)

LoaderUARTTerminal launches the "UART Terminal" FAP for serial comms on the Flipper's GPIO header.

func (*Flipper) LoaderUnitemp

func (f *Flipper) LoaderUnitemp() (string, error)

LoaderUnitemp launches the "Unitemp" FAP for reading external temperature sensors over the GPIO header.

func (*Flipper) LogStream

func (f *Flipper) LogStream(duration time.Duration, level string) (string, error)

LogStream opens a live log stream from the Flipper for the supplied duration, returning the captured text. Read-only; the Flipper keeps running after the stream ends. level filters the minimum severity — empty string means the firmware's default; recognised values are "default", "error", "warn", "info", "debug", "trace". CLI: log [<level>]

func (*Flipper) LogStreamCtx added in v0.62.0

func (f *Flipper) LogStreamCtx(ctx context.Context, duration time.Duration, level string) (string, error)

LogStreamCtx is the context-aware variant of LogStream.

func (*Flipper) LogStreamLines added in v0.57.0

func (f *Flipper) LogStreamLines(ctx context.Context, duration time.Duration, level string, onLine func(line string) (stop bool)) (string, error)

LogStreamLines is the line-streaming variant of LogStream. Each log line emitted by firmware is delivered to onLine as it arrives; returning stop=true ends the capture early.

Empty level uses the firmware default. Recognised values match LogStream. CLI: log [<level>]

func (*Flipper) NFCAPDU

func (f *Flipper) NFCAPDU(apduHex string, timeout time.Duration) (string, error)

NFCAPDU sends an APDU command to a contactless smart card (ISO7816) via the nfc subshell. Fork-gated. Subshell verb (stock/Unleashed): apdu <hex> Subshell verb (Momentum): apdu -d <hex>

func (*Flipper) NFCDetect

func (f *Flipper) NFCDetect(timeout time.Duration) (string, error)

NFCDetect enters the NFC subshell, runs the scanner subcommand, and exits. The subshell prompt is "[nfc]>: ". Not all firmware forks expose an NFC CLI — Xtreme XFW ships an empty `nfc` subsystem — so we surface a clear error rather than hanging on a non-responsive subcommand.

func (*Flipper) NFCDumpProtocol

func (f *Flipper) NFCDumpProtocol(protocol string, timeout time.Duration) (string, error)

NFCDumpProtocol dumps tag contents for a specific NFC protocol via the nfc subshell. Callers pass the canonical friendly name ("Mifare_Classic", "Mifare_Ultralight", "Mifare_Plus", "FeliCa"); the wrapper translates to the firmware's accepted token.

Subshell verb (stock/Unleashed): dump <protocol> Subshell verb (Momentum): dump -p <token> (token is mfc/mfu/mfp/felica)

Pass an empty string to skip the protocol arg entirely — Momentum's `dump` (no -p) auto-detects the protocol and writes a .nfc file in /ext/nfc/dump-YYYYMMDD-HHMMSS.nfc. That auto-save shape is the real "scan and save" workflow on Momentum and is preferred when the caller doesn't already know the protocol.

func (*Flipper) NFCEmulate

func (f *Flipper) NFCEmulate(filePath string) (string, error)

NFCEmulate launches the NFC emulation app via the loader, waits for the app to exit, then verifies the loader is free before returning. This ensures subsequent Exec calls are not blocked by "application is open" errors. Returns an error if the loader does not free within ~1 second. CLI: loader open NFC <file_path> → loader close → poll loader info

func (*Flipper) NFCMFURead

func (f *Flipper) NFCMFURead(page int, timeout time.Duration) (string, error)

NFCMFURead reads a single MIFARE Ultralight page/block. Fork-gated. Subshell verb (stock/Unleashed): mfu rdbl <page> Subshell verb (Momentum): mfu rdbl -b <page>

func (*Flipper) NFCMFUWrite

func (f *Flipper) NFCMFUWrite(page int, hexData string, timeout time.Duration) (string, error)

NFCMFUWrite writes 4 bytes of hex data to a MIFARE Ultralight page/block. Destructive — overwrites whatever the tag currently holds. Fork-gated. Subshell verb (stock): mfu wrbl <page> <hex> Subshell verb (Momentum/Unleashed): mfu wrbl -b <page> -d <hex>

func (*Flipper) NFCRawFrame

func (f *Flipper) NFCRawFrame(hexData string, timeout time.Duration) (string, error)

NFCRawFrame sends a raw ISO14443 frame to a tag via the nfc subshell and returns the tag's response. Fork-gated: not available on Xtreme (no NFC CLI subshell). Subshell verb (stock/Unleashed): raw <hex> Subshell verb (Momentum): raw -p iso14a -d <hex> Momentum's NFC CLI uses a flag-based parser; positional args are rejected. Protocol defaults to iso14a (ISO 14443-3A), the most common NFC protocol.

func (*Flipper) NFCSubcommand

func (f *Flipper) NFCSubcommand(subcommand string, timeout time.Duration) (string, error)

NFCSubcommand enters the NFC subshell, sends an arbitrary subcommand, and exits. Valid subcommands include: scanner, emulate, dump, field, raw, apdu, mfu. Not available on firmware forks without an NFC CLI subshell (e.g., Xtreme).

func (*Flipper) OneWireSearch

func (f *Flipper) OneWireSearch(duration time.Duration) (string, error)

OneWireSearch enumerates devices on the 1-Wire bus. Read-only; buzzes on success so the user knows something was found. CLI: onewire search

func (*Flipper) OneWireSearchCtx added in v0.62.0

func (f *Flipper) OneWireSearchCtx(ctx context.Context, duration time.Duration) (string, error)

OneWireSearchCtx is the context-aware variant of OneWireSearch. Preserves the 120 ms success-buzz wrapper.

func (*Flipper) Power3V3 added in v0.16.0

func (f *Flipper) Power3V3(enable bool) (string, error)

Power3V3 enables (enable=true) or disables (enable=false) the 3.3 V GPIO supply rail on the Flipper's external header. CLI: power 3v3 1 or power 3v3 0

func (*Flipper) Power5V added in v0.16.0

func (f *Flipper) Power5V(enable bool) (string, error)

Power5V enables (enable=true) or disables (enable=false) the 5 V GPIO supply rail on the Flipper's external header. CLI: power 5v 1 or power 5v 0

func (*Flipper) PowerInfo

func (f *Flipper) PowerInfo() (string, error)

PowerInfo returns power/battery information. The CLI spelling differs by fork: Xtreme uses `info power`; stock/Unleashed/RogueMaster use `power_info`. The capability map stores the right verb at connect time. On BLE the firmware exposes a single SystemPowerInfoRequest regardless of fork — fork-specific CLI spelling is not relevant — and the (key, value) pairs are reformatted to the same `key: value` block the CLI emits.

Migrated to the compat-layer dispatch (Phase A). Fork-specific CLI verb selection still happens inside the viaCLI closure — the compat layer doesn't know about per-fork verb spellings, only about transport-level routing.

func (*Flipper) PowerInfoMap added in v0.2.0

func (f *Flipper) PowerInfoMap() (map[string]string, error)

PowerInfoMap runs the fork-appropriate power_info command and returns the parsed key→value map (charge_level, battery_voltage, capacity_*, etc.).

Separate from DeviceInfoMap because none of the forks expose power fields via device_info: Xtreme and Momentum serve them via `info power` (dot-separated keys — normalised to underscore here), stock/Unleashed/RogueMaster via the legacy `power_info` (already underscore-separated).

func (*Flipper) PowerOff added in v0.16.0

func (f *Flipper) PowerOff() (string, error)

PowerOff powers off the Flipper. The device will not respond after this until the user presses the power button. The Spec risk band enforces confirmation. CLI: power off

func (*Flipper) PowerRebootDFU

func (f *Flipper) PowerRebootDFU() (string, error)

PowerRebootDFU reboots the Flipper into the STM32 DFU bootloader. Leaves the device without a running firmware until a host reflashes or the user power-cycles — recovery is physical. Guarded as Critical at the risk layer.

CLI transport: `power reboot2dfu` text command. RPC transport (BLE): SystemRebootRequest with mode=DFU streamed via the persistent rpc.Client. As with Reboot, the firmware does not emit a response — the link drops immediately. Both branches return an empty string on success. CLI: power reboot2dfu

func (*Flipper) PropertyGet added in v0.12.0

func (f *Flipper) PropertyGet(key string) ([]struct{ Key, Value string }, error)

PropertyGet retrieves the (key, value) pairs the firmware exposes under the supplied key prefix. An empty prefix returns every exposed property. The returned slice preserves the firmware's emission order — useful for callers that want to keep keys grouped by namespace (e.g. "devinfo.").

USB transports: returns ErrCommandRequiresUSB-wrapped error.

func (*Flipper) RFIDEmulate

func (f *Flipper) RFIDEmulate(protocol string, data string, duration time.Duration) (string, error)

RFIDEmulate emulates an RFID tag for the given duration. The firmware command is streaming (prints "Emulating RFID..." then waits for Ctrl+C), so the wrapper uses ExecLong with streaming semantics: on deadline the lower layer sends \x03 to abort and returns the accumulated output with nil error. Pass a reasonable duration (typical: 2–10 s) — a reader needs to be pointed at the Flipper during the window. CLI: rfid emulate <protocol> <hex_data>

func (*Flipper) RFIDEmulateCtx added in v0.62.0

func (f *Flipper) RFIDEmulateCtx(ctx context.Context, protocol string, data string, duration time.Duration) (string, error)

RFIDEmulateCtx is the context-aware variant of RFIDEmulate.

func (*Flipper) RFIDRawAnalyze

func (f *Flipper) RFIDRawAnalyze(filePath string) (string, error)

RFIDRawAnalyze post-processes a raw LF capture, attempting to decode the contained protocol. Pure local analysis — no RF activity. CLI: rfid raw_analyze <file_path>

func (*Flipper) RFIDRawEmulate

func (f *Flipper) RFIDRawEmulate(filePath string, duration time.Duration) (string, error)

RFIDRawEmulate replays a raw 125 kHz capture against a reader. Active transmission — use with authorisation. CLI: rfid raw_emulate <file_path>

func (*Flipper) RFIDRawEmulateCtx added in v0.62.0

func (f *Flipper) RFIDRawEmulateCtx(ctx context.Context, filePath string, duration time.Duration) (string, error)

RFIDRawEmulateCtx is the context-aware variant of RFIDRawEmulate.

func (*Flipper) RFIDRawRead

func (f *Flipper) RFIDRawRead(mode, filePath string, duration time.Duration) (string, error)

RFIDRawRead performs a raw 125 kHz capture to a file for later analysis. Mode is "ask" or "psk" (pass "" for auto); filePath is where the raw capture is written. Read-only from the RF perspective — no transmit. CLI: rfid raw_read [<mode>] <file_path>

Momentum firmware (confirmed via Next-Flip/Momentum-Firmware lfrfid_cli.c) uses the same `rfid raw_read` verb with the same arg shape as stock. Firmware-side arg errors are reported as a usage banner (no error code), which callers would otherwise see as a silent success. Output-scanning converts the banner to an explicit error.

func (*Flipper) RFIDRead

func (f *Flipper) RFIDRead(ctx context.Context, mode string, timeout time.Duration) (string, error)

RFIDRead reads a 125kHz RFID tag. mode is optional (normal, indala, ask, psk); pass "" for auto. Unlike a plain ExecLong, this streams the Flipper's output and returns as soon as a tag is decoded — so a successful scan takes ~1-2 seconds instead of waiting out the full timeout. If nothing appears within timeout, a helpful "no tag detected" error is returned. CLI: rfid read [mode]

func (*Flipper) RFIDWrite

func (f *Flipper) RFIDWrite(protocol string, data string) (string, error)

RFIDWrite writes data to an RFID tag. CLI: rfid write <protocol> <hex_data>

func (*Flipper) RawCLI

func (f *Flipper) RawCLI(command string) (string, error)

RawCLI sends an arbitrary CLI command string to the Flipper and returns its output. Escape hatch for firmware features we haven't wrapped, or for debugging. Callers MUST risk-gate this — it can reboot the device, write arbitrary files, jam frequencies, etc. The 30s timeout is a safety cap; for long-running commands use ExecLong directly from a wrapper.

func (*Flipper) Reboot

func (f *Flipper) Reboot() (string, error)

Reboot reboots the Flipper Zero.

CLI transport: `power reboot` text command. The firmware reboots immediately so Exec returns whatever bytes (if any) the CLI emitted before the device dropped off the bus. RPC transport (BLE — text CLI is not available there): SystemRebootRequest with mode=OS streamed via the persistent rpc.Client. The firmware does not emit a response for reboot requests; the BLE link drops as soon as the bytes are flushed. Both branches return an empty string on success to match the CLI's typical short/empty output. CLI: power reboot

Migrated to the compat-layer dispatch (Phase A).

func (*Flipper) Reconnect

func (f *Flipper) Reconnect(ctx context.Context) error

Reconnect forces a fresh reconnect cycle: closes the current transport, asks the transport to rescan + reopen, re-handshakes, and re-detects capabilities. Useful from a /reconnect slash command when the user has replugged and auto-detect didn't fire (e.g., the agent was idle and no IO error surfaced the drop).

func (*Flipper) SetConnectionReport added in v0.13.0

func (f *Flipper) SetConnectionReport(r *ConnectionReport)

SetConnectionReport stashes a ConnectionReport on the Flipper handle so /api/device and --verbose can read it after ConnectURL returns.

Stored via atomic.Pointer because /api/device may read concurrently with a future Reconnect path that wants to refresh the report.

func (*Flipper) SetExecTimeout added in v0.2.5

func (f *Flipper) SetExecTimeout(d time.Duration)

SetExecTimeout overrides the per-command read deadline for ExecCtx. A zero or negative value restores the 10 s default.

func (*Flipper) SetLED

func (f *Flipper) SetLED(color string, brightness int) error

SetLED sets the RGB LED to the given color + brightness (0-255). Best-effort — errors are returned but most callers ignore them. The REPL drives this at turn scope so the LED stays steady for the whole prompt, rather than flickering on/off per scan. Color is one of "r", "g", "b" (or "bl" for backlight).

func (*Flipper) SetMaxAccumBytes

func (f *Flipper) SetMaxAccumBytes(n int)

SetMaxAccumBytes overrides the per-operation read-buffer cap. Values <= 0 reset to the default (8 MiB). The cap applies to the next Exec/Stream call.

func (*Flipper) SetPipeline added in v0.13.0

func (f *Flipper) SetPipeline(p PipelineProfile)

SetPipeline swaps the active profile bundle on f. The swap is atomic (atomic.Pointer) so an in-flight ExecCtx that read the old pipeline reference completes against the old values; the next ExecCtx sees the new bundle. Empty / unknown names resolve to ProfileBalanced via ProfileSettings.

func (*Flipper) SetPipelineBundle added in v0.13.0

func (f *Flipper) SetPipelineBundle(p Pipeline)

SetPipelineBundle is a Pipeline-typed counterpart to SetPipeline used by callers (and tests) that have already resolved a Pipeline by hand — e.g. tests asserting a specific timeout, or a future auto-tuner emitting bundles that don't correspond to one of the three named profiles. A zero-valued Pipeline is rejected with a warn log (every timeout would be 0, so ExecCtx / WriteFileCtx would fire context.DeadlineExceeded immediately on every call); pass ProfileSettings(ProfileBalanced) to reset.

The reject path was promised by the docstring but pre-this-fix not enforced — a caller passing `Pipeline{}` silently wedged the agent's CLI dispatch on the next command.

func (*Flipper) SetReconnectCallback

func (f *Flipper) SetReconnectCallback(cb func(phase, message string))

SetReconnectCallback registers a function invoked at each reconnect phase ("start", "success", "fail"). Called while f.mu is held, so keep the handler quick — typically a single stderr write via the output mutex.

func (*Flipper) SetWriteFileTimeout added in v0.2.5

func (f *Flipper) SetWriteFileTimeout(d time.Duration)

SetWriteFileTimeout overrides the post-payload read deadline for WriteFileCtx. A zero or negative value restores the 10 s default.

func (*Flipper) State added in v0.3.0

func (f *Flipper) State(ctx context.Context) (State, error)

State returns the freshest State snapshot that satisfies the cache TTL. On a cache miss it re-queries capabilities + power_info with the caller's context; partial results (capabilities only, no power data) are still cached and returned because they carry useful framing for the agent. A genuinely empty cache is returned with Connected=false and an error — the caller treats that as "skip injection this turn".

func (*Flipper) StorageCopy

func (f *Flipper) StorageCopy(src, dst string) (string, error)

StorageCopy copies a file or directory on the Flipper SD card. CLI: storage copy <src> <dst>

USB-only: there is no storage_copy_request RPC verb on any firmware fork (the protobuf surface lacks it — see flipperdevices/flipperzero- protobuf storage.proto). On BLE we surface a descriptive error rather than hang; agent callers gate on errors.Is(err, ErrCommandRequiresUSB) to suggest the operator attach the Flipper via USB.

func (*Flipper) StorageExtract added in v0.16.0

func (f *Flipper) StorageExtract(archive, outdir string) (string, error)

StorageExtract unpacks a tar archive on the Flipper SD card. CLI: storage extract <archive.tar> <outdir>

func (*Flipper) StorageFSInfo added in v0.2.0

func (f *Flipper) StorageFSInfo(path string) (string, error)

StorageFSInfo returns filesystem info for a storage root. CLI: storage info <path>

A real Flipper emits a multi-line block like:

Label: Flipper SD
Type: FAT32
60194KiB total
42088KiB free

Or, when the filesystem isn't ready (no SD card inserted):

Storage error: not ready

func (*Flipper) StorageFSInfoMap added in v0.2.0

func (f *Flipper) StorageFSInfoMap(path string) (map[string]string, error)

StorageFSInfoMap runs `storage info <path>` and parses it into a flat key→value map. Known keys:

present     — "true" / "false"
error       — error text when present=false
label       — filesystem label (ext) or device name (int)
type        — filesystem type ("FAT32", "exFAT", "Virtual", ...)
totalSpace  — total bytes (decimal)
freeSpace   — free bytes (decimal)

device_info does NOT carry storage fields on any fork; this CLI is the canonical source both for the mobile app and /status.

func (*Flipper) StorageFormat added in v0.16.0

func (f *Flipper) StorageFormat() (string, error)

StorageFormat formats the external SD card (/ext). Destructive — the Spec risk band enforces confirmation; this method is a plain wire wrapper with no guard of its own. CLI: storage format /ext

func (*Flipper) StorageList

func (f *Flipper) StorageList(path string) (string, error)

StorageList lists files and directories at the given path. CLI: storage list <path>

On BLE the CLI is unavailable and the equivalent RPC verb (storage_list_request) is dispatched via the persistent rpc.Client; the response is reformatted into the same `\t[D] name\n` / `\t[F] name <size>b\n` block the firmware emits over USB so downstream parsers (parseStorageList in internal/web) work without knowing which transport produced it.

func (*Flipper) StorageMD5

func (f *Flipper) StorageMD5(path string) (string, error)

StorageMD5 returns the MD5 hash of a file on the SD card. CLI: storage md5 <path>

CLI emits the 32-character lowercase-hex digest followed by a newline; the RPC variant returns the same string and we append the newline so downstream parsers (which trim whitespace) see identical output.

func (*Flipper) StorageMkdir

func (f *Flipper) StorageMkdir(path string) (string, error)

StorageMkdir creates a directory. CLI: storage mkdir <path>

func (*Flipper) StorageRead

func (f *Flipper) StorageRead(path string) (string, error)

StorageRead reads the contents of a file. CLI: storage read <path>

Over USB the firmware emits "Size: <N>\n" then the raw bytes. stripStorageReadHeader and similar callers parse that shape. On BLE the RPC verb returns just the bytes; we reformat to match.

func (*Flipper) StorageRemove

func (f *Flipper) StorageRemove(path string) (string, error)

StorageRemove removes a file or directory. CLI: storage remove <path>

func (*Flipper) StorageRename

func (f *Flipper) StorageRename(src, dst string) (string, error)

StorageRename renames/moves a file or directory on the SD card. CLI: storage rename <src> <dst>

func (*Flipper) StorageStat

func (f *Flipper) StorageStat(path string) (string, error)

StorageStat returns metadata about a file or directory. CLI: storage stat <path>

On BLE the RPC response is reformatted to match the CLI's two canonical shapes that ParseStorageStat recognises:

Directory
File, size: <N>

Storage errors map to "Storage error: <msg>" so the parser's error branch fires.

func (*Flipper) StorageTree

func (f *Flipper) StorageTree(path string) (string, error)

StorageTree walks a directory recursively and returns its tree listing. CLI: storage tree <path>

On BLE the firmware exposes no `tree` RPC verb; we recreate the CLI's recursive `storage list` walk by issuing storage_list_request once per directory, depth-first. The CLI emits paths absolute to root (e.g. "\t[D] /ext/subghz/Tesla\n") rather than relative names — so do we, joining the directory path with the entry name and emitting one `\t[D|F] <path> [<size>b]` line per entry.

func (*Flipper) StorageWrite

func (f *Flipper) StorageWrite(path string, data string) error

StorageWrite writes data to a file using the write_chunk protocol.

func (*Flipper) StorageWriteCtx added in v0.2.5

func (f *Flipper) StorageWriteCtx(ctx context.Context, path string, data string) error

StorageWriteCtx is the context-aware variant of StorageWrite. On BLE the firmware exposes only RPC, so we dispatch via the persistent rpc.Client (storage_write_request, multi-Main with has_next) instead of the USB-only write_chunk text protocol that WriteFileCtx uses.

func (*Flipper) StreamCtx

func (f *Flipper) StreamCtx(ctx context.Context, command string, onLine func(line string) (stop bool)) error

StreamCtx runs command and invokes onLine for each output line as it arrives. It returns when ctx is done, when onLine returns true (caller asks to stop), or when the Flipper emits its terminating ">: " prompt. A Ctrl+C is always sent to the Flipper on exit so in-flight commands (like `rfid read` or `subghz rx`) are halted.

func (*Flipper) SubGHzChat

func (f *Flipper) SubGHzChat(frequency uint32, duration time.Duration) (string, error)

SubGHzChat joins an interactive Sub-GHz text chat on the given frequency. Long-running and actively transmits — the caller bounds it with a duration. Xtreme firmware requires the trailing `<device>` arg. CLI: subghz chat <frequency> [<device>]

func (*Flipper) SubGHzChatCtx added in v0.63.0

func (f *Flipper) SubGHzChatCtx(ctx context.Context, frequency uint32, duration time.Duration) (string, error)

SubGHzChatCtx is the context-aware variant of SubGHzChat.

func (*Flipper) SubGHzChatDevice added in v0.16.0

func (f *Flipper) SubGHzChatDevice(frequency uint32, duration time.Duration, device int) (string, error)

SubGHzChatDevice is like SubGHzChat but passes the device index explicitly. Long-running; the caller bounds it with a duration. CLI: subghz chat <frequency> -d <device>

func (*Flipper) SubGHzChatDeviceCtx added in v0.63.0

func (f *Flipper) SubGHzChatDeviceCtx(ctx context.Context, frequency uint32, duration time.Duration, device int) (string, error)

SubGHzChatDeviceCtx is the context-aware variant of SubGHzChatDevice.

func (*Flipper) SubGHzDecode

func (f *Flipper) SubGHzDecode(filePath string) (string, error)

SubGHzDecode decodes a previously captured raw Sub-GHz file. CLI: subghz decode_raw <file_path>

func (*Flipper) SubGHzRx

func (f *Flipper) SubGHzRx(frequency uint32, duration time.Duration) (string, error)

SubGHzRx receives Sub-GHz signals on the given frequency (Hz). Xtreme firmware's `subghz rx` requires a trailing <device> arg (0=internal CC1101, 1=external); we append "0" when capabilities report that quirk.

Note: withSuccessBuzz is intentionally omitted here. ExecLong returns nil on timeout (streaming semantics), so the buzz would always fire. On firmware that does not honour Ctrl+C (e.g. Momentum's subghz rx), the device may still be executing the command when the vibro Exec is sent, causing the buzz Exec calls to hang for their full safety-net deadline.

func (*Flipper) SubGHzRxCtx added in v0.62.0

func (f *Flipper) SubGHzRxCtx(ctx context.Context, frequency uint32, duration time.Duration) (string, error)

SubGHzRxCtx is the context-aware variant of SubGHzRx. ctx cancellation propagates via ExecLongCtx so a turn-level Ctrl+C aborts the in-flight capture without waiting for the duration timer.

func (*Flipper) SubGHzRxRaw

func (f *Flipper) SubGHzRxRaw(frequency uint32, duration time.Duration) (string, error)

SubGHzRxRaw streams raw Sub-GHz pulse data to the caller's return value. Available on Momentum firmware (subghz_cli.c:subghz_cli_command_rx_raw streams pulses to stdout with no file-path argument). On stock/Unleashed/ Xtreme firmware, the rx_raw verb requires a file-path argument that this API no longer accepts — those callers should use SubGHzRx for time-bounded capture, or construct a .sub capture manually via StorageWrite. CLI (Momentum): subghz rx_raw [<frequency>]

func (*Flipper) SubGHzRxRawCtx added in v0.62.0

func (f *Flipper) SubGHzRxRawCtx(ctx context.Context, frequency uint32, duration time.Duration) (string, error)

SubGHzRxRawCtx is the context-aware variant of SubGHzRxRaw. The same Momentum-only capability gate as the blocking variant applies — non-Momentum forks return the file-path-required error before any wire traffic.

func (*Flipper) SubGHzRxRawStream added in v0.57.0

func (f *Flipper) SubGHzRxRawStream(ctx context.Context, frequency uint32, duration time.Duration, onLine func(line string) (stop bool)) (string, error)

SubGHzRxRawStream is the line-streaming variant of SubGHzRxRaw. Each pulse line emitted while `subghz rx_raw` is running is delivered to onLine; stop=true ends the capture early. The same firmware-fork capability check as SubGHzRxRaw applies — non-Momentum forks return the file-path-required error before any streaming starts. CLI (Momentum): subghz rx_raw [<frequency>]

func (*Flipper) SubGHzRxStream added in v0.56.0

func (f *Flipper) SubGHzRxStream(ctx context.Context, frequency uint32, duration time.Duration, onLine func(line string) (stop bool)) (string, error)

SubGHzRxStream is the streaming variant of SubGHzRx. Each line emitted by firmware while `subghz rx` is running is delivered to onLine as it arrives; the callback can return stop=true to terminate the capture early (e.g. once a candidate signal lands). duration bounds the call like SubGHzRx; ctx cancel also terminates early. The accumulated raw output is returned so callers can feed it to ParseSubGHzReceive on the streaming path the same way they would on the blocking path.

func (*Flipper) SubGHzTx

func (f *Flipper) SubGHzTx(filePath string) (string, error)

SubGHzTx transmits a Sub-GHz signal from a saved file. CLI: subghz tx_from_file <file_path>

func (*Flipper) SubGHzTxKey

func (f *Flipper) SubGHzTxKey(keyHex string, freq uint32, te uint32, repeat int) (string, error)

SubGHzTxKey transmits a raw Sub-GHz key. Xtreme firmware requires a trailing <device> arg (0=internal CC1101, 1=external); appended when the detected capability flag is set. CLI: subghz tx <key_hex> <frequency> <te> <repeat> [device]

func (*Flipper) SubGHzTxKeyDevice added in v0.16.0

func (f *Flipper) SubGHzTxKeyDevice(keyHex string, freq uint32, te uint32, repeat int, device int) (string, error)

SubGHzTxKeyDevice is like SubGHzTxKey but sends the device index explicitly via the -d flag rather than relying on the auto-append logic in SubGHzTxKey (which only supports device=0). Use this when device != 0 (i.e. an external CC1101 module is wired to the GPIO header). CLI: subghz tx <key_hex> <frequency> <te> <repeat> -d <device>

func (*Flipper) Suspend added in v0.10.0

func (f *Flipper) Suspend(reason string) error

Suspend marks this handle inactive and closes the underlying transport so a sibling process (e.g. marauder.Connect) can open the same OS-level port. Subsequent CLI calls return ErrFlipperSuspended until the process exits. Suspend is idempotent — the first call's reason wins.

func (*Flipper) SuspensionReason added in v0.10.0

func (f *Flipper) SuspensionReason() string

SuspensionReason returns the string passed to the most recent Suspend call, or "" when not suspended.

func (*Flipper) Transport

func (f *Flipper) Transport() transport.Transport

Transport returns the underlying byte channel. Exposed read-only so /status output and telemetry can read Identity/Kind without the command-layer mutex.

func (*Flipper) UpdateInstall

func (f *Flipper) UpdateInstall(manifestPath string) (string, error)

UpdateInstall applies a firmware update from an already-staged manifest on the SD card. Long-running — uses a 5-minute deadline. Critical. CLI: update install <manifest_path>

func (*Flipper) Vibro

func (f *Flipper) Vibro(on bool) (string, error)

Vibro turns the vibration motor on (true) or off (false). On Momentum, vibro 1 is silently suppressed in two cases:

  • stealth mode (FuriHalRtcFlagStealthMode): "Flipper is in stealth mode…"
  • vibro disabled in settings: "Vibro is disabled in settings…"

Both return success at the firmware layer, so we detect the banner and return an error so callers know the motor was never activated. CLI: vibro <0|1>

func (*Flipper) WriteFile

func (f *Flipper) WriteFile(path string, data []byte) error

WriteFile writes data to a file on the Flipper using the storage write_chunk interactive protocol: send the command with the byte count, then send the raw bytes immediately after. Preserved for backward compatibility; prefer WriteFileCtx so cancellation and reconnect propagate a ctx.

func (*Flipper) WriteFileCtx

func (f *Flipper) WriteFileCtx(ctx context.Context, path string, data []byte) error

WriteFileCtx is the context-aware variant of WriteFile. It reconnects if needed, uses a cancellable sleep between command and payload, and tags any disconnect-class error via markDisconnectedIfRelevant so the next op can recover.

Firmware append behaviour: some firmware builds — notably Momentum dev branch as of mntm-dev 430a3d50 (2026-03-09) — do NOT truncate an existing file when storage write_chunk is called; they append to the existing content instead. Callers that need truncate semantics must issue "storage remove <path>" before writing, or the re-written file will contain concatenated data.

type LoaderApps

type LoaderApps struct {
	Apps     []string `json:"apps"`
	Settings []string `json:"settings"`
}

LoaderApps is the parsed shape of `loader list`: user-facing apps plus the settings menu entries (which are also launchable via `loader open`).

type NFCDetectResult added in v0.3.0

type NFCDetectResult struct {
	Detected   bool   `json:"detected"`
	Type       string `json:"type,omitempty"`       // "NTAG215", "MIFARE Classic 1K", ...
	Technology string `json:"technology,omitempty"` // "ISO14443-3a (NFC-A)" etc.
	UID        string `json:"uid,omitempty"`
	ATQA       string `json:"atqa,omitempty"`
	SAK        string `json:"sak,omitempty"`
	Raw        string `json:"raw,omitempty"`
}

NFCDetectResult captures the structured shape of an NFC subshell scanner response. At least one of UID/Type is set when a card was detected.

func ParseNFCDetect added in v0.3.0

func ParseNFCDetect(raw string) NFCDetectResult

ParseNFCDetect parses the output of the nfc subshell scanner subcommand into a structured result. A successfully parsed card sets Detected=true plus whatever fields the firmware emitted; empty / timeout output sets Detected=false.

type Pipeline added in v0.13.0

type Pipeline struct {
	// CLIRetryAttempts is the number of times ExecCtx will issue the
	// command before giving up on a hung CLI. ExecCtx's existing
	// per-attempt timeout (ExecTimeout) gates each try.
	//
	// NOTE: today's serial.go ExecCtx is single-shot — it sends once and
	// either returns the response or the "hung" error. We carry this
	// field so the auto-tune side can grow per-op retries without a
	// second refactor; the current dispatcher reads it but treats values
	// > 1 as a no-op until the retry loop is added in a follow-up.
	CLIRetryAttempts int
	// CLIRetryDelay is the delay between CLI retries when CLIRetryAttempts > 1.
	CLIRetryDelay time.Duration

	// RPCRetryAttempts is the number of Ping attempts rpc.Client.Open
	// will make before giving up. The legacy value was 5.
	RPCRetryAttempts int
	// RPCRetryDelay is the per-attempt context timeout used by Open's
	// Ping. The legacy value was 500ms.
	RPCRetryDelay time.Duration

	// FileWriteRetryAttempts is the number of times WriteFileCtx will
	// re-issue a failed storage write_chunk before giving up. As with
	// CLIRetryAttempts, today's WriteFileCtx is single-shot; values > 1
	// are reserved for the auto-tune follow-up.
	FileWriteRetryAttempts int
	// FileWriteRetryDelay is the delay between file-write retries.
	FileWriteRetryDelay time.Duration

	// Exec is the per-command read deadline used by ExecCtx. Replaces
	// the previous f.execTimeout SetExecTimeout setter as the source of
	// truth.
	Exec time.Duration
	// WriteFile is the post-payload read deadline used by WriteFileCtx.
	WriteFile time.Duration
	// Connect is the budget for the connect/reconnect cycle. ConnectURL
	// uses the caller-supplied timeout argument directly today; this
	// value is consulted by reconnectIfNeededLocked when the original
	// connectTimeout wasn't recorded.
	Connect time.Duration

	// ReconnectAttemptDelay is the inner sleep between transport
	// reconnect attempts in reconnectIfNeededLocked. Legacy value was
	// 250ms.
	ReconnectAttemptDelay time.Duration
}

Pipeline carries the resolved retry and timeout knobs for one profile. Zero values are not valid; always construct via ProfileSettings (or copy from one and tweak fields explicitly) so missing fields don't silently degrade to no-retry/no-timeout behaviour. Pipeline values are immutable after construction — *Flipper holds them by atomic.Pointer so a live SetPipeline call can swap the whole bundle without partial reads.

func ProfileSettings added in v0.13.0

func ProfileSettings(p PipelineProfile) Pipeline

ProfileSettings returns the canonical Pipeline bundle for the named profile. An unknown or empty name returns the Balanced bundle so a stale config string can never zero out the timeouts.

type PipelineProfile added in v0.13.0

type PipelineProfile string

PipelineProfile names a bundled retry/timeout policy applied across the command-dispatch layer (CLI exec, file write, RPC handshake, reconnect). Profiles let operators trade latency for reliability without re-deriving every constant by hand: a flaky USB cable picks "resilient", a known-good dev rig picks "fast", and the default ("balanced") matches the historical hard-coded behaviour byte-for-byte so existing scripts and tests keep their timing.

Inspired by V3SP3R's CommandPipelineAutotuneStatus shape — but the auto-tune feedback loop is intentionally NOT implemented here; profile selection is manual in this round. The struct is deliberately a flat bundle of durations so the future telemetry-driven auto-tuner can swap it atomically via SetPipeline without redoing the wire path.

const (
	// ProfileFast favours snappy failure over robustness. Suitable for
	// known-good USB rigs running CI or interactive dev where a hung
	// command should fall through fast and surface as an error rather
	// than waste seconds retrying.
	ProfileFast PipelineProfile = "fast"

	// ProfileBalanced is the default. Every value matches the legacy
	// hard-coded constants from before the pipeline refactor:
	//   - rpc.Open: 5 attempts, 500ms per ping
	//   - ExecCtx: 10s
	//   - WriteFileCtx: 10s
	//   - reconnect inter-attempt sleep: 250ms
	// Anything depending on the previous timing characteristics
	// (existing tests, scripts, hand-tuned configs) must observe
	// identical behaviour under this profile.
	ProfileBalanced PipelineProfile = "balanced"

	// ProfileResilient stretches every retry budget so commands ride
	// through transient cable wobble, BLE link drops, or a busy
	// firmware. Pays for it with latency on the failure path.
	ProfileResilient PipelineProfile = "resilient"
)

type RouteDecision added in v0.13.0

type RouteDecision struct {
	Route  CommandRoute
	Reason string
}

RouteDecision is the result of a routing decision. Reason is a short human-readable phrase that explains WHY the route was chosen — it gets wrapped into errors when Route is RouteUSBOnly so the agent layer can show the operator something more actionable than a bare "command failed".

func RouteFor added in v0.13.0

func RouteFor(operation string, support CommandSupport, transportKind string, caps Capabilities) RouteDecision

RouteFor picks the dispatch path for an operation given the command's declared CommandSupport, the live transport kind, and the detected firmware capabilities. The function is pure — same inputs always produce the same output — so it is straightforward to unit-test with table-driven cases.

Decision matrix (Phase A):

transport | HasRPCVerb | HasCLI | route
----------+------------+--------+------------------
ble       | true       | *      | RouteRPC
ble       | false      | *      | RouteUSBOnly (no RPC verb)
usb/mock  | *          | true   | RouteTextCLI
usb/mock  | *          | false  | RouteUSBOnly (no CLI verb)

FirmwareForkRequired is checked AFTER the transport-based selection: if the live caps.FirmwareFork does not match (case- insensitive), the route is rewritten to RouteUSBOnly with a descriptive reason regardless of what the transport offered.

type State added in v0.3.0

type State struct {
	Connected       bool   `json:"connected"`
	Fork            string `json:"fork,omitempty"`             // stock/Momentum/Unleashed/RogueMaster/Xtreme
	FirmwareVersion string `json:"firmware_version,omitempty"` // version string from device_info
	HardwareName    string `json:"hardware_name,omitempty"`    // user-settable dolphin name
	HardwareUID     string `json:"hardware_uid,omitempty"`
	BatteryPct      int    `json:"battery_pct,omitempty"`  // 0-100, omitted when unknown
	ChargeState     string `json:"charge_state,omitempty"` // "charging" / "discharging" / ""

	// Transport identifies how PromptZero is talking to the Flipper
	// ("serial" / "ble" / "mock"). The agent uses it to warn before
	// high-throughput operations on the slower BLE path.
	Transport string `json:"transport,omitempty"`

	// SDPresent reports whether the /ext volume exists at all. When
	// false, the SD-space fields are omitted and any storage_* tool
	// call will fail — surfacing this early saves the model a turn.
	SDPresent bool `json:"sd_present"`
	// SDTotalBytes and SDFreeBytes track SD capacity in bytes. Zero
	// values are omitted so a failed storage-info probe doesn't
	// masquerade as "0 free".
	SDTotalBytes int64 `json:"sd_total_bytes,omitempty"`
	SDFreeBytes  int64 `json:"sd_free_bytes,omitempty"`

	CollectedAt time.Time `json:"collected_at"`
}

State is a point-in-time snapshot of the connected Flipper, cheap to render into the model's turn context. Carries only fields that help the agent avoid redundant "what's connected?" round-trips; heavyweight probes (SD walk, loader state, log dump) are deliberately excluded.

Fields honour `omitempty` wherever missing data is better expressed as absence than as a zero sentinel (notably BatteryPct — a partial fetch that couldn't reach power_info must not surface as "battery: 0%").

type StorageStatResult added in v0.3.0

type StorageStatResult struct {
	Exists    bool   `json:"exists"`
	IsDir     bool   `json:"is_dir"`
	SizeBytes int64  `json:"size_bytes,omitempty"`
	Error     string `json:"error,omitempty"`
	Raw       string `json:"raw,omitempty"`
}

StorageStatResult captures the structured shape of `storage stat <path>` output. The Flipper firmware emits a short line per attribute: "File, size: 1234" for regular files, "Directory" for directories, "Storage error: <msg>" on failure.

func ParseStorageStat added in v0.3.0

func ParseStorageStat(raw string) StorageStatResult

ParseStorageStat parses `storage stat <path>` output. Order matters: the "Storage error:" check runs FIRST so interleaved output like "File\nStorage error: not found" (seen on some firmware forks) doesn't produce a false-positive Exists=true. When both markers appear, the error takes precedence — the file-regex matches anywhere on any line (case-insensitive), so a naked File/Directory check without the error gate would misclassify the error path.

type SubGHzCandidate added in v0.3.0

type SubGHzCandidate struct {
	Protocol  string `json:"protocol,omitempty"`
	Frequency uint32 `json:"frequency,omitempty"`
	Key       string `json:"key,omitempty"`
	Bit       int    `json:"bit,omitempty"`
	TE        int    `json:"te,omitempty"`
	RSSI      int    `json:"rssi,omitempty"`
}

SubGHzCandidate is one detected protocol / key block.

type SubGHzReceiveResult added in v0.3.0

type SubGHzReceiveResult struct {
	Candidates []SubGHzCandidate `json:"candidates,omitempty"`
	Count      int               `json:"count"`
	RawLines   []string          `json:"raw_lines,omitempty"`
}

SubGHzReceiveResult summarises the output of `subghz rx` / the subghz_receive tool. The Flipper emits detected protocol candidates as blocks like:

[Protocol: Princeton]
  Frequency: 433920000
  Key: 00 00 00 1A 2B 3C 4D 00
  Bit: 24

The parser collects one Candidate per block; unstructured noise lines go into RawLines.

func ParseSubGHzReceive added in v0.3.0

func ParseSubGHzReceive(raw string) SubGHzReceiveResult

ParseSubGHzReceive parses the output of subghz_receive. Detects one or more "Protocol:" blocks and extracts the common fields of each. Lines that don't belong to a block are preserved in RawLines.

Directories

Path Synopsis
Package mock provides a pty-backed fake Flipper CLI so serial.go and the command wrappers can be exercised without real hardware.
Package mock provides a pty-backed fake Flipper CLI so serial.go and the command wrappers can be exercised without real hardware.
rpc
Package rpc implements a typed Flipper Zero RPC client over a transport.Transport.
Package rpc implements a typed Flipper Zero RPC client over a transport.Transport.
pb
Package transport defines the byte-channel substrate the Flipper CLI layer operates over.
Package transport defines the byte-channel substrate the Flipper CLI layer operates over.

Jump to

Keyboard shortcuts

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