Sign in

    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
    last month
    Downloads
    54

    No dependencies

    #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