{"id":25146891,"url":"https://github.com/hn/linkplay-a31","last_synced_at":"2025-04-28T13:08:00.913Z","repository":{"id":230914831,"uuid":"777945060","full_name":"hn/linkplay-a31","owner":"hn","description":"Linkplay A31 WiFi audio module alternative firmware (OpenWrt) and device tree source file","archived":false,"fork":false,"pushed_at":"2024-12-27T12:21:39.000Z","size":58830,"stargazers_count":29,"open_issues_count":4,"forks_count":3,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-04-28T13:07:45.752Z","etag":null,"topics":["alsa","i2s","linkplay","mediatek","mt7688","mt7688an","openwrt","ramips"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-26T19:46:15.000Z","updated_at":"2025-04-20T12:10:25.000Z","dependencies_parsed_at":"2025-02-08T20:38:50.098Z","dependency_job_id":null,"html_url":"https://github.com/hn/linkplay-a31","commit_stats":null,"previous_names":["hn/linkplay-a31"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hn%2Flinkplay-a31","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hn%2Flinkplay-a31/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hn%2Flinkplay-a31/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hn%2Flinkplay-a31/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hn","download_url":"https://codeload.github.com/hn/linkplay-a31/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251319608,"owners_count":21570426,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["alsa","i2s","linkplay","mediatek","mt7688","mt7688an","openwrt","ramips"],"created_at":"2025-02-08T20:27:58.188Z","updated_at":"2025-04-28T13:08:00.854Z","avatar_url":"https://github.com/hn.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Linkplay A31 WiFi audio streaming module\n\n## Preamble\n\nThe [Linkplay A31](https://www.linkplay.com/modules) is a WiFi audio streaming module\n(System on Module, SoM) developed to be used in wireless speakers and wireless audio systems.\nThe module seems to be sold under [many different names](https://github.com/nagyrobi/home-assistant-custom-components-linkplay#about-linkplay)\n([Rakoit](https://www.rakoit.com/product/wifi-module-a31/), Wiimu).\n\nBecause the module itself does not have a DAC and amplifier,\nit is typically connected to another board in many consumer products.\nFor example, in the [DYON Area L (Serie 2) WiFi speaker](https://www.dyon.eu/portfolio/dyon-area-lautsprecher/)\nit is connected to an C.AP8064.05 amplifier board\n([PCB back](c-ap8064-05-amp-pcb-back.jpg), [PCB front with serial cable attached to the A31](c-ap8064-05-amp-pcb-front.jpg),\n[MVsilicon AP8064 MCU datasheet](https://www.diyaudio.com/community/attachments/ap8064_datasheet_v1-2-pdf.1145388/)).\n\nThe aim of this project is to replace the vendor firmware by an open source solution.\nWith the following instructions you can install an [OpenWrt](https://openwrt.org/) system\nwith network streaming capabilities for [Apple](https://apple.com)\n[Airplay](https://en.wikipedia.org/wiki/AirPlay) devices.\n\nPlease do me a favor: :thumbsup: If you use any information or code you find here, please link back to this page.\n:star: Also, please consider to star this project. I really like to keep track of who is using this to do creative things, especially if you are from other parts of the world.\n:smiley: You are welcome to open an issue to report on your personal success project and share it with others.\n\n\n## Hardware\n\nThe Linkplay A31 V04 module ([PCB front](linkplay-a31-pcb-front.jpg), [datasheet](https://fcc.report/FCC-ID/2AAPP-A31/3034717.pdf))\nis based on a MediaTek MT7688AN MCU (32-bit MIPS 24KEc) with 64MB RAM and 16MB W25Q128 SPI NOR flash.\nThe module offers pins for I2S, I2C, ethernet, USB, UART and 5 GPIOs.\n\nThe module has an undocumented serial port used as Linux console ttyS1\n([RX/TX pins next to the WiFi antenna](linkplay-a31-serial-port.jpg),\n[full PCB](c-ap8064-05-amp-pcb-front.jpg)).\nThe serial port works with 5V (not 3V3) and 57600 8N1.\n\n:raised_hand: Some (newer) modules do not output anything to the serial port.\nThis likely can be fixed by [patching the bootloader](#vendor-bootloader).\n\n:warning: Warning: There are probably [multiple hardware versions](https://github.com/hn/linkplay-a31/issues/4) of the Linkplay A31 module\n(which is weird because they are all labeled \"V04\"). The versions seem to differ in the voltage used at the serial port (3v3 vs. 5v) and\neven the MCU seems to be slightly different (MT7628  vs. MT7688). This needs to be investigated further.\n\n\n## Software\n\n### Vendor firmware\n\nThe module uses a patched (and sometimes [crippled](#vendor-bootloader)) version of U-Boot 1.1.3 (Ralink UBoot Version 4.3.0.0),\na (MediaTek SDK) patched Linux kernel 2.6.36 and\na weird combination of proprietary and OSS/GNU operating system components.\n\nOther users thankfully have comprehensivly analysed the system and vendor application layer:\n[Crymeiriver started reverse engineering some years ago](https://github.com/Crymeiriver/LS150),\n[Jan21493 added a ton of more information](https://github.com/Jan21493/Linkplay) and\n[AndersFluur wrote an inofficial API documentation](https://github.com/AndersFluur/LinkPlayApi).\n\n\n### Linux kernel support\n\n[mt7628an_linkplay_a31.dts](openwrt-linkplay-a31/mt7628an_linkplay_a31.dts) is a\n[Device Tree Source file](https://en.wikipedia.org/wiki/Devicetree)\nwhich describes the hardware components of the Linkplay A31 WiFi module.\n\nThe DTS configures the A31 as an I2S slave (see A31 manual section 2.3) using a generic S/PDIF transmitter.\nFor I2S master configuration, you'll need to add a real codec,\nsee DTS files of other boards if needed.\n\nOriginal firmware has a 'firmware' partition with size 0x730000 at 0x250000,\na jffs2 'user' partitition with size 0x80000 at 0x980000 and\na jffs2 'user2' partition with size 0x600000 at 0xa00000.\nThe DTS drops both user partitions in favour of a bigger 'firmware' partition\n(writeable, with [OverlayFS](https://openwrt.org/docs/techref/flash.layout#partitioning_of_nor_flash-based_devices)).\n\nThe WiFi module GPIOs 1-5 (PCB pins 23, 22, 27, 26, 19) are wired\nto MT7688AN pins 14-18, one can access them from Linux via\nGPIOs 494 (GPIO2), 495 (GPIO1), 496 (GPIO4), 497 (GPIO3) and 498 (GPIO5).\n\n\n### OpenWrt\n\n#### Compiling\n\nMake sure to read the general OpenWrt docs\n[here](https://openwrt.org/docs/guide-developer/toolchain/start) and\n[here](https://openwrt.org/docs/guide-developer/toolchain/use-buildsystem)\nand at least [install the necessary build packages](https://openwrt.org/docs/guide-developer/toolchain/install-buildsystem#debianubuntumint).\n\nThen proceed like this:\n\n```\ngit clone https://github.com/hn/linkplay-a31.git\ngit clone https://git.openwrt.org/openwrt/openwrt.git\ncd openwrt\ngit checkout v23.05.5\n./scripts/feeds update -a\n./scripts/feeds install -a\n../linkplay-a31/openwrt-linkplay-a31/prepare-openwrt-a31.sh\n# edit 'files/etc/config/wireless' and set your WiFi credentials\nmake -j$(nproc) defconfig download clean world\n```\n\nOutput images are found in `./bin/targets/ramips/mt76x8`.\n\nThe script includes a patch which works around \n[the MT7688 from crashing the entire system when I2S audio playback is stopped manually](openwrt-linkplay-a31/836-mt7688-i2s-audio-crash-workaround.patch).\n\n#### Testing\n\nThe OpenWrt image can easily be tested _without_ writing anything to flash\n(no permanent modifications).\nYou can use [self-compiled packages](#compiling) or \n[packages provided by this project](openwrt-ramips-mt76x8-linkplay_a31-initramfs-kernel.bin).\n\nInstall Kermit (`apt-get install ckermit`),\nconnect the [serial console of the A31](#hardware)\nand execute `../linkplay-a31/linkplay-a31-serial-upload-kermit`\n(change `port /dev/ttyUSB0` if necessary).\nReset (e.g. powercycle) the A31.\nThe Kermit script will interrupt the A31 boot process,\nupload the `openwrt-ramips-mt76x8-linkplay_a31-initramfs-kernel.bin` image\nvia `loadb` and boot the system from RAM (`bootm`).\nBe patient, the serial upload will take roughly 20-25 minutes to finish.\n\nYou might want to edit `/etc/config/wireless` within the\ntest system (followed by `wifi reconf`) to set your WiFi credentials.\n\nThe image contains the package `shairport-sync-mini` and\ntherefore you can use an Apple device to stream music to it.\nAlternativly, you can use `aplay` for wav-files or `mpg123`\nfor music streams (non-ssl only). Do not download large files\nto the system, space is extremely limited.\n\nWith `fw_printenv` one can list the NVRAM contents of the vendor firmware\n(config [fw_env.config](openwrt-linkplay-a31/files/etc/fw_env.config)).\n\n#### Installing\n\nIt is recommended to [boot the system via serial](#testing) and\nflash the image from within the running system with\n\n```\nmtd -r write /tmp/openwrt-ramips-mt76x8-linkplay_a31-squashfs-sysupgrade.bin firmware\n```\n\nMake sure that the time of the newly installed system is correct, otherwise you won't be able to download HTTPS content due to certificate validity problems.\n\n:warning: Warning: Make a backup of the flash content before\noverwriting it. No need to say that flashing will void the warranty.\n\n:warning: Warning: Take extra care when flashing, especially\nmake sure not to overwrite the U-Boot bootloader, this will\nbrick your device.\n\n:raised_hand: [Flash memory has a limited number of write cycles](https://en.wikipedia.org/wiki/Flash_memory#Memory_wear).\nAvoid unnecessary system upgrades or writing large files\n(always use [tmpfs](https://en.wikipedia.org/wiki/Tmpfs) in /tmp if possible).\n\n#### Rescue access point\n\nWhen installed to flash, the system sets up a [rescue access point](openwrt-linkplay-a31/files/etc/init.d/rescue-ap)\nif there is no WiFi connectivity (e.g. wrong credentials) three minutes after booting.\nThe SSID is `LINKPLAY` and [passphrase](openwrt-linkplay-a31/files/etc/hostapd-rescue.conf) is `A31A31A31`.\nIP address for SSH connection is `192.168.31.1` (no DHCP server, you have to set IP manually on your client).\nAfter 10 minutes of inactivity the rescue access point will be shut down (sytem reboot).\n\n#### Additional software\n\nThe flash space of the A31 module is extremely limited.\nTherefore the [OpenWrt config](openwrt-linkplay-a31/defconfig) includes only a minimal set of software packages (e.g. no web interface).\nAfter installation, you (only) have roughly 6 MB of free space available.\n\nWhile it is possible to install additional software, many packages (e.g. [gmrender-resurrect](https://github.com/hosac/openwrt-feed-gmediarender), OwnTone, ...) depend on Gstreamer and FFmpeg libraries, which are way to big to be installed on the module.\n\nI suggest to install [mpd-mini](https://openwrt.org/packages/pkgdata/mpd-mini) (config [diff here](openwrt-linkplay-a31/mpd-config.diff), and you have to add user 'mpd' to the 'audio' group).\nHTTPS-streams seem to overload the cpu and tend to be unstable.\n\n#### Web firmware updater\n\nThe vendor's web interface offers the option of uploading a binary firmware update to the device.\nHowever, the vendor has taken some precautions to ensure that only files in a certain format are accepted:\n- The operating system (OS) identifier in the uImage kernel header must be set to 'SVR4' instead of the standard 'Linux'.\n- The root filesystem has to be wrapped into an uImage as well (unlike OpenWrt does this).\n- Various length checks are enforced.\n\n[openwrt-ramips-mt76x8-linkplay_a31-squashfs-firmware.bin](openwrt-ramips-mt76x8-linkplay_a31-squashfs-firmware.bin)\nis a firmware file that fulfills these requirements by [patching the kernel and various headers and inserting some padding](openwrt-linkplay-a31/linkplay-firmware-bin.diff).\nThis allows it to be flashed via the devices's web interface.\nUse the [rescue access point](#rescue-access-point) to access the system after web flashing.\n\n:warning: Warning: Overwriting the firmware may brick you device.\nIt is strongly recommended to [flash via the serial port](#installing) because the\nweb firmware file has only been tested once on a single board.\nAnd if you use the Web Updater, you do not have the option of creating a backup copy of the vendor software.\n\nIf you want to rebuild the web firmware image, you have to pass\nthe `-fw` option to the [prepare-openwrt-a31.sh](openwrt-linkplay-a31/prepare-openwrt-a31.sh) script.\n\n\n### Amplifier control app\n\nThe Linkplay A31 does not have a DAC and amplifier, so it\nis necessary to control volume and mute status via\nserial commands (e.g. `AXX+VOL+007`, sent via `ttyS0 57600 8N1`\nto the amp board).\n\nAudio is [not always that straightforward with Linux](https://hacktivis.me/articles/linux-audio-output),\nso this project uses a possibly confusing (some might call it clever)\n[ALSA](https://www.alsa-project.org/) setup to control the amplifier status:\n\n- The I2S S/PDIF output device does not have a mixer, so client programs\n  (e.g. shairport-sync) would not be able to control the volume.\n- Therefore a Linux dummy sound device (with dummy mixer) is activated (`kmod-sound-dummy`).\n- With a [specially crafted asound.conf](openwrt-linkplay-a31/files/etc/asound.conf),\n  default audio output is routed to the I2S device (the amp board accepts audio format S16, rate 44,100 Hz)\n  and default audio control is routed to the dummy device (mixer).\n  Client programs happily change the volume of the dummy mixer,\n  but the real audio data is passed unchanged (volume 100%) to the amplifier board\n  (assuming that the client program itself does no software mixing,\n  it is recommended to disable any client volume/mixer processing).\n- A [Linkplay Emulator Daemon](openwrt-linkplay-a31/linkplay-emu/src/linkplay-emu.c)\n  is installed to monitor both devices and send serial control commands\n  to the amplifier board when sound is played or the volume changes.\n\nThe terms 'emulator' and 'daemon' are a bit exaggerated, the code only\nemulates three serial commands and does not daemonize itself,\nbut hey, it works :)\n\n\n### Vendor bootloader\n\nSome (newer) A31 module versions are shipped with a crippled U-Boot bootloader\nwhich does not output anything to the serial port.\nIt seems that the vendor does not want users to modify the software on the device themselves.\n\nAccording to [Jan21493](https://github.com/Jan21493/)'s\n[research](https://github.com/Jan21493/Linkplay/blob/main/Downgrade.md)\nthe most recent bootloader version is\n[uboot_v632.img](http://silenceota.linkplay.com/wifi_audio_image/2ANRu7eyAEYtoo4NZPy9dL/uboot_v632.img).\n\nIf you have a look at the\n[source code of a bootloader of a comparable board](https://github.com/MediaTek-Labs/linkit-smart-7688-uboot/blob/master/lib_mips/board.c#L728)\none can see the following initialization sequence:\n\n```\ntimer_init();\nenv_init();\t\t/* initialize environment */\ninit_baudrate();\t/* initialze baudrate settings */\nserial_init();\t\t/* serial communications setup */\nconsole_init_f();\n```\n\nSince `uboot_v632.img` is distributed without source code,\na deeper understanding can be obtained by disassembling the binary file\n(function names have been annotated after thorough analysis):\n\n```\nbc001164 10 00 bc 8f     lw       gp,local_40(sp)\nbc001168 84 02 99 8f     lw       t9,0x284(gp)=\u003e-\u003eFUN_timer_init_bc004358\nbc00116c 09 f8 20 03     jalr     t9=\u003eFUN_timer_init_bc004358\nbc001170 00 00 00 00     _nop\nbc001174 10 00 bc 8f     lw       gp,local_40(sp)\nbc001178 6c 01 99 8f     lw       t9,0x16c(gp)=\u003e-\u003eFUN_env_init_bc011990\nbc00117c 09 f8 20 03     jalr     t9=\u003eFUN_env_init_bc011990\nbc001180 00 00 00 00     _nop\nbc001184 10 00 bc 8f     lw       gp,local_40(sp)\nbc001188 00 e1 02 34     ori      v0,zero,0xe100\nbc00118c 08 00 42 af     sw       v0,local_30(k0)\nbc001190 c4 00 99 8f     lw       t9,0xc4(gp)=\u003e-\u003eFUN_console_init_f_bc00f8ec\nbc001194 09 f8 20 03     jalr     t9=\u003eFUN_console_init_f_bc00f8ec\nbc001198 00 00 00 00     _nop\nbc00119c 10 00 bc 8f     lw       gp,local_40(sp)\nbc0011a0 ff df 02 3c     lui      v0,0xdfff\nbc0011a4 ff ff 42 34     ori      v0,v0,0xffff\nbc0011a8 68 00 99 8f     lw       t9,0x68(gp)=\u003e-\u003eFUN_dram_cali_bc003b90\nbc0011ac 24 c8 22 03     and      t9,t9,v0\nbc0011b0 09 f8 20 03     jalr     t9=\u003eSUB_9c003b90\nbc0011b4 00 00 00 00     _nop\n```\n\nOne quickly can see that the calls to `init_baudrate` and `serial_init` are missing.\n\nAn obvious approach would be to replace the jump table call at offset\n`0xc4` to `console_init_f` (this function is not absolutely necessary) with a call of `serial_init`.\nUnfortunately, the jump table does not contain an entry for `serial_init`,\nas this function is intentionally not used by the manufacturer's firmware.\nBut fortunately there is an entry for `serial_setbrg` in the jump table at offset `0x30c`,\nwhich [does virtually nothing else](https://github.com/MediaTek-Labs/linkit-smart-7688-uboot/blob/master/board/rt2880/serial.c#L251) than `serial_init`.\nSo the minimally invasive solution is therefore to replace the (little-endian)\nbytes `0xc400` at position `0x1190` with `0x0c03`, for example as follows:\n\n```\n$ wget http://silenceota.linkplay.com/wifi_audio_image/2ANRu7eyAEYtoo4NZPy9dL/uboot_v632.img\n$ md5sum uboot_v632.img\n8e0445af74e108eace0c90e849c37723\n$ cp -v uboot_v632.img uboot_v632-patched.img\n'uboot_v632.img' -\u003e 'uboot_v632-patched.img'\n$ echo -n -e \"\\x0C\\x03\" | dd seek=4496 bs=1 count=2 conv=notrunc of=uboot_v632-patched.img\n2 bytes copied, 0.000151282 s\n$ md5sum uboot_v632-patched.img\n7c9cbe63d6c0f7f6425eb1c1246d7d0b\n```\n\nThe modified firmware can be written to flash memory e.g. by executing\n`mtd_write -w write /tmp/uboot_v632-patched.img Bootloader`\non the A31 module.\n\n:warning: Warning: Overwriting the bootloader may brick you device. Be careful.\n\n\n# Dyon Area L WiFi speaker\n\nThere are (at least) two different hardware revisions of this speaker:\n\n## Series 1: C.8064.01 amplifier board\n\nSeries 1 has a rectangular label (8x5 cm) and can be opened from bottom of the speaker enclosure.\nIt consists of a WiiMu A21 WiFi module (MT7620A MCU) and a C.8064.01 amp board.\nNo research has yet been undertaken with this model.\n\n## Series 2: C.AP8064.05 amplifier board\n\nSeries 2 has a square label (8x8 cm, with \"Serie 2\" printed on it) and\ncan be opened from the back of the speaker enclosure, the screws are hidden behind the edges of the label.\n\nThe WiFi speaker plays a particularly annoying\nwelcome message (\"Geniesse WiFi Musik\" in german) when you\nswitch to the Airplay source (`AXX+PLM+001`).\nThis message is _not_ stored on the A31 module but on the amp board.\n\nSince it was not possible to stop this dumb behavior via serial commands,\na more stringent solution had to be found.\n\nThe firmware of the MVsilicon AP8064 MCU is stored in a 4MB GD25Q32 SPI NOR\nflash which can be [read with a SOIC-8 clip](https://www.flashrom.org) like this:\n\n![AP8064 SPI NOR flash SOIC8](c-ap8064-05-amp-pcb-soic8.jpg)\n\nHaven't looked closely, but the dump doesn't seem to contain a known\nkernel or file system, everything appears to be proprietary.\n\nBy examining the dump more carefully one sees that there is some\nkind of 'table of contents' represented by text-offset-length values\nat flash position `0x100000`:\n\n```\nToC 01, Signature: WIFI, Offset: 100bfd, Length:  32f8\nToC 02, Signature: USB_, Offset: 103ef5, Length:  2c9c\nToC 03, Signature: WTCN, Offset: 106b91, Length:  2e9c\nToC 04, Signature: AUX_, Offset: 109a2d, Length:  316d\nToC 05, Signature: CARD, Offset: 10cb9a, Length:  2de1\nToC 06, Signature: FMRD, Offset: 10f97b, Length:  2442\nToC 07, Signature: CNND, Offset: 111dbd, Length:  1d07\n...\nToC 29, Signature: 3DFF, Offset: 13fd41, Length:  b36c\nToC 30, Signature: WIfi, Offset: 14b0ad, Length: 1631a\nToC 31, Signature: Aux_, Offset: 1613c7, Length: 15f05\nToC 32, Signature: CNnd, Offset: 1772cc, Length:  c800\nToC 33, Signature: CNlt, Offset: 183acc, Length: 1094e\nToC 34, Signature: LWbt, Offset: 19441a, Length: 22b1a\nToC 35, Signature: CHrg, Offset: 1b6f34, Length:  b398\nToC 36, Signature: PWou, Offset: 1c22cc, Length: 121cc\nToC 37, Signature: WTcn, Offset: 1d4498, Length: 17399\nToC 38, Signature: FMup, Offset: 1eb831, Length:  af83\n```\n\nIf you extract the data, you exactly get 38 valid MP3 files with\naudio data for all types of events ('Charging', 'Connection lost' etc.).\n\nThe annoying WiFi connection message is stored in ToC 30 (Signature `WIfi`) for\ngerman audio and in ToC 01 (Signature `WIFI`) for english audio.\n\nThe fix is to change the `WIfi` signature to something meaningless (`FOOO`)\nin the ToC and flash the modified image file back to the flash chip\n(only 4 bytes changed compared to the vendor image).\nThe amplifier driver then will not be able to find the audio\ndata and ... you will _really_ be able to enjoy WiFi music _undisturbed_ :)\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhn%2Flinkplay-a31","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhn%2Flinkplay-a31","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhn%2Flinkplay-a31/lists"}