madk

Transport-independent Android Open Accessory protocol support for MoonBit.

moonbit
android
usb
aoa
accessory
Download zip
Version
0.1.0
License
MIT
Last updated
23 hours ago
Downloads
21

#madk

madk is a MoonBit toolkit for hosts that communicate with Android devices through the Android Open Accessory (AOA) protocol.

The project keeps the protocol state machine independent from USB and platform details. Native USB access is provided by an optional libusb adapter; a simulator is available for deterministic tests.

#Status

madk is pre-1.0 software. Public MoonBit APIs, the WIT contract, and transport adapter behavior may change between releases.

#What it provides

  • AOA v1 accessory negotiation and bulk transport;
  • AOAv2 HID registration, descriptor transfer, and input events;
  • AOAv2 audio-mode negotiation with explicit capability errors;
  • transport interfaces for control and bulk transfers;
  • a deterministic simulator with failure injection;
  • a native libusb transport with runtime diagnostics;
  • a native AOA probe for end-to-end negotiation and fixture frame checks;
  • an Android fixture application for physical AOA acceptance tests;
  • a WIT host-capability contract for a future WASI integration.

#Package layout

PathPurpose
protocol.mbt, session.mbtportable AOA protocol state machine
transport/transport interfaces and shared transfer types
sim/deterministic simulator and tests
libusb/native libusb adapter
examples/native/aoa_probe/native libusb probe for physical AOA checks
wit/WASI host capability contract
examples/android/Android-side test fixture
docs/protocol and integration-boundary notes

#WASI boundary

WASI is an integration boundary, not a USB driver. A WASI component cannot access Android USB APIs or libusb directly. The host must provide a capability for device selection, control transfers, bulk transfers, timeouts, disconnects, and cleanup.

The current WIT file defines that host contract but does not embed a WASI runtime or provide a complete WASI adapter. See docs/wasi.md.

#Requirements

  • MoonBit compiler release 0.10.9+6e6c44045, selected by .moon-version;
  • a native C toolchain for native builds;
  • libusb 1.0 runtime and host USB permissions for physical native USB tests;
  • JDK 17, Android SDK, and Gradle 9.6.1 for the Android fixture;
  • adb from Android SDK platform-tools for wireless fixture deployment.

The pinned release bundles MoonBit compiler moonc 0.10.9+6e6c44045. The repository pins the exact release identifier because MoonBit is still evolving and compiler/API changes must be deliberate. Do not replace the version with the latest channel in local or CI setup.

#Build and test

The portable core and simulator do not require an Android device:

moon fmt --check moon check --target native --deny-warn moon test --target native --deny-warn moon check --target all --deny-warn

The native probe package has an explicit CLI smoke test:

moon check examples/native/aoa_probe --target native --deny-warn moon run examples/native/aoa_probe --target native -- --help

Build the Android fixture with:

cd examples/android gradle :app:assembleDebug --no-daemon

The Android application does not perform the AOA handshake. A host must negotiate AOA, start accessory mode, handle USB re-enumeration, and then use the fixture's length-prefixed test frames. Physical Windows tests may require WinUSB on the Android Accessory interface and may require stopping a conflicting ADB service before opening the composite device with libusb.

#Native AOA probe

examples/native/aoa_probe/ uses LibusbTransport and AoaSession to perform the host-side AOA flow:

  1. select the normal Android device with --vid and --pid;
  2. query the AOA protocol and send the madk-fixture identity;
  3. start accessory mode and wait for USB re-enumeration;
  4. verify the status response and a length-prefixed echo response.

Use decimal IDs or 0x-prefixed hexadecimal IDs:

moon run examples/native/aoa_probe --target native -- --vid 0x18d1 --pid <normal-pid>

To connect to a device that is already in accessory mode, use --accessory instead of --vid and --pid:

moon run examples/native/aoa_probe --target native -- --accessory

The probe requires a loadable libusb 1.0 runtime. On Windows, bind WinUSB only to the Android Accessory interface and keep the ADB interface on its existing driver. A running ADB service may need to be stopped before the probe opens the accessory interface.

#Wireless Android fixture deployment

The fixture can be installed and started over wireless ADB:

./examples/android/deploy-wireless.ps1 -Device "<device-ip>:<debug-port>" -Build -ClearLog -DumpLog

The script searches adb in PATH, ANDROID_HOME, ANDROID_SDK_ROOT, and the default Windows SDK location. -Apk selects an existing APK, -Build builds the debug APK first, -ClearLog clears the device log, and -DumpLog prints the madk-fixture logcat stream. -AdbPath can override adb discovery.

Wireless deployment verifies APK installation, Activity startup, and Android logs only. It does not exercise the USB AOA negotiation or the native bulk transport; use a physical USB data connection and the native probe for that.

#Documentation

#License

madk is distributed under the MIT License.

AoaError

pub(all) suberror AoaError {
InvalidState(String)
InvalidIdentityField(Int)
UnsupportedProtocol(Int)
DeviceNotInAccessoryMode(Int)
MissingBulkEndpoints
ShortTransfer(Int, Int)
UnsupportedFeature(AoaFeature)
InvalidHidId(Int)
HidAlreadyRegistered(Int)
UnknownHid(Int)
InvalidHidDescriptorLength(Int, Int)
HidDescriptorNotReady(Int)
Transport(
TransportError
)
} derive(Eq,
Debug
)

AoaFeature

pub(all) enum AoaFeature {
Bulk
Hid
Audio
} derive(Eq,
Debug
)

AoaIdentity

pub struct AoaIdentity {
// private fields
} derive(Eq,
Debug
)

AoaIdentity::description

fn AoaIdentity::description(self : AoaIdentity) -> String

AoaIdentity::manufacturer

fn AoaIdentity::manufacturer(self : AoaIdentity) -> String

AoaIdentity::minimal

fn AoaIdentity::minimal(manufacturer~ : String, model~ : String, version~ : String) -> AoaIdentity raise AoaError

AoaIdentity::model

fn AoaIdentity::model(self : AoaIdentity) -> String

AoaIdentity::new

fn AoaIdentity::new(manufacturer~ : String, model~ : String, description~ : String, version~ : String, uri~ : String, serial~ : String) -> AoaIdentity raise AoaError

AoaIdentity::serial

fn AoaIdentity::serial(self : AoaIdentity) -> String

AoaIdentity::uri

fn AoaIdentity::uri(self : AoaIdentity) -> String

AoaIdentity::version

fn AoaIdentity::version(self : AoaIdentity) -> String

AoaProtocolVersion

pub(all) enum AoaProtocolVersion {
V1
V2
} derive(Eq,
Debug
)

AoaProtocolVersion::wire_value

fn AoaProtocolVersion::wire_value(self : AoaProtocolVersion) -> Int

AoaSession

pub struct AoaSession {
// private fields
}

AoaSession::audio_capability

fn AoaSession::audio_capability(self : AoaSession) -> AudioCapability

AoaSession::audio_is_active

fn AoaSession::audio_is_active(self : AoaSession) -> Bool

AoaSession::audio_mode

fn AoaSession::audio_mode(self : AoaSession) -> AudioMode

AoaSession::close

fn AoaSession::close(self : AoaSession) -> Unit raise AoaError

AoaSession::configure

fn AoaSession::configure(self : AoaSession, identity : AoaIdentity, audio_mode? : AudioMode) -> Unit raise AoaError

AoaSession::device_info

AoaSession::probe

AoaSession::protocol

fn AoaSession::protocol(self : AoaSession) -> AoaProtocolVersion?

AoaSession::read_bulk

fn AoaSession::read_bulk(self : AoaSession, max_length : Int, timeout_ms : Int) -> Bytes raise AoaError

AoaSession::read_bulk_exact

fn AoaSession::read_bulk_exact(self : AoaSession, length : Int, timeout_ms : Int) -> Bytes raise AoaError

AoaSession::refresh_bulk_endpoints

fn AoaSession::refresh_bulk_endpoints(self : AoaSession) -> Unit raise AoaError

AoaSession::register_hid

fn AoaSession::register_hid(self : AoaSession, identifier~ : Int, descriptor_length~ : Int) -> HidDevice raise AoaError

AoaSession::send_hid_event

fn AoaSession::send_hid_event(self : AoaSession, device : HidDevice, report : Bytes) -> Unit raise AoaError

AoaSession::send_hid_report_descriptor

fn AoaSession::send_hid_report_descriptor(self : AoaSession, device : HidDevice, descriptor : Bytes, packet_size? : Int) -> Unit raise AoaError

AoaSession::set_audio_mode

fn AoaSession::set_audio_mode(self : AoaSession, mode : AudioMode) -> Unit raise AoaError

AoaSession::start

fn AoaSession::start(self : AoaSession) -> Unit raise AoaError

AoaSession::state

fn AoaSession::state(self : AoaSession) -> AoaState

AoaSession::supports

fn AoaSession::supports(self : AoaSession, feature : AoaFeature) -> Bool

AoaSession::unregister_hid

fn AoaSession::unregister_hid(self : AoaSession, device : HidDevice) -> Unit raise AoaError

AoaSession::write_bulk

fn AoaSession::write_bulk(self : AoaSession, payload : Bytes, timeout_ms : Int) -> Unit raise AoaError

AoaState

pub(all) enum AoaState {
Detected
Probed(AoaProtocolVersion)
Configured
Started
BulkReady
Closed
} derive(Eq,
Debug
)

AudioCapability

pub(all) enum AudioCapability {
Unsupported
Pcm44100Stereo
} derive(Eq,
Debug
)

AudioMode

pub(all) enum AudioMode {
Disabled
Pcm44100Stereo
} derive(Eq,
Debug
)

HidDevice

pub struct HidDevice {
// private fields
} derive(Eq,
Debug
)

HidDevice::identifier

fn HidDevice::identifier(self : HidDevice) -> Int

accessory_adb_product_id

let accessory_adb_product_id : Int

accessory_audio_adb_product_id

let accessory_audio_adb_product_id : Int

accessory_audio_product_id

let accessory_audio_product_id : Int

accessory_get_protocol

let accessory_get_protocol : Int

accessory_product_id

let accessory_product_id : Int

accessory_register_hid

let accessory_register_hid : Int

accessory_send_hid_event

let accessory_send_hid_event : Int

accessory_send_string

let accessory_send_string : Int

accessory_set_audio_mode

let accessory_set_audio_mode : Int

accessory_set_hid_report_desc

let accessory_set_hid_report_desc : Int

accessory_start

let accessory_start : Int

accessory_unregister_hid

let accessory_unregister_hid : Int

aoa_vendor_id

let aoa_vendor_id : Int

audio_adb_product_id

let audio_adb_product_id : Int

audio_product_id

let audio_product_id : Int

default_control_timeout_ms

let default_control_timeout_ms : Int

usb_vendor_device_in

let usb_vendor_device_in : Int

usb_vendor_device_out

let usb_vendor_device_out : Int

Powered by MoonBit

Site sourceReport issuePackagesBuild queueSkillsStatistics

© 2026 mooncakes.io