This is a Rust-Written proxy tool to bridge between wireless Android phone and a USB-wired car head unit for using Google's Android Auto.
Currently it is intended to run as a more-or-less drop-in replacement of the aawgd
from the WirelessAndroidAutoDongle project.
- written in Rust: reliable code and minimized all memory related issues and bugs
- fast: main IO loop is using modern io_uring kernel API
- reconnecting: trying to reconnect/recover AndroidAuto connection in any possible case
- bandwidth/transfer statistics
- stall transfer detection
Now after a lot of stress-testing and coding I think the project has matured enough, that I can say that the main stability goal was reached. I am using this project almost daily in my car and trying to get rid of all issues I may encounter.
I am using Nisarg's RaspberryPi images from WirelessAndroidAutoDongle project and replacing aawgd
with aa-proxy-rs
.
Those images are available on the Release page.
You can also find there a pure aa-proxy-rs
binary which you can install manually on targets with similar libc versions, or build your own (read below: Installing into target and Building).
There are a lot of commercial solutions like AAWireless or Motorola MA1. I even bought a clone of those on AliExpress, but it ended up not working in my car (passed to my friend which has a compatible car for this).
Then thanks to Nicnl from reddit under my post,
I headed to a great open source solution, based on Raspberry Pi hardware:
WirelessAndroidAutoDongle by Nisarg Jhaveri
The author made a lot of great research and created a DIY working solution, and - what is most important - he shared his work to the public. This is so cool and I really appreciate his work! Because it's open source, I was even able to run my own additional LoRa hardware on the same Raspberry Pi for different purpose!
The original project is using aawgd
daemon which is doing the necessary proxying of data between the phone and USB port.
(Un)fortunately the project was not always working reliable for me (crashing, unable to reconnect, need restarting, etc.).
Finally after making this PR, I decided that I will try to make the Rust-based alternative.
And this is where this project begins and I started to reimplement the aawgd
C++ code into rust application.
I was trying to simplify things and original code where it was possible. It was also a great opportunity and fun to learn how it was designed and how it is working, but I have to admit that I was struggling a lot with doing a final forwarding I/O: normally I would just use copy_bidirectional for this as the whole code is async and using tokio, but the problem with it was, that the USB socket for the "usb-gadget" kernel module seems to be not compatible with this approach (probably some polling/epoll problems). I was also trying to call read/writes in tokio tasks, but finally I decided to use different approach: using modern io_uring kernel API provided by tokio_uring. And this finally worked perfectly fine (and also really efficient as a bonus).
- Currently only the default "connection strategy" is supported.
- Because the project main functionality (data transfer) is dependent on kernel io_uring API, the Linux kernel has to be in version 5.10 or later.
- My time resources are limited, so don't expect to prompt answers and ETAs on different requests. I am doing this as a hobby in my spare time.
The whole connection process is not trivial and quite complex. Here I am listing the needed steps the app is doing from the start to make a connection:
- USB: disabling all gadgets
- USB: registering uevents (for receiving USB state changes)
- Starting local TCP server
- Bluetooth: powering up the bluetooth adapter and make it discoverable and pairable
- Bluetooth: registering two profiles: one for android auto, and the other for fake headset (fooling the phone we are supported wireless AndroidAuto head unit)
- When a phone connects to the AA profile the app is sending two frames of specific google protocol data:
- WifiStartRequest: with IP address and port of the destination TCP server/connection
- WifiInfoResponse: with Access Point information for the WiFi connection
- after successfull response for the above, the tool is disabling bluetooth
- the phone is connecting to car's head unit bluetooth (e.g. for phone calls)
- in the same time the phone is connecting via WiFi to our TCP server on specified port
- USB: switching to "default" followed by "accessory" gadgets to enable proper USB mode for data transmission to the car head unit (fooling that we are the android phone connected via USB)
- final (and normal working stage): bidirectional forwarding the data between TCP client (phone) and USB port (car)
USB is the active part here (starting the transmission by sending 10-bytes first frame), so all of this has to be done with well timing, i.e., if we start the USB dongle connection too fast (when the phone is not yet connected), then it would start the transmission, and we don't have a TCP socket ready; similar in the other direction: when the phone starts too fast, and we are not ready with USB connection then we can't send data to the phone and Android will close/timeout the connection.
rpi02w
binaries build by WirelessAndroidAutoDongle are for arm-unknown-linux-gnueabihf
32-bit architecture, probably
because of usb-gadget module incompatibility.
To be able to properly crosscompile output binary I provided .cargo/config.toml
with target set for this specific arch.
- Installing Rust
gcc-arm-linux-gnueabihf
package is needed on Debian. This is distro-depended so I recommend to RTFM.
To compile you need to add proper rustup target with:
rustup target add arm-unknown-linux-gnueabihf
and make sure that it is installed on target list:
arm-unknown-linux-gnueabihf (installed)
and then use:
cargo build --release
To compile a STATIC aa-proxy-rs
binary (to be able to use it on target with older OSes and different libc versions), you need to compile with:
RUSTFLAGS='-C target-feature=+crt-static' cargo build --release
To build with Docker you need to have a buildx and BuildKit.
Docker container is also preparing an SD card images based on @nisargjhaveri's latests assets.
It has to loop-mount that images, thus an insecure builder is needed.
To sum it up - the following commands are needed when building for the first time:
mkdir out
docker buildx create --use --name insecure-builder --buildkitd-flags '--allow-insecure-entitlement security.insecure'
docker buildx build --builder insecure-builder --allow security.insecure --output out .
After successful execution the resulting aa-proxy-rs
and SD card images will be in out
directory.
If you currently using a Raspberry Pi with working WirelessAndroidAutoDongle, then you can also manually install aa-proxy-rs
:
You need to transfer the resulting output binary to the target filesystem and start it. I am using ssh/scp for this, but it should be also possible with wget
.
You can also do it "offline" by making a changes directly on the SD card: mounting system partition and make necessary changes.
Sample startup script is provided for convenience.
Example steps:
- put
aa-proxy-rs
into /usr/bin - put
S93aa-proxy-rs
into /etc/init.d - remove or disable /etc/init.d/S93aawgd
Startup parameters (see below) are defined here.
aa-proxy-rs 0.2.0
AndroidAuto wired/wireless proxy
USAGE:
aa-proxy-rs [OPTIONS]
OPTIONS:
-a, --advertise
BLE advertising
-b, --btalias <BTALIAS>
BLE device name
-c, --connect <CONNECT>
Auto-connect to saved phone or specified phone MAC address if provided
-d, --debug
Enable debug info
-h, --help
Print help information
--hostapd-conf <HOSTAPD_CONF>
hostapd.conf file location [default: /etc/hostapd.conf]
-i, --iface <IFACE>
WLAN / Wi-Fi Hotspot interface [default: wlan0]
-k, --keepalive
Keep alive mode: BLE adapter doesn't turn off after successful connection, so that the
phone can remain connected (used in special configurations)
-l, --legacy
Enable legacy mode
-l, --logfile <LOGFILE>
Log file path [default: /var/log/aa-proxy-rs.log]
-s, --stats-interval <SECONDS>
Interval of showing data transfer statistics (0 = disabled) [default: 0]
-t, --timeout-secs <SECONDS>
Data transfer timeout [default: 5]
-u, --udc <UDC>
UDC Controller name
-V, --version
Print version information
Most options are self explanatory, but these needs some more attention:
-
-l, --legacy
Originalaawgd
is using two USB gadgets: default and accessory. When connecting to car headunit, it switches first to default then to accessory. During my development I found out that my car headunit doesn't need this switching. It is working fine connecting directly to accessory gadget. Moreover with this approach it is much faster and doesn't need to wait for USB events in dedicated UEvent thread. As the result I decided to leave the old (legacy) code under this switch for compatibility with some headunits.
In short: if you have problems with USB connection try to enable the legacy mode. -
-c, --connect <CONNECT>
By default without this switch the aa-proxy-rs is starting but it is only visible as a bluetooth dongle, to which you have to connect manually from your phone to initiate AndroidAuto connection. If I am correct this was calleddongle mode
inaawgd
.
If you provide-c
switch without any additional address, then the daemon is trying to connect to known (paired?) bluetooth devices (phones) in a loop (the bluetoothd have a cached list of recently connected devices in /var/lib/bluetooth). This is the default mode foraawgd
for the time I am writing this.
If you provide-c MAC_ADDRESS
where MAC_ADDRESS is the MAC of your phone (bluetooth), then the aa-proxy-rs will try to connect only to this specified device in a loop (ignoring all bluetoothd cached devices).
Sometimes deleting the system Bluetooth cache at /var/lib/bluetooth and restarting bluetoothd fixes persistent issues with device connectivity. Consider also using "Forget" of bluetooth device in the Android phone.
Application by default is logging into /var/log/aa-proxy-rs.log file. This log could be helpful when trying to solve issues. If you want to get logs out of device, read here how to obtain it.
Sometimes it is desirable (because of SD cards longevity) to make a whole system read-only. This would also help because we don't have any control when the car headunit is powering off the dongle (USB port).
In some corner cases the filesystem could be damaged because the system is not properly shutdown and unmounted.
When you have the dongle set up properly and it is working as intended (you was connecting with your phone to the car, BT was paired, AA is working) you can make the following changes in the SD card:
Partition #1 (boot):
edit the cmdline.txt
file and add ro
at the end of the line
Partition #2 (main filesystem):
--- old/etc/fstab 2024-03-30 17:44:15.000000000 +0100
+++ new/etc/fstab 2024-05-03 16:33:48.083059982 +0200
@@ -1,5 +1,5 @@
# <file system> <mount pt> <type> <options> <dump> <pass>
-/dev/root / ext2 rw,noauto 0 1
+/dev/root / ext2 ro,noauto 0 1
proc /proc proc defaults 0 0
devpts /dev/pts devpts defaults,gid=5,mode=620,ptmxmode=0666 0 0
tmpfs /dev/shm tmpfs mode=0777 0 0
diff -Nru 22/etc/inittab pizero-aa-backup/p2/etc/inittab
--- old/etc/inittab 2024-03-30 18:57:51.000000000 +0100
+++ new/etc/inittab 2024-05-03 16:45:24.184119996 +0200
@@ -15,7 +15,7 @@
# Startup the system
::sysinit:/bin/mount -t proc proc /proc
-::sysinit:/bin/mount -o remount,rw /
+#::sysinit:/bin/mount -o remount,rw /
::sysinit:/bin/mkdir -p /dev/pts /dev/shm
::sysinit:/bin/mount -a
::sysinit:/bin/mkdir -p /run/lock/subsys
Again: before doing this, make sure that you've connect your phone at least once and all is working fine, specifically the /var/lib/bluetooth/
directory is populated with your phone pairing information.
This way after reboot all partitions will stay in read-only mode and should work longer and without possible problems.
If you want to make some changes to the filesystem or pair new phone you should revert those changes and it will be read-write again.
It should be also possible to ssh
and execute:
mount -o remount,rw /
to make root filesystem read-write again temporarily.