~aleteoryx/muditaos

bb6989c2a84746038de75a5c6816960042b0b3d8 — Hubert Chrzaniuk 5 years ago 4352643
[EGD-4978] Add Bluetooth virtual audio device

Bluetooth audio device requires different handling than other
audio devices. The commit adds proxy device that does not
handle requests itself but instead sends requests too Bluetooth
service.
M changelog.md => changelog.md +1 -0
@@ 6,6 6,7 @@

* `[PowerManagement]` Critial battery level notification to SystemManager.
* `[Bluetooth]`  Add settings storage to bluetooth related items
* Add Bluetooth virtual audio device.

## [0.51.1 2020-12-18]


M module-audio/Audio/Audio.cpp => module-audio/Audio/Audio.cpp +0 -9
@@ 93,10 93,6 @@ namespace audio
            currentOperation->SetDataStreams(&dataStreamOut, &dataStreamIn);

            UpdateProfiles();

            if (btData) {
                currentOperation->SetBluetoothStreamData(btData);
            }
        }
        catch (const AudioInitException &audioException) {
            // If creating operation failed fallback to IdleOperation which is guaranteed to work


@@ 163,11 159,6 @@ namespace audio
        return SetOutputVolume(0);
    }

    void Audio::SetBluetoothStreamData(std::shared_ptr<BluetoothStreamData> data)
    {
        btData = data;
    }

    void Audio::UpdateProfiles()
    {
        auto updateEvents = audioSinkState.getUpdateEvents();

M module-audio/Audio/Audio.hpp => module-audio/Audio/Audio.hpp +0 -2
@@ 105,8 105,6 @@ namespace audio

        virtual audio::RetCode Mute();

        virtual void SetBluetoothStreamData(std::shared_ptr<BluetoothStreamData> data);

      private:
        void UpdateProfiles();
        AudioSinkState audioSinkState;

M module-audio/Audio/AudioCommon.hpp => module-audio/Audio/AudioCommon.hpp +5 -11
@@ 8,6 8,7 @@
#include <bsp/audio/bsp_audio.hpp>
#include <Utils.hpp>
#include <utility>
#include <Service/Message.hpp>

#include "Profiles/Profile.hpp"



@@ 249,14 250,7 @@ namespace audio

namespace AudioServiceMessage
{

    class Message
    {
      public:
        virtual ~Message() = default;
    };

    class EndOfFile : public Message
    class EndOfFile : public sys::Message
    {
      public:
        explicit EndOfFile(audio::Token &token) : token(token)


@@ 270,7 264,7 @@ namespace AudioServiceMessage
        audio::Token token = audio::Token::MakeBadToken();
    };

    class FileSystemNoSpace : public Message
    class FileSystemNoSpace : public sys::Message
    {
      public:
        explicit FileSystemNoSpace(audio::Token &token) : token(token)


@@ 284,7 278,7 @@ namespace AudioServiceMessage
        audio::Token token = audio::Token::MakeBadToken();
    };

    class DbRequest : public Message
    class DbRequest : public sys::Message
    {
      public:
        explicit DbRequest(std::string path) : path(std::move(path))


@@ 298,5 292,5 @@ namespace AudioServiceMessage
        const std::string path;
    };

    using Callback = std::function<std::optional<std::string>(const Message *e)>;
    using Callback = std::function<std::optional<std::string>(const sys::Message *msg)>;
} // namespace AudioServiceMessage

A module-audio/Audio/BluetoothProxyAudio.cpp => module-audio/Audio/BluetoothProxyAudio.cpp +75 -0
@@ 0,0 1,75 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "BluetoothProxyAudio.hpp"
#include <service-bluetooth/BluetoothMessage.hpp>

namespace bsp
{
    BluetoothProxyAudio::BluetoothProxyAudio(AudioServiceMessage::Callback callback,
                                             audio::Stream &dataStreamOut,
                                             audio::Stream &dataStreamIn,
                                             AudioDevice::Format &format)
        : AudioDevice(nullptr), dataStreamOut(dataStreamOut), dataStreamIn(dataStreamIn), serviceCallback(callback),
          audioFormat(format)
    {
        LOG_DEBUG("BluetoothProxyAudio created.");
    }

    AudioDevice::RetCode BluetoothProxyAudio::Start(const AudioDevice::Format &format)
    {
        auto msg = BluetoothProxyStartMessage(dataStreamOut, dataStreamIn, format);
        serviceCallback(&msg);
        return AudioDevice::RetCode::Success;
    }

    AudioDevice::RetCode BluetoothProxyAudio::Stop()
    {
        auto msg = BluetoothProxyStopMessage(audioFormat);
        serviceCallback(&msg);
        return AudioDevice::RetCode::Success;
    }

    AudioDevice::RetCode BluetoothProxyAudio::OutputVolumeCtrl(float vol)
    {
        audioFormat.outputVolume = vol;
        auto msg                 = BluetoothProxySetVolumeMessage(audioFormat);
        serviceCallback(&msg);
        return AudioDevice::RetCode::Success;
    }

    AudioDevice::RetCode BluetoothProxyAudio::InputGainCtrl(float gain)
    {
        audioFormat.inputGain = gain;
        auto msg              = BluetoothProxySetGainMessage(audioFormat);
        serviceCallback(&msg);
        return AudioDevice::RetCode::Success;
    }

    AudioDevice::RetCode BluetoothProxyAudio::OutputPathCtrl(AudioDevice::OutputPath outputPath)
    {
        audioFormat.outputPath = outputPath;
        auto msg               = BluetoothProxySetOutputPathMessage(audioFormat);
        serviceCallback(&msg);
        return AudioDevice::RetCode::Success;
    }

    AudioDevice::RetCode BluetoothProxyAudio::InputPathCtrl(AudioDevice::InputPath inputPath)
    {
        audioFormat.inputPath = inputPath;
        auto msg              = BluetoothProxySetInputPathMessage(audioFormat);
        serviceCallback(&msg);
        return AudioDevice::RetCode::Success;
    }

    bool BluetoothProxyAudio::IsFormatSupported(const AudioDevice::Format &format)
    {
        LOG_DEBUG("Format assumed to be supported");
        return true;
    }

    BluetoothProxyAudio::~BluetoothProxyAudio()
    {
        Stop();
    }
} // namespace bsp

A module-audio/Audio/BluetoothProxyAudio.hpp => module-audio/Audio/BluetoothProxyAudio.hpp +38 -0
@@ 0,0 1,38 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "AudioCommon.hpp"
#include "Stream.hpp"

#include "bsp/audio/bsp_audio.hpp"

namespace bsp
{
    class BluetoothProxyAudio final : public AudioDevice
    {
      public:
        BluetoothProxyAudio(AudioServiceMessage::Callback callback,
                            audio::Stream &dataStreamOut,
                            audio::Stream &dataStreamIn,
                            AudioDevice::Format &format);

        ~BluetoothProxyAudio() final;

        AudioDevice::RetCode Start(const Format &format) final;
        AudioDevice::RetCode Stop() final;
        AudioDevice::RetCode OutputVolumeCtrl(float vol) final;
        AudioDevice::RetCode InputGainCtrl(float gain) final;
        AudioDevice::RetCode OutputPathCtrl(OutputPath outputPath) final;
        AudioDevice::RetCode InputPathCtrl(InputPath inputPath) final;
        bool IsFormatSupported(const Format &format) final;

      private:
        audio::Stream &dataStreamOut;
        audio::Stream &dataStreamIn;
        AudioServiceMessage::Callback serviceCallback;
        AudioDevice::Format audioFormat;
    };

} // namespace bsp

M module-audio/Audio/Operation/IdleOperation.hpp => module-audio/Audio/Operation/IdleOperation.hpp +0 -3
@@ 57,9 57,6 @@ namespace audio
        {
            return 0.0;
        }

        void SetBluetoothStreamData(std::shared_ptr<BluetoothStreamData> data) final
        {}
    };

} // namespace audio

M module-audio/Audio/Operation/Operation.cpp => module-audio/Audio/Operation/Operation.cpp +11 -12
@@ 10,10 10,9 @@
#include "RecorderOperation.hpp"
#include "RouterOperation.hpp"

#include "Audio/decoder/Decoder.hpp"
#include "Audio/BluetoothProxyAudio.hpp"

#include <bsp/audio/bsp_audio.hpp>
#include <module-bsp/board/rt1051/bsp/audio/RT1051BluetoothAudio.hpp>

namespace audio
{


@@ 57,16 56,6 @@ namespace audio
        }
    }

    void Operation::SetBluetoothStreamData(std::shared_ptr<BluetoothStreamData> data)
    {
        if (auto device = dynamic_cast<bsp::RT1051BluetoothAudio *>(audioDevice.get()); device != nullptr) {
            GetProfile()->SetSampleRate(data->metadata.sampleRate);
            device->sourceQueue = data->out;
            device->metadata    = data->metadata;
            LOG_INFO("Queue and metadata set!");
        }
    }

    audio::RetCode Operation::SwitchToPriorityProfile()
    {
        for (auto &p : supportedProfiles) {


@@ 104,4 93,14 @@ namespace audio

        supportedProfiles.emplace_back(Profile::Create(profile, nullptr, volume, gain), isAvailable);
    }
    std::optional<std::unique_ptr<bsp::AudioDevice>> Operation::CreateDevice(bsp::AudioDevice::Type type,
                                                                             bsp::AudioDevice::audioCallback_t callback)
    {
        if (type == bsp::AudioDevice::Type::Bluetooth) {
            auto audioFormat = currentProfile->GetAudioFormat();
            return std::make_unique<bsp::BluetoothProxyAudio>(
                serviceCallback, *dataStreamOut, *dataStreamIn, audioFormat);
        }
        return bsp::AudioDevice::Create(type, callback).value_or(nullptr);
    }
} // namespace audio

M module-audio/Audio/Operation/Operation.hpp => module-audio/Audio/Operation/Operation.hpp +9 -10
@@ 69,7 69,6 @@ namespace audio
        virtual audio::RetCode SetInputGain(float gain)                                 = 0;

        virtual Position GetPosition() = 0;
        virtual void SetBluetoothStreamData(std::shared_ptr<BluetoothStreamData> data);

        Volume GetOutputVolume() const
        {


@@ 135,26 134,26 @@ namespace audio

        std::shared_ptr<Profile> currentProfile;
        std::unique_ptr<bsp::AudioDevice> audioDevice;

        std::vector<SupportedProfile> supportedProfiles;
        void SetProfileAvailability(std::vector<Profile::Type> profiles, bool available);
        void AddProfile(const Profile::Type &profile, const PlaybackType &playback, bool isAvailable);

        State state = State::Idle;

        audio::Token operationToken;
        Type opType = Type::Idle;
        std::string filePath;

        audio::PlaybackType playbackType = audio::PlaybackType::None;

        virtual audio::RetCode SwitchProfile(const Profile::Type type) = 0;

        std::shared_ptr<Profile> GetProfile(const Profile::Type type);

        AudioServiceMessage::Callback serviceCallback;
        std::function<int32_t(const void *inputBuffer, void *outputBuffer, unsigned long framesPerBuffer)>
            audioCallback = nullptr;

        void SetProfileAvailability(std::vector<Profile::Type> profiles, bool available);
        void AddProfile(const Profile::Type &profile, const PlaybackType &playback, bool isAvailable);

        virtual audio::RetCode SwitchProfile(const Profile::Type type) = 0;
        std::shared_ptr<Profile> GetProfile(const Profile::Type type);

        std::optional<std::unique_ptr<bsp::AudioDevice>> CreateDevice(bsp::AudioDevice::Type type,
                                                                      bsp::AudioDevice::audioCallback_t callback);
    };

} // namespace audio

M module-audio/Audio/Operation/PlaybackOperation.cpp => module-audio/Audio/Operation/PlaybackOperation.cpp +1 -1
@@ 168,7 168,7 @@ namespace audio
            dec->disconnectStream();
        }

        audioDevice = bsp::AudioDevice::Create(currentProfile->GetAudioDeviceType(), audioCallback).value_or(nullptr);
        audioDevice = CreateDevice(currentProfile->GetAudioDeviceType(), audioCallback).value_or(nullptr);
        if (audioDevice == nullptr) {
            LOG_ERROR("Error creating AudioDevice");
            return RetCode::Failed;

M module-audio/Audio/Operation/PlaybackOperation.hpp => module-audio/Audio/Operation/PlaybackOperation.hpp +0 -2
@@ 10,8 10,6 @@
#include "Audio/StreamQueuedEventsListener.hpp"
#include "Audio/decoder/Decoder.hpp"

#include <bsp/audio/bsp_audio.hpp>

namespace audio::playbackDefaults
{
    constexpr audio::Volume defaultLoudspeakerVolume = 10;

M module-audio/Audio/Operation/RecorderOperation.cpp => module-audio/Audio/Operation/RecorderOperation.cpp +1 -1
@@ 157,7 157,7 @@ namespace audio
            return RetCode::UnsupportedProfile;
        }

        audioDevice = AudioDevice::Create(currentProfile->GetAudioDeviceType(), audioCallback).value_or(nullptr);
        audioDevice = CreateDevice(currentProfile->GetAudioDeviceType(), audioCallback).value_or(nullptr);
        if (audioDevice == nullptr) {
            LOG_ERROR("Error creating AudioDevice");
            return RetCode::Failed;

M module-audio/Audio/Operation/RouterOperation.cpp => module-audio/Audio/Operation/RouterOperation.cpp +2 -3
@@ 158,13 158,12 @@ namespace audio
            return RetCode::UnsupportedProfile;
        }

        audioDevice = bsp::AudioDevice::Create(currentProfile->GetAudioDeviceType(), nullptr).value_or(nullptr);
        audioDevice = CreateDevice(currentProfile->GetAudioDeviceType(), nullptr).value_or(nullptr);
        if (audioDevice == nullptr) {
            LOG_ERROR("Error creating AudioDevice");
            return RetCode::Failed;
        }

        audioDeviceCellular = bsp::AudioDevice::Create(bsp::AudioDevice::Type::Cellular, nullptr).value_or(nullptr);
        audioDeviceCellular = CreateDevice(bsp::AudioDevice::Type::Cellular, nullptr).value_or(nullptr);
        if (audioDeviceCellular == nullptr) {
            LOG_ERROR("Error creating AudioDeviceCellular");
            return RetCode::Failed;

M module-audio/Audio/test/unittest_audio.cpp => module-audio/Audio/test/unittest_audio.cpp +2 -2
@@ 74,8 74,8 @@ class MockAudio : public audio::Audio

  public:
    MockAudio(audio::Audio::State state, audio::PlaybackType plbckType, audio::Operation::State opState)
        : audio::Audio([](const AudioServiceMessage::Message *e) { return std::optional<std::string>(); }),
          state(state), plbckType(plbckType), opState(opState)
        : audio::Audio([](const sys::Message *e) { return std::optional<std::string>(); }), state(state),
          plbckType(plbckType), opState(opState)
    {}

    audio::PlaybackType GetCurrentOperationPlaybackType() const override

M module-audio/CMakeLists.txt => module-audio/CMakeLists.txt +1 -0
@@ 21,6 21,7 @@ set(SOURCES
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/Endpoint.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/Stream.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/StreamQueuedEventsListener.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/BluetoothProxyAudio.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/Operation/Operation.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/Operation/PlaybackOperation.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/Audio/Operation/RecorderOperation.cpp"

M module-bsp/bsp/audio/bsp_audio.cpp => module-bsp/bsp/audio/bsp_audio.cpp +1 -1
@@ 15,7 15,7 @@

namespace bsp{

    std::optional<std::unique_ptr<AudioDevice>> AudioDevice::Create(bsp::AudioDevice::Type type,audioCallback_t callback) {
    std::optional<std::unique_ptr<AudioDevice>> AudioDevice::Create(bsp::AudioDevice::Type type, audioCallback_t callback) {

        std::unique_ptr<AudioDevice> inst;


M module-bsp/targets/Target_Linux.cmake => module-bsp/targets/Target_Linux.cmake +0 -2
@@ 16,8 16,6 @@ set(BOARD_SOURCES

        "${CMAKE_CURRENT_SOURCE_DIR}/board/linux/audio/linux_audiocodec.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/board/linux/audio/LinuxCellularAudio.cpp"
        #needed because of PlaybackOperation::SetBluetoothStreamData
        "${CMAKE_CURRENT_SOURCE_DIR}/board/rt1051/bsp/audio/RT1051BluetoothAudio.cpp"

        "${CMAKE_CURRENT_SOURCE_DIR}/board/linux/bluetooth/Bluetooth.cpp"


M module-services/service-audio/ServiceAudio.cpp => module-services/service-audio/ServiceAudio.cpp +24 -13
@@ 107,7 107,7 @@ sys::ReturnCodes ServiceAudio::DeinitHandler()
    return sys::ReturnCodes::Success;
}

std::optional<std::string> ServiceAudio::AudioServicesCallback(const AudioServiceMessage::Message *msg)
std::optional<std::string> ServiceAudio::AudioServicesCallback(const sys::Message *msg)
{
    if (const auto *eof = dynamic_cast<const AudioServiceMessage::EndOfFile *>(msg); eof) {
        auto newMsg =


@@ 123,6 123,29 @@ std::optional<std::string> ServiceAudio::AudioServicesCallback(const AudioServic
        }
        return settings_it->second;
    }
    else if (const auto *btReq = dynamic_cast<const BluetoothProxyMessage *>(msg); btReq) {
        std::shared_ptr<BluetoothProxyMessage> request;
        if (const auto *btStart = dynamic_cast<const BluetoothProxyStartMessage *>(msg); btStart) {
            request = std::make_shared<BluetoothProxyStartMessage>(*btStart);
        }
        else if (const auto *btVolume = dynamic_cast<const BluetoothProxySetVolumeMessage *>(msg); btVolume) {
            request = std::make_shared<BluetoothProxySetVolumeMessage>(*btVolume);
        }
        else if (const auto *btGain = dynamic_cast<const BluetoothProxySetGainMessage *>(msg); btGain) {
            request = std::make_shared<BluetoothProxySetGainMessage>(*btGain);
        }
        else if (const auto *btOutPath = dynamic_cast<const BluetoothProxySetOutputPathMessage *>(msg); btOutPath) {
            request = std::make_shared<BluetoothProxySetOutputPathMessage>(*btOutPath);
        }
        else if (const auto *btInPath = dynamic_cast<const BluetoothProxySetInputPathMessage *>(msg); btInPath) {
            request = std::make_shared<BluetoothProxySetInputPathMessage>(*btInPath);
        }
        else {
            LOG_DEBUG("BluetoothProxyMessage not supported.");
            return std::nullopt;
        }
        sys::Bus::SendUnicast(request, service::name::bluetooth, this);
    }
    else {
        LOG_DEBUG("Message received but not handled - no effect.");
    }


@@ 328,9 351,6 @@ std::unique_ptr<AudioResponseMessage> ServiceAudio::HandleSendEvent(std::shared_

    for (auto &input : audioMux.GetAllInputs()) {
        input.audio->SendEvent(evt);
        if (isBT && evt->getDeviceState() == audio::Event::DeviceState::Connected) {
            input.audio->SetBluetoothStreamData(nullptr);
        }
    }
    return std::make_unique<AudioEventResponse>(RetCode::Success);
}


@@ 506,15 526,6 @@ sys::MessagePointer ServiceAudio::DataReceivedHandler(sys::DataMessage *msgl, sy
        auto *msg   = static_cast<AudioKeyPressedRequest *>(msgl);
        responseMsg = HandleKeyPressed(msg->step);
    }
    else if (msgType == typeid(BluetoothRequestStreamResultMessage)) {
        auto *msg = static_cast<BluetoothRequestStreamResultMessage *>(msgl);
        for (auto &input : audioMux.GetAllInputs()) {
            input.audio->SetBluetoothStreamData(msg->getData());
            input.audio->SendEvent(
                std::make_unique<Event>(EventType::BlutoothA2DPDeviceState, audio::Event::DeviceState::Connected));
            LOG_INFO("Queues received!");
        }
    }
    else {
        LOG_DEBUG("Unhandled message");
    }

M module-services/service-audio/service-audio/ServiceAudio.hpp => module-services/service-audio/service-audio/ServiceAudio.hpp +1 -1
@@ 56,7 56,7 @@ class ServiceAudio : public sys::Service
        return vibrationMotorStatus == audio::AudioMux::VibrationStatus::On;
    }

    auto AudioServicesCallback(const AudioServiceMessage::Message *msg) -> std::optional<std::string>;
    auto AudioServicesCallback(const sys::Message *msg) -> std::optional<std::string>;

    auto HandleStart(const audio::Operation::Type opType,
                     const std::string                       = "",

M module-services/service-bluetooth/service-bluetooth/BluetoothMessage.hpp => module-services/service-bluetooth/service-bluetooth/BluetoothMessage.hpp +73 -2
@@ 3,10 3,13 @@

#pragma once

#include <Bluetooth/Device.hpp>
#include <MessageType.hpp>
#include "ServiceBluetoothCommon.hpp"

#include <Bluetooth/Device.hpp>
#include <Service/Message.hpp>
#include <Audio/Stream.hpp>
#include <MessageType.hpp>
#include <bsp_audio.hpp>

#include <utility>
#include <vector>


@@ 126,3 129,71 @@ class BluetoothRequestStreamResultMessage : public sys::DataMessage
  private:
    std::shared_ptr<BluetoothStreamData> data;
};

class BluetoothProxyMessage : public sys::DataMessage
{
  public:
    BluetoothProxyMessage(MessageType messageType, bsp::AudioDevice::Format format)
        : DataMessage(messageType), format(format){};

    ~BluetoothProxyMessage() override = default;

    bsp::AudioDevice::Format format;
};

/// Bluetooth proxy messages

class BluetoothProxyStartMessage : public BluetoothProxyMessage
{
  public:
    BluetoothProxyStartMessage(audio::Stream &streamOut, audio::Stream &streamIn, bsp::AudioDevice::Format format)
        : BluetoothProxyMessage(MessageType::BluetoothProxyStart, format), audioStreamOut(streamOut),
          audioStreamIn(streamIn){};

    ~BluetoothProxyStartMessage() override = default;

    audio::Stream &audioStreamOut;
    audio::Stream &audioStreamIn;
};

class BluetoothProxyStopMessage : public BluetoothProxyMessage
{
  public:
    BluetoothProxyStopMessage(bsp::AudioDevice::Format format)
        : BluetoothProxyMessage(MessageType::BluetoothProxyStop, format){};
    ~BluetoothProxyStopMessage() override = default;
};

class BluetoothProxySetVolumeMessage : public BluetoothProxyMessage
{
  public:
    BluetoothProxySetVolumeMessage(bsp::AudioDevice::Format format)
        : BluetoothProxyMessage(MessageType::BluetoothProxyOutputVolumeCtrl, format){};
    ~BluetoothProxySetVolumeMessage() override = default;
};

class BluetoothProxySetGainMessage : public BluetoothProxyMessage
{
  public:
    BluetoothProxySetGainMessage(bsp::AudioDevice::Format format)
        : BluetoothProxyMessage(MessageType::BluetoothProxyInputGainCtrl, format){};
    ~BluetoothProxySetGainMessage() override = default;

    float value;
};

class BluetoothProxySetOutputPathMessage : public BluetoothProxyMessage
{
  public:
    BluetoothProxySetOutputPathMessage(bsp::AudioDevice::Format format)
        : BluetoothProxyMessage(MessageType::BluetoothProxyOutputPathCtrl, format){};
    ~BluetoothProxySetOutputPathMessage() override = default;
};

class BluetoothProxySetInputPathMessage : public BluetoothProxyMessage
{
  public:
    BluetoothProxySetInputPathMessage(bsp::AudioDevice::Format format)
        : BluetoothProxyMessage(MessageType::BluetoothProxyInputPathCtrl, format){};
    ~BluetoothProxySetInputPathMessage() override = default;
};

M source/MessageType.hpp => source/MessageType.hpp +8 -0
@@ 192,6 192,14 @@ enum class MessageType
    BluetoothDeviceMetadata,
    BluetoothRequestStream,

    // bluetooth proxy
    BluetoothProxyStart,
    BluetoothProxyStop,
    BluetoothProxyOutputVolumeCtrl,
    BluetoothProxyInputGainCtrl,
    BluetoothProxyOutputPathCtrl,
    BluetoothProxyInputPathCtrl,

    LwIP_request,
    EVM_GPIO,
    SIMTrayEvent,