#[cfg(feature = "wireless")]
use bluetooth_rust::{BluetoothAdapterTrait, MessageToBluetoothHost};
use ringbuf::traits::Producer;
use std::{collections::HashSet, sync::Arc};
use tokio::sync::Mutex;
use android_auto::{HeadUnitInfo, VideoConfiguration};
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use eframe::egui;
#[cfg(feature = "wireless")]
mod nmrs_extensions;
#[cfg(feature = "wireless")]
async fn get_wifi_interface(nmrs: &nmrs::NetworkManager) -> Option<nmrs::Device> {
if let Ok(devs) = nmrs.list_wireless_devices().await {
for dev in devs {
if dev.device_type == nmrs::DeviceType::Wifi {
log::info!("Found wifi device {:?}", dev);
return Some(dev);
}
}
}
None
}
type AudioProducer = ringbuf::HeapProd<i16>;
struct AndroidAutoInner {
relay: Option<tokio::task::JoinHandle<()>>,
connected: bool,
send: tokio::sync::mpsc::Sender<MessageFromAsync>,
arecv: Option<tokio::sync::mpsc::Receiver<android_auto::SendableAndroidAutoMessage>>,
android_send: tokio::sync::mpsc::Sender<android_auto::SendableAndroidAutoMessage>,
audio_input: Option<cpal::Device>,
media_stream: Option<(AudioProducer, cpal::Stream)>,
sys_stream: Option<(AudioProducer, cpal::Stream)>,
speech_stream: Option<(AudioProducer, cpal::Stream)>,
input_stream: Option<cpal::Stream>,
}
#[cfg(feature = "wireless")]
#[async_trait::async_trait]
impl android_auto::AndroidAutoWirelessTrait for AndroidAuto {
async fn setup_bluetooth_profile(
&self,
suggestions: &bluetooth_rust::BluetoothRfcommProfileSettings,
) -> Result<bluetooth_rust::BluetoothRfcommProfileAsync, String> {
if let Some(b) = self.bluetooth.supports_async() {
b.register_rfcomm_profile(suggestions.clone()).await
} else {
Err("Async not supported".to_string())
}
}
fn get_wifi_details(&self) -> android_auto::NetworkInformation {
self.network.as_ref().to_owned()
}
}
#[derive(Clone)]
struct AndroidAuto {
inner: Arc<Mutex<AndroidAutoInner>>,
config: VideoConfiguration,
#[cfg(feature = "wireless")]
blue: android_auto::BluetoothInformation,
#[cfg(feature = "wireless")]
bluetooth: Arc<bluetooth_rust::BluetoothAdapter>,
#[cfg(feature = "wireless")]
network: Arc<android_auto::NetworkInformation>,
sensors: android_auto::SensorInformation,
input_config: android_auto::InputConfiguration,
}
enum MessageFromAsync {
VideoData {
data: Vec<u8>,
_timestamp: Option<u64>,
},
Connected,
Disconnected,
ExitContainer,
}
enum MessageToAsync {
AndroidAutoMessage(android_auto::SendableAndroidAutoMessage),
}
#[async_trait::async_trait]
impl android_auto::AndroidAutoVideoChannelTrait for AndroidAuto {
async fn receive_video(&self, data: Vec<u8>, timestamp: Option<u64>) {
let i = self.inner.lock().await;
let _ = i
.send
.send(MessageFromAsync::VideoData {
data,
_timestamp: timestamp,
})
.await;
}
async fn setup_video(&self) -> Result<(), ()> {
Ok(())
}
async fn teardown_video(&self) {}
async fn wait_for_focus(&self) {}
async fn set_focus(&self, _focus: bool) {}
fn retrieve_video_configuration(&self) -> &VideoConfiguration {
&self.config
}
}
#[cfg(feature = "wireless")]
#[async_trait::async_trait]
impl android_auto::AndroidAutoBluetoothTrait for AndroidAuto {
async fn do_stuff(&self) {}
fn get_config(&self) -> &android_auto::BluetoothInformation {
&self.blue
}
}
#[async_trait::async_trait]
impl android_auto::AndroidAutoSensorTrait for AndroidAuto {
fn get_supported_sensors(&self) -> &android_auto::SensorInformation {
&self.sensors
}
async fn start_sensor(&self, stype: android_auto::Wifi::sensor_type::Enum) -> Result<(), ()> {
if self.sensors.sensors.contains(&stype) {
let mut m3 = android_auto::Wifi::SensorEventIndication::new();
match stype {
android_auto::Wifi::sensor_type::Enum::DRIVING_STATUS => {
let mut ds = android_auto::Wifi::DrivingStatus::new();
ds.set_status(android_auto::Wifi::DrivingStatusEnum::UNRESTRICTED as i32);
m3.driving_status.push(ds);
}
android_auto::Wifi::sensor_type::Enum::NIGHT_DATA => {
let mut ds = android_auto::Wifi::NightMode::new();
ds.set_is_night(false);
m3.night_mode.push(ds);
}
_ => {
todo!();
}
}
let s = self.inner.lock().await;
let m = android_auto::AndroidAutoMessage::Sensor(m3);
s.android_send.send(m.sendable()).await.map_err(|_| ())?;
Ok(())
} else {
Err(())
}
}
}
#[async_trait::async_trait]
impl android_auto::AndroidAutoAudioOutputTrait for AndroidAuto {
async fn open_output_channel(&self, _t: android_auto::AudioChannelType) -> Result<(), ()> {
Ok(())
}
async fn close_output_channel(&self, _t: android_auto::AudioChannelType) -> Result<(), ()> {
Ok(())
}
async fn receive_output_audio(&self, t: android_auto::AudioChannelType, data: Vec<u8>) {
let mut s = self.inner.lock().await;
let r2: Vec<i16> = data
.chunks_exact(2)
.map(|v| i16::from_le_bytes([v[0], v[1]]))
.collect();
match t {
android_auto::AudioChannelType::Media => {
s.media_stream.as_mut().map(|m| m.0.push_slice(&r2));
}
android_auto::AudioChannelType::System => {
s.sys_stream.as_mut().map(|m| m.0.push_slice(&r2));
}
android_auto::AudioChannelType::Speech => {
s.speech_stream.as_mut().map(|m| m.0.push_slice(&r2));
}
}
}
async fn start_output_audio(&self, t: android_auto::AudioChannelType) {
let s = self.inner.lock().await;
match t {
android_auto::AudioChannelType::Media => {
s.media_stream.as_ref().map(|m| m.1.play());
}
android_auto::AudioChannelType::System => {
s.sys_stream.as_ref().map(|m| m.1.play());
}
android_auto::AudioChannelType::Speech => {
s.speech_stream.as_ref().map(|m| m.1.play());
}
}
}
async fn stop_output_audio(&self, t: android_auto::AudioChannelType) {
let s = self.inner.lock().await;
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
match t {
android_auto::AudioChannelType::Media => {
s.media_stream.as_ref().map(|m| m.1.pause());
}
android_auto::AudioChannelType::System => {
s.sys_stream.as_ref().map(|m| m.1.pause());
}
android_auto::AudioChannelType::Speech => {
s.speech_stream.as_ref().map(|m| m.1.pause());
}
}
}
}
#[async_trait::async_trait]
impl android_auto::AndroidAutoInputChannelTrait for AndroidAuto {
async fn binding_request(&self, _code: u32) -> Result<(), ()> {
Ok(())
}
fn retrieve_input_configuration(&self) -> &android_auto::InputConfiguration {
&self.input_config
}
}
#[async_trait::async_trait]
impl android_auto::AndroidAutoAudioInputTrait for AndroidAuto {
async fn open_input_channel(&self) -> Result<(), ()> {
log::error!("Start audio input channel");
let mut s = self.inner.lock().await;
let config = cpal::StreamConfig {
channels: 1,
sample_rate: 16000,
buffer_size: cpal::BufferSize::Default,
};
if let Some(ai) = &s.audio_input {
let android_send = s.android_send.clone();
if let Ok(str) = ai.build_input_stream(
&config,
move |data: &[i16], _| {
let bytes: Vec<u8> = data.iter().flat_map(|s| s.to_le_bytes()).collect();
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_micros() as u64;
let msg = android_auto::AndroidAutoMessage::Audio(Some(timestamp), bytes);
if let Err(e) = android_send.try_send(msg.sendable()) {
log::warn!("Dropped audio input frame: {:?}", e);
}
},
|err| log::error!("Audio input error: {:?}", err),
None,
) {
let _ = str.play();
s.input_stream = Some(str);
} else {
log::error!("Failed to open input channel stream");
}
}
Ok(())
}
async fn close_input_channel(&self) -> Result<(), ()> {
let mut s = self.inner.lock().await;
s.input_stream.take();
Ok(())
}
async fn start_input_audio(&self) {}
async fn audio_input_ack(&self, chan: u8, ack: android_auto::Wifi::AVMediaAckIndication) {
log::info!("Ack audio input for chan {chan} {ack:?}");
}
async fn stop_input_audio(&self) {
log::error!("Stop audio input channel");
let mut s = self.inner.lock().await;
s.input_stream.take();
}
}
#[cfg(feature = "usb")]
#[async_trait::async_trait]
impl android_auto::AndroidAutoWiredTrait for AndroidAuto {}
#[async_trait::async_trait]
impl android_auto::AndroidAutoMainTrait for AndroidAuto {
async fn connect(&self) {
let mut i = self.inner.lock().await;
let _ = i.send.send(MessageFromAsync::Connected).await;
log::info!("Android auto connected");
i.connected = true;
}
async fn disconnect(&self) {
let mut s = self.inner.lock().await;
let _ = s.send.send(MessageFromAsync::Disconnected).await;
log::info!("Android auto disconnected");
s.connected = false;
}
async fn get_receiver(
&self,
) -> Option<tokio::sync::mpsc::Receiver<android_auto::SendableAndroidAutoMessage>> {
let mut s = self.inner.lock().await;
s.arecv.take()
}
#[cfg(feature = "wireless")]
fn supports_bluetooth(&self) -> Option<&dyn android_auto::AndroidAutoBluetoothTrait> {
Some(self)
}
#[cfg(feature = "wireless")]
fn supports_wireless(&self) -> Option<Arc<dyn android_auto::AndroidAutoWirelessTrait>> {
Some(Arc::new(self.clone()))
}
#[cfg(feature = "usb")]
fn supports_wired(&self) -> Option<Arc<dyn android_auto::AndroidAutoWiredTrait>> {
Some(Arc::new(self.clone()))
}
}
impl AndroidAuto {
fn new(
mut recv: tokio::sync::mpsc::Receiver<MessageToAsync>,
send: tokio::sync::mpsc::Sender<MessageFromAsync>,
#[cfg(feature = "wireless")] bluetooth: Arc<bluetooth_rust::BluetoothAdapter>,
#[cfg(feature = "wireless")] blue_address: String,
#[cfg(feature = "wireless")] network: android_auto::NetworkInformation,
android_recv: tokio::sync::mpsc::Receiver<android_auto::SendableAndroidAutoMessage>,
android_send: tokio::sync::mpsc::Sender<android_auto::SendableAndroidAutoMessage>,
) -> Self {
let mut s = HashSet::new();
s.insert(android_auto::Wifi::sensor_type::Enum::DRIVING_STATUS);
s.insert(android_auto::Wifi::sensor_type::Enum::NIGHT_DATA);
let android_send2 = android_send.clone();
let relay = tokio::spawn(async move {
'main_loop: loop {
while let Some(m) = recv.recv().await {
match m {
MessageToAsync::AndroidAutoMessage(android_auto_message) => {
let a = android_send2.send(android_auto_message).await;
if let Err(e) = a {
log::error!("Error relaying info {e:?}");
break 'main_loop;
}
}
}
}
}
});
let (ai, media_stream, sys_stream, speech_stream) = {
let h = cpal::default_host();
let mut ao = h.default_output_device();
let ai = h.default_input_device();
let mut media_stream = None;
let mut sys_stream = None;
let mut speech_stream = None;
if let Some(ao) = &mut ao {
if let Ok(c) = ao.supported_output_configs() {
{
let mut media_config = None;
let mut sys_config = None;
let mut speech_config = None;
for c in c {
const MEDIA_RATE: u32 = 48000;
const MEDIA_CHANNELS: u16 = 2;
if c.min_sample_rate() <= MEDIA_RATE
&& c.max_sample_rate() >= MEDIA_RATE
{
if c.channels() == MEDIA_CHANNELS {
if c.sample_format() == cpal::SampleFormat::I16 {
media_config = c.try_with_sample_rate(MEDIA_RATE);
}
}
}
const SYS_RATE: u32 = 16000;
const SYS_CHANNELS: u16 = 1;
if c.min_sample_rate() <= SYS_RATE && c.max_sample_rate() >= SYS_RATE {
if c.channels() == SYS_CHANNELS {
if c.sample_format() == cpal::SampleFormat::I16 {
sys_config = c.try_with_sample_rate(SYS_RATE);
}
}
}
const SPEECH_RATE: u32 = 16000;
const SPEECH_CHANNELS: u16 = 1;
if c.min_sample_rate() <= SPEECH_RATE
&& c.max_sample_rate() >= SPEECH_RATE
{
if c.channels() == SPEECH_CHANNELS {
if c.sample_format() == cpal::SampleFormat::I16 {
speech_config = c.try_with_sample_rate(SPEECH_RATE);
}
}
}
}
if let Some(mc) = media_config {
let rb = ringbuf::HeapRb::new(48000);
let (producer, mut consumer) = ringbuf::traits::Split::split(rb);
let s = ao.build_output_stream(
&mc.config(),
move |data: &mut [i16], _: &cpal::OutputCallbackInfo| {
let mut index = 0;
while index < data.len() {
let c = ringbuf::traits::Consumer::pop_slice(
&mut consumer,
&mut data[index..],
);
if c == 0 {
break;
}
index += c;
}
},
move |err| {
log::error!("Error in media audio output: {:?}", err);
},
None,
);
if let Ok(s) = s {
media_stream = Some((producer, s));
}
}
if let Some(mc) = sys_config {
let rb = ringbuf::HeapRb::new(16000);
let (producer, mut consumer) = ringbuf::traits::Split::split(rb);
let s = ao.build_output_stream(
&mc.config(),
move |data: &mut [i16], _: &cpal::OutputCallbackInfo| {
let mut index = 0;
while index < data.len() {
let c = ringbuf::traits::Consumer::pop_slice(
&mut consumer,
&mut data[index..],
);
if c == 0 {
break;
}
index += c;
}
},
move |err| {
log::error!("Error in media audio output: {:?}", err);
},
None,
);
if let Ok(s) = s {
sys_stream = Some((producer, s));
}
}
if let Some(mc) = speech_config {
let rb = ringbuf::HeapRb::new(16000);
let (producer, mut consumer) = ringbuf::traits::Split::split(rb);
let s = ao.build_output_stream(
&mc.config(),
move |data: &mut [i16], _: &cpal::OutputCallbackInfo| {
let mut index = 0;
while index < data.len() {
let c = ringbuf::traits::Consumer::pop_slice(
&mut consumer,
&mut data[index..],
);
if c == 0 {
break;
}
index += c;
}
},
move |err| {
log::error!("Error in media audio output: {:?}", err);
},
None,
);
if let Ok(s) = s {
speech_stream = Some((producer, s));
}
}
}
}
}
(ai, media_stream, sys_stream, speech_stream)
};
Self {
inner: Arc::new(Mutex::new(AndroidAutoInner {
relay: Some(relay),
connected: false,
send,
arecv: Some(android_recv),
android_send,
audio_input: ai,
media_stream,
sys_stream,
speech_stream,
input_stream: None,
})),
#[cfg(feature = "wireless")]
bluetooth,
#[cfg(feature = "wireless")]
network: Arc::new(network),
#[cfg(feature = "wireless")]
blue: android_auto::BluetoothInformation {
address: blue_address,
},
config: VideoConfiguration {
resolution: android_auto::Wifi::video_resolution::Enum::_480p,
fps: android_auto::Wifi::video_fps::Enum::_30,
dpi: 111,
},
sensors: android_auto::SensorInformation { sensors: s },
input_config: android_auto::InputConfiguration {
keycodes: vec![1, 2, 3, 4, 5],
touchscreen: Some((800, 480)),
},
}
}
async fn start_android_auto(
self,
config: android_auto::AndroidAutoConfiguration,
setup: android_auto::AndroidAutoSetup,
) -> Result<(), String> {
let mut joinset = tokio::task::JoinSet::new();
let relay = {
let mut s = self.inner.lock().await;
s.relay.take()
};
use android_auto::AndroidAutoMainTrait;
let b = Box::new(self);
let a = b.run(config, &mut joinset, &setup).await;
log::info!("join_all on the android auto joinset");
joinset.join_all().await;
log::info!("Done with join_all");
relay.map(|r| r.abort());
a
}
}
struct MyEguiApp {
android_auto_video_decoder: openh264::decoder::Decoder,
android_auto_texture: Option<egui::TextureHandle>,
container: Option<AndroidAutoContainer>,
setup: android_auto::AndroidAutoSetup,
}
impl MyEguiApp {
fn new(_cc: &eframe::CreationContext<'_>, setup: android_auto::AndroidAutoSetup) -> Self {
Self {
android_auto_video_decoder: openh264::decoder::Decoder::new().unwrap(),
android_auto_texture: None,
container: Some(AndroidAutoContainer::new(setup)),
setup,
}
}
}
impl eframe::App for MyEguiApp {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
let mut replace_container = false;
if let Some(con) = &mut self.container {
while let Ok(v) = con.recv.try_recv() {
match v {
MessageFromAsync::ExitContainer => {
log::info!("Got request to exit container");
replace_container = true;
}
MessageFromAsync::Connected => {
log::info!("Connected");
}
MessageFromAsync::Disconnected => {
log::info!("Android auto disconnected");
let _ = self.android_auto_video_decoder.flush_remaining();
self.android_auto_texture.take();
}
MessageFromAsync::VideoData {
data,
_timestamp: _,
} => {
let mut units = openh264::nal_units(&data).peekable();
while let Some(p) = units.next() {
match self.android_auto_video_decoder.decode(p) {
Err(e) => {
log::error!("Failed to decode android auto video {:?}", e);
}
Ok(Some(image)) => {
use openh264::formats::YUVSource;
let rgb_len = image.rgb8_len();
let mut rgb_raw = vec![0; rgb_len];
image.write_rgb8(&mut rgb_raw);
let (w, h) = image.dimensions_uv();
let pixels: Vec<egui::Color32> = rgb_raw
.chunks_exact(3)
.map(|i| egui::Color32::from_rgb(i[0], i[1], i[2]))
.collect();
let image = egui::ColorImage {
source_size: egui::Vec2 {
x: w as f32 * 2.0,
y: h as f32 * 2.0,
},
size: [w * 2usize, h * 2usize],
pixels,
};
if self.android_auto_texture.is_none() {
self.android_auto_texture = Some(ctx.load_texture(
"android_auto",
image,
egui::TextureOptions::LINEAR,
));
} else if let Some(t) = &mut self.android_auto_texture {
t.set_partial([0, 0], image, egui::TextureOptions::LINEAR);
}
}
_ => {}
}
}
}
}
}
}
if replace_container {
self.container = Some(AndroidAutoContainer::new(self.setup));
}
egui::CentralPanel::default().show(ctx, |ui| {
let size = ui.available_size();
if let Some(t) = &self.android_auto_texture {
ctx.request_repaint();
let isize = t.size();
let zoom = isize[1] as f32 / size.y;
let zoom2 = isize[0] as f32 / size.x;
let zoom = zoom.max(zoom2);
let dsize = t.size_vec2() / zoom;
let p = ui.cursor();
let r = ui.add(
egui::Image::from_texture(egui::load::SizedTexture {
id: t.id(),
size: dsize,
})
.sense(egui::Sense::drag()),
);
let o = if let Some(mut o) = r.interact_pointer_pos() {
o.x -= p.left();
o.y -= p.top();
o.x *= zoom;
o.y *= zoom;
Some(o)
} else if let Some(mut o) = r.hover_pos() {
o.x -= p.left();
o.y -= p.top();
o.x *= zoom;
o.y *= zoom;
Some(o)
} else {
None
};
if let Some(o) = o {
let mut i_event = android_auto::Wifi::InputEventIndication::new();
let timestamp: u64 = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_micros() as u64;
i_event.set_timestamp(timestamp);
let mut te = android_auto::Wifi::TouchEvent::new();
let mut tl = android_auto::Wifi::TouchLocation::new();
tl.set_x(o.x as u32);
tl.set_y(o.y as u32);
tl.set_pointer_id(0);
te.touch_location = vec![tl];
let mut do_touch = true;
if r.drag_started() {
te.set_touch_action(android_auto::Wifi::touch_action::Enum::POINTER_DOWN);
} else if r.drag_stopped() {
te.set_touch_action(android_auto::Wifi::touch_action::Enum::POINTER_UP);
} else if r.dragged() {
te.set_touch_action(android_auto::Wifi::touch_action::Enum::DRAG);
} else if r.hovered() {
te.set_touch_action(android_auto::Wifi::touch_action::Enum::DRAG);
} else {
do_touch = false;
}
if do_touch {
i_event.touch_event = android_auto::protobuf::MessageField::some(te);
let e = android_auto::AndroidAutoMessage::Input(i_event);
if let Some(con) = &mut self.container {
let a = con
.send
.blocking_send(MessageToAsync::AndroidAutoMessage(e.sendable()));
if let Err(e) = a {
log::error!("Error sending touch event {:?}", e);
}
}
}
}
}
});
}
}
struct AndroidAutoContainer {
thread: Option<std::thread::JoinHandle<Result<(), String>>>,
recv: tokio::sync::mpsc::Receiver<MessageFromAsync>,
send: tokio::sync::mpsc::Sender<MessageToAsync>,
kill: Option<tokio::sync::oneshot::Sender<()>>,
}
impl AndroidAutoContainer {
fn new(setup: android_auto::AndroidAutoSetup) -> Self {
let to_async = tokio::sync::mpsc::channel(50);
let from_async = tokio::sync::mpsc::channel(50);
let kill = tokio::sync::oneshot::channel::<()>();
let runtime_builder = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect("Failed to build the Tokio runtime");
let send_exit = from_async.0.clone();
let thread_handle = std::thread::spawn(move || {
let r = runtime_builder.block_on(async {
#[cfg(feature = "wireless")]
let wifi = nmrs::NetworkManager::new().await.expect("Wifi not found");
#[cfg(feature = "wireless")]
let wifi_dev = get_wifi_interface(&wifi)
.await
.expect("No wifi device found");
#[cfg(feature = "wireless")]
let hotspot_ssid = "Hotspot".to_string();
#[cfg(feature = "wireless")]
let hotspot_psk = "qwertyuiop".to_string();
#[cfg(feature = "wireless")]
nmrs_extensions::start_hotspot(
hotspot_ssid.clone(),
hotspot_psk.clone(),
&wifi_dev.path,
)
.await
.expect("Failed to build wifi hotspot");
#[cfg(feature = "wireless")]
let (mut bluechan, bluetooth) = {
let bluechan = tokio::sync::mpsc::channel(5);
let mut bluetooth = bluetooth_rust::BluetoothAdapterBuilder::new();
bluetooth.with_sender(bluechan.0);
let bluetooth = Arc::new(
bluetooth
.async_build()
.await
.expect("Could not open bluetooth"),
);
(bluechan.1, bluetooth)
};
#[cfg(feature = "wireless")]
{
if let Some(bluetooth) = bluetooth.supports_async() {
bluetooth
.set_discoverable(true)
.await
.expect("Failed to make bluetooth discoverable");
}
}
#[cfg(feature = "wireless")]
tokio::spawn(async move {
loop {
if let Some(m) = bluechan.recv().await {
match m {
MessageToBluetoothHost::DisplayPasskey(a, sender) => {
log::info!("Passkey is {}", a);
let _ =
sender.send(bluetooth_rust::ResponseToPasskey::Yes).await;
}
MessageToBluetoothHost::ConfirmPasskey(a, sender) => {
log::info!("Passkey is confirmed {}", a);
let _ =
sender.send(bluetooth_rust::ResponseToPasskey::Yes).await;
}
MessageToBluetoothHost::CancelDisplayPasskey => {
log::info!("Cancel show passkey");
}
}
}
}
});
#[cfg(feature = "wireless")]
let blue_addresses: Vec<bluetooth_rust::BluetoothAdapterAddress> = {
if let Some(bluetooth) = bluetooth.supports_async() {
bluetooth.addresses().await
} else {
panic!("Async not supported");
}
};
#[cfg(feature = "wireless")]
let bluetooth_address = blue_addresses
.first()
.map(|b| match b {
bluetooth_rust::BluetoothAdapterAddress::String(s) => s.to_owned(),
bluetooth_rust::BluetoothAdapterAddress::Byte(b) => {
let a = format!(
"{:02X}:{:02X}:{:02X}:{:02X}:{:02X}:{:02X}",
b[0], b[1], b[2], b[3], b[4], b[5]
);
a
}
})
.expect("No bluetooth hardware found");
let aauto = tokio::sync::mpsc::channel(50);
let aa = AndroidAuto::new(
to_async.1,
from_async.0,
#[cfg(feature = "wireless")]
bluetooth,
#[cfg(feature = "wireless")]
bluetooth_address,
#[cfg(feature = "wireless")]
android_auto::NetworkInformation {
ssid: hotspot_ssid,
psk: hotspot_psk,
mac_addr: wifi_dev.identity.current_mac.clone(),
ip: "10.42.0.1".to_string(),
port: 5277,
security_mode: android_auto::Bluetooth::SecurityMode::WPA2_PERSONAL,
ap_type: android_auto::Bluetooth::AccessPointType::STATIC,
},
aauto.1,
aauto.0,
);
let config = android_auto::AndroidAutoConfiguration {
unit: HeadUnitInfo {
name: "Example".to_string(),
car_model: "Example".to_string(),
car_year: "1943".to_string(),
car_serial: "42".to_string(),
left_hand: false,
head_manufacturer: "Example".to_string(),
head_model: "Example".to_string(),
sw_build: "37".to_string(),
sw_version: "1.2.3".to_string(),
native_media: true,
hide_clock: Some(true),
},
custom_certificate: None,
};
tokio::select! {
_ = aa.start_android_auto(config, setup) => {
log::info!("android auto exited");
}
_ = kill.1 => {
log::info!("Killing the android auto container");
}
}
Ok::<(), String>(())
});
log::info!("Exiting the android auto container thread");
send_exit
.blocking_send(MessageFromAsync::ExitContainer)
.map_err(|e| e.to_string())?;
r
});
Self {
thread: Some(thread_handle),
recv: from_async.1,
send: to_async.0,
kill: Some(kill.0),
}
}
}
impl Drop for AndroidAutoContainer {
fn drop(&mut self) {
let _ = self.kill.take().map(|s| s.send(()));
self.thread.take().map(|t| t.join());
}
}
fn main() -> Result<(), u32> {
simple_logger::SimpleLogger::new()
.with_level(log::LevelFilter::Info)
.init()
.unwrap();
let native_options = eframe::NativeOptions::default();
let setup = android_auto::setup();
let _ = eframe::run_native(
"Android auto demo",
native_options,
Box::new(move |cc| Ok(Box::new(MyEguiApp::new(cc, setup)))),
);
Ok(())
}