NetMD.Device (NetMD v0.1.0)

Copy Markdown View Source

The fundamental NetMD USB exchange protocol, ported from netmd-js.

Commands go out as vendor control transfers; replies are fetched by polling the device for a reply length and then reading that many bytes. Audio data moves over the bulk endpoints. This layer carries raw payloads only; NetMD.Interface gives them meaning.

Summary

Types

A connected NetMD device, as reported by list/1.

t()

An open NetMD device.

Functions

Release the device.

Quirk flags from the known device table.

List the connected NetMD devices without opening any of them.

Display name from the known device table.

Open a NetMD device and drain any leftover reply from a previous session.

Read length bytes from the bulk endpoint in :chunk_size chunks (default 0x10000). An optional :progress function is called with (total, read_so_far) after each chunk.

Read the reply to the last command, polling until the device has one.

Poll the length of the pending reply. {:ok, 0} means no reply yet.

Send a raw command payload. Set factory?: true for the factory command set (only do this if you know what you are doing).

Subscribe pid (default the caller) to {:netmd_status, status} events.

Stop pid (default the caller) receiving status events.

Run fun holding the device lock, if the transport has one.

Write data to the bulk OUT endpoint.

Types

listing()

@type listing() :: %{
  vendor_id: 0..65535,
  product_id: 0..65535,
  name: String.t(),
  flags: NetMD.Devices.flags(),
  bus: pos_integer() | nil,
  address: pos_integer() | nil
}

A connected NetMD device, as reported by list/1.

t()

@type t() :: %NetMD.Device{
  handle: NetMD.Transport.handle(),
  max_polls: pos_integer(),
  poll_interval_ms: non_neg_integer(),
  product_id: 0..65535,
  transport: module(),
  vendor_id: 0..65535
}

An open NetMD device.

Functions

close(device)

@spec close(t()) :: :ok

Release the device.

flags(device)

@spec flags(t()) :: NetMD.Devices.flags()

Quirk flags from the known device table.

list(opts \\ [])

@spec list(keyword()) :: [listing()]

List the connected NetMD devices without opening any of them.

Each entry carries its USB ids, bus location and the display name and quirk flags from the known device table. Multiple identical models are told apart by their :bus and :address. Enumeration does not fail; an empty list means nothing is connected.

Options:

Remaining options are passed to the transport. Raises ArgumentError for a transport that cannot enumerate devices.

name(device)

@spec name(t()) :: String.t()

Display name from the known device table.

open(opts \\ [])

@spec open(keyword()) :: {:ok, t()} | {:error, term()}

Open a NetMD device and drain any leftover reply from a previous session.

Options:

  • :reconnect - front the device with NetMD.Transport.Managed so it survives the USB re-enumeration that a disc insert/eject or session reset triggers, keeping the same handle across it (default true). Passing an explicit :transport takes precedence and disables the wrapper. See NetMD.Transport.Managed for :reconnect_wait and friends.
  • :transport - NetMD.Transport implementation, defaults to NetMD.Transport.Usb (wrapped per :reconnect above)
  • :vendor_id, :product_id - open a specific device instead of the first known one
  • :status_event_poll - with the managed transport (the default), ms between background status polls that drive subscribe/2 events, or false to disable (default 1000)

Remaining options are passed to the transport.

read_bulk(device, length, opts \\ [])

@spec read_bulk(t(), non_neg_integer(), keyword()) ::
  {:ok, binary()} | {:error, term()}

Read length bytes from the bulk endpoint in :chunk_size chunks (default 0x10000). An optional :progress function is called with (total, read_so_far) after each chunk.

read_reply(device, opts \\ [])

@spec read_reply(
  t(),
  keyword()
) :: {:ok, binary()} | {:error, term()}

Read the reply to the last command, polling until the device has one.

Polling backs off exponentially from poll_interval_ms and gives up after max_polls attempts with {:error, :no_reply}. Options:

  • factory?: true - read a factory command reply
  • length: n - skip polling and read exactly n bytes

reply_length(device)

@spec reply_length(t()) :: {:ok, non_neg_integer()} | {:error, term()}

Poll the length of the pending reply. {:ok, 0} means no reply yet.

send_command(device, command, opts \\ [])

@spec send_command(t(), binary(), keyword()) :: :ok | {:error, term()}

Send a raw command payload. Set factory?: true for the factory command set (only do this if you know what you are doing).

subscribe(device, pid \\ self())

@spec subscribe(t(), pid()) :: :ok | {:error, :status_events_unavailable}

Subscribe pid (default the caller) to {:netmd_status, status} events.

Only the managed transport polls status; other transports return {:error, :status_events_unavailable}.

unsubscribe(device, pid \\ self())

@spec unsubscribe(t(), pid()) :: :ok | {:error, :status_events_unavailable}

Stop pid (default the caller) receiving status events.

with_lock(device, fun)

@spec with_lock(t(), (-> result)) :: result when result: var

Run fun holding the device lock, if the transport has one.

The managed transport serialises the whole exchange against the status poller and any other holder; the lock is reentrant per process. Transports without a lock (bare USB, simulator, tests) just run fun.

write_bulk(device, data, opts \\ [])

@spec write_bulk(t(), binary(), keyword()) :: :ok | {:error, term()}

Write data to the bulk OUT endpoint.