~aleteoryx/muditaos

1db3f192052d9290387a35e5880d5fd311cbc49a — Piotr Tański 5 years ago d87c694
[EGD-5472] Bluetooth worker state machine

State machine POC implemented using SML library.
M module-apps/application-settings-new/ApplicationSettings.cpp => module-apps/application-settings-new/ApplicationSettings.cpp +23 -1
@@ 116,7 116,8 @@ namespace app
        else if (auto responseStatusMsg = dynamic_cast<::message::bluetooth::ResponseStatus *>(msgl);
                 nullptr != responseStatusMsg) {
            if (gui::window::name::bluetooth == getCurrentWindow()->getName()) {
                auto btStatusData = std::make_unique<gui::BluetoothStatusData>(responseStatusMsg->getStatus());
                const auto status = responseStatusMsg->getStatus();
                auto btStatusData = std::make_unique<gui::BluetoothStatusData>(status.state, status.visibility);
                switchWindow(gui::window::name::bluetooth, std::move(btStatusData));
            }
        }


@@ 189,6 190,27 @@ namespace app
            ::settings::SystemProperties::lockPassHash,
            [this](std::string value) { lockPassHash = utils::getNumericValue<unsigned int>(value); },
            ::settings::SettingsScope::Global);
        settings->registerValueChange(
            ::settings::Bluetooth::state,
            [this](std::string value) {
                if (gui::window::name::bluetooth == getCurrentWindow()->getName()) {
                    const auto isBtOn = utils::getNumericValue<bool>(value);
                    auto btStatusData = std::make_unique<gui::BluetoothStatusData>(
                        isBtOn ? BluetoothStatus::State::On : BluetoothStatus::State::Off);
                    switchWindow(gui::window::name::bluetooth, std::move(btStatusData));
                }
            },
            ::settings::SettingsScope::Global);
        settings->registerValueChange(
            ::settings::Bluetooth::deviceVisibility,
            [this](std::string value) {
                if (gui::window::name::bluetooth == getCurrentWindow()->getName()) {
                    const auto isVisible = utils::getNumericValue<bool>(value);
                    auto btStatusData    = std::make_unique<gui::BluetoothStatusData>(isVisible);
                    switchWindow(gui::window::name::bluetooth, std::move(btStatusData));
                }
            },
            ::settings::SettingsScope::Global);

        return ret;
    }

M module-apps/application-settings-new/windows/BluetoothWindow.cpp => module-apps/application-settings-new/windows/BluetoothWindow.cpp +29 -19
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "BluetoothWindow.hpp"


@@ 22,9 22,13 @@ namespace gui
    void BluetoothWindow::onBeforeShow(ShowMode mode, SwitchData *data)
    {
        if (data != nullptr) {
            const auto newData        = static_cast<BluetoothStatusData *>(data);
            isBluetoothSwitchOn       = newData->getState();
            isPhoneVisibilitySwitchOn = newData->getVisibility();
            const auto newData = static_cast<BluetoothStatusData *>(data);
            if (const auto btState = newData->getState(); btState.has_value()) {
                isBluetoothSwitchOn = btState.value();
            }
            if (const auto visibility = newData->getVisibility(); visibility.has_value()) {
                isPhoneVisibilitySwitchOn = visibility.value();
            }
        }
        rebuildOptionList();
    }


@@ 36,7 40,7 @@ namespace gui
        optionsList.emplace_back(std::make_unique<gui::option::OptionSettings>(
            utils::translateI18("app_settings_bluetooth_main"),
            [=](gui::Item &item) {
                switchHandler(isBluetoothSwitchOn);
                changeBluetoothState(isBluetoothSwitchOn);
                return true;
            },
            [=](gui::Item &item) {


@@ 69,7 73,7 @@ namespace gui
            optionsList.emplace_back(std::make_unique<gui::option::OptionSettings>(
                utils::translateI18("app_settings_bluetooth_phone_visibility"),
                [=](gui::Item &item) {
                    switchHandler(isPhoneVisibilitySwitchOn);
                    changeVisibility(isPhoneVisibilitySwitchOn);
                    return true;
                },
                [=](gui::Item &item) {


@@ 104,22 108,28 @@ namespace gui
        return optionsList;
    }

    void BluetoothWindow::switchHandler(bool &switchState)
    void BluetoothWindow::changeBluetoothState(bool currentState)
    {
        switchState = !switchState;
        BluetoothStatus btStatus;
        ::message::bluetooth::SetStatus setStatus(makeDesiredStatus(!currentState, isPhoneVisibilitySwitchOn));
        sys::Bus::SendUnicast(std::make_shared<::message::bluetooth::SetStatus>(std::move(setStatus)),
                              service::name::bluetooth,
                              application);
    }

        if (isBluetoothSwitchOn) {
            btStatus.state = BluetoothStatus::State::On;
        }
        else {
            btStatus.state = BluetoothStatus::State::Off;
        }
        btStatus.visibility = isPhoneVisibilitySwitchOn;
        ::message::bluetooth::SetStatus setStatus(btStatus);
    void BluetoothWindow::changeVisibility(bool currentVisibility)
    {
        ::message::bluetooth::SetStatus setStatus(makeDesiredStatus(isBluetoothSwitchOn, !currentVisibility));
        sys::Bus::SendUnicast(std::make_shared<::message::bluetooth::SetStatus>(std::move(setStatus)),
                              service::name::bluetooth,
                              application);
    }

        sys::Bus::SendUnicast(
            std::make_shared<::message::bluetooth::SetStatus>(setStatus), service::name::bluetooth, application);
    BluetoothStatus BluetoothWindow::makeDesiredStatus(bool desiredBluetoothState, bool desiredVisibility) noexcept
    {
        BluetoothStatus status;
        status.state      = desiredBluetoothState ? BluetoothStatus::State::On : BluetoothStatus::State::Off;
        status.visibility = desiredVisibility;
        return status;
    }

    void BluetoothWindow::rebuildOptionList()

M module-apps/application-settings-new/windows/BluetoothWindow.hpp => module-apps/application-settings-new/windows/BluetoothWindow.hpp +23 -8
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 18,28 18,43 @@ namespace gui
        bool isBluetoothSwitchOn       = false;
        bool isPhoneVisibilitySwitchOn = false;
        auto bluetoothOptionsList() -> std::list<gui::Option>;
        void switchHandler(bool &switchState);
        void changeBluetoothState(bool currentState);
        void changeVisibility(bool currentVisibility);
        void rebuildOptionList();

        static BluetoothStatus makeDesiredStatus(bool desiredBluetoothState, bool desiredVisibility) noexcept;
    };

    class BluetoothStatusData : public SwitchData
    {
      public:
        explicit BluetoothStatusData(BluetoothStatus status) : SwitchData(), status(std::move(status))
        explicit BluetoothStatusData(BluetoothStatus::State state) : state{state}
        {}
        explicit BluetoothStatusData(bool visibility) : visibility{visibility}
        {}
        BluetoothStatusData(BluetoothStatus::State state, bool visibility) : state{state}, visibility{visibility}
        {}
        [[nodiscard]] auto getState() const noexcept -> bool

        [[nodiscard]] auto getState() const noexcept -> std::optional<bool>
        {
            if (status.state == BluetoothStatus::State::On) {
            if (!state.has_value()) {
                return std::nullopt;
            }
            if (state == BluetoothStatus::State::On) {
                return true;
            }
            return false;
        }
        [[nodiscard]] auto getVisibility() const noexcept -> bool
        [[nodiscard]] auto getVisibility() const noexcept -> std::optional<bool>
        {
            return status.visibility;
            if (!visibility.has_value()) {
                return std::nullopt;
            }
            return visibility;
        }

      private:
        BluetoothStatus status;
        std::optional<BluetoothStatus::State> state;
        std::optional<bool> visibility;
    };
} // namespace gui

M module-apps/application-settings/windows/BtWindow.cpp => module-apps/application-settings/windows/BtWindow.cpp +2 -2
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <functional>


@@ 106,7 106,7 @@ namespace gui

        add_box_label(box, "  -> Stop", [=](Item &) {
            LOG_DEBUG("Stop playback");
            message_bt(application, BluetoothMessage::Request::Stop);
            message_bt(application, BluetoothMessage::Request::StopPlayback);
            return true;
        });


M module-bluetooth/Bluetooth/BluetoothWorker.cpp => module-bluetooth/Bluetooth/BluetoothWorker.cpp +86 -120
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <service-bluetooth/ServiceBluetooth.hpp>


@@ 24,10 24,6 @@

using namespace bsp;

[[nodiscard]] auto to_string(bluetooth::Error::Code code) -> std::string
{
    return utils::enumToString(code);
}
namespace queues
{
    constexpr inline auto io  = "qBtIO";


@@ 36,112 32,110 @@ namespace queues

    constexpr inline auto queueLength        = 10;
    constexpr inline auto triggerQueueLength = 3;

} // namespace queues

namespace
{
    class DeviceRegistration
    {
      public:
        using OnLinkKeyAddedCallback = std::function<void(const std::string &)>;

        DeviceRegistration(std::shared_ptr<bluetooth::SettingsHolder> settings, OnLinkKeyAddedCallback &&onLinkKeyAdded)
            : settings{std::move(settings)}, onLinkKeyAdded{std::move(onLinkKeyAdded)}
        {}

        [[nodiscard]] auto operator()()
        {
            bluetooth::KeyStorage::settings = settings;
            bluetooth::GAP::register_scan();

            auto settingsName = std::get<std::string>(settings->getValue(bluetooth::Settings::DeviceName));
            if (settingsName.empty()) {
                LOG_WARN("Settings name is empty!");
                constexpr auto name = "PurePhone";
                settings->setValue(bluetooth::Settings::DeviceName, name);
                settingsName = name;
            }
            bluetooth::set_name(settingsName);
            bluetooth::GAP::set_visibility(
                std::visit(bluetooth::BoolVisitor{}, settings->getValue(bluetooth::Settings::Visibility)));

            settings->onLinkKeyAdded = onLinkKeyAdded;
            return bluetooth::Error::Success;
        }

      private:
        std::shared_ptr<bluetooth::SettingsHolder> settings;
        OnLinkKeyAddedCallback onLinkKeyAdded;
    };

    auto createStatefulController(sys::Service *service,
                                  bluetooth::RunLoop *loop,
                                  std::shared_ptr<bluetooth::SettingsHolder> settings,
                                  std::shared_ptr<bluetooth::Profile> currentProfile,
                                  DeviceRegistration::OnLinkKeyAddedCallback &&onLinkKeyAddedCallback)
    {
        auto driver         = std::make_unique<bluetooth::Driver>(loop->getRunLoopInstance());
        auto commandHandler = std::make_unique<bluetooth::CommandHandler>(service, settings, std::move(currentProfile));
        return std::make_unique<bluetooth::StatefulController>(
            std::move(driver),
            std::move(commandHandler),
            DeviceRegistration{std::move(settings), std::move(onLinkKeyAddedCallback)});
    }
} // namespace

BluetoothWorker::BluetoothWorker(sys::Service *service)
    : Worker(service), service(service), currentProfile(std::make_shared<bluetooth::HSP>()),
      settings(static_cast<ServiceBluetooth *>(service)->settingsHolder),
      runLoop(std::make_unique<bluetooth::RunLoop>()), driver(std::make_unique<bluetooth::Driver>())
      runLoop(std::make_unique<bluetooth::RunLoop>()),
      controller{createStatefulController(
          service, runLoop.get(), settings, currentProfile, [this](const std::string &addr) { onLinkKeyAdded(addr); })}
{
    init({
        {queues::io, sizeof(bluetooth::Message), queues::queueLength},
        {queues::cmd, sizeof(bluetooth::Command), queues::queueLength},
        {queues::btstack, sizeof(bool), queues::triggerQueueLength},
    });
    registerQueues();
}

void BluetoothWorker::registerQueues()
{
    static_cast<ServiceBluetooth *>(service)->workerQueue = Worker::getQueueHandleByName(queues::cmd);
    runLoop->setTriggerQueue(Worker::getQueueHandleByName(queues::btstack));
    driver->init(runLoop->getRunLoopInstance());
    BlueKitchen::getInstance()->qHandle = queues[queueIO_handle]->GetQueueHandle();
}

BluetoothWorker::~BluetoothWorker()
void BluetoothWorker::onLinkKeyAdded(const std::string &deviceAddress)
{
    if (this->bt_worker_task != nullptr) {
        vTaskDelete(this->bt_worker_task);
    for (auto &device : bluetooth::GAP::devices) {
        if (bd_addr_to_str(device.address) == deviceAddress) {
            pairedDevices.emplace_back(device);
            settings->setValue(bluetooth::Settings::BondedDevices, SettingsSerializer::toString(pairedDevices));
        }
    }
    LOG_INFO("Worker removed");
}

BluetoothWorker::~BluetoothWorker()
{
    controller->shutdown();
}

auto BluetoothWorker::run() -> bool
{
    LOG_INFO("-> BluetoothWorker run request");
    if (is_running) {
    if (isRunning) {
        return true;
    }
    if (Worker::run()) {
        is_running                          = true;
        auto el                             = queues[queueIO_handle];
        BlueKitchen::getInstance()->qHandle = el->GetQueueHandle();
        bluetooth::KeyStorage::settings     = settings;

        driver->registerHardwareErrorCallback(nullptr);
        bluetooth::GAP::register_scan();

        std::string name = "PurePhone";
        auto settingsName = std::get<std::string>(settings->getValue(bluetooth::Settings::DeviceName));
        if (settingsName.empty()) {
            LOG_ERROR("settings name empty!");
            settings->setValue(bluetooth::Settings::DeviceName, name);
            settingsName = name;
        }

        bluetooth::set_name(settingsName);
        bluetooth::GAP::set_visibility(
            std::visit(bluetooth::BoolVisitor(), settings->getValue(bluetooth::Settings::Visibility)));

        settings->onLinkKeyAdded = [this](std::string addr) {
            for (auto &device : bluetooth::GAP::devices) {
                if (bd_addr_to_str(device.address) == addr) {
                    // found paired device
                    pairedDevices.emplace_back(device);
                    settings->setValue(bluetooth::Settings::BondedDevices, SettingsSerializer::toString(pairedDevices));
                }
            }
        };

        return driver->run() == bluetooth::Error::Success;
    }
    else {
        return false;
    }
}

auto BluetoothWorker::scan() -> bool
{
    std::vector<Device> empty;
    bluetooth::GAP::setOwnerService(service);
    auto ret = bluetooth::GAP::scan();
    if (ret.err != bluetooth::Error::Success) {
        LOG_ERROR("Cant start scan!: %s %" PRIu32 "", to_string(ret.err).c_str(), ret.lib_code);
    if (const auto status = Worker::run(); !status) {
        return false;
    }
    else {
        LOG_INFO("Scan started!");
        // open new scan window
        return true;
    }
}

void BluetoothWorker::stopScan()
{
    bluetooth::GAP::stop_scan();
}

void BluetoothWorker::setVisibility(bool visibility)
{
    bluetooth::GAP::set_visibility(visibility);
    settings->setValue(bluetooth::Settings::Visibility, visibility);
    isRunning = true;
    return true;
}

auto BluetoothWorker::start_pan() -> bool
{
    bluetooth::PAN::bnep_setup();
    auto err = bluetooth::PAN::bnep_start();
    if (err.err != bluetooth::Error::Success) {
        LOG_ERROR("PAN setup error: %s %" PRIu32, to_string(err.err).c_str(), err.lib_code);
    }
    return false;
}
auto BluetoothWorker::handleCommand(QueueHandle_t queue) -> bool
{
    bluetooth::Command command;


@@ 149,29 143,16 @@ auto BluetoothWorker::handleCommand(QueueHandle_t queue) -> bool
        LOG_ERROR("Queue receive failure!");
        return false;
    }
    switch (command) {
    case bluetooth::PowerOn:

    switch (command) {
    case bluetooth::Command::PowerOn:
        controller->turnOn();
        break;
    case bluetooth::StartScan:
        scan();
        break;
    case bluetooth::StopScan:
        stopScan();
        break;
    case bluetooth::VisibilityOn:
        setVisibility(true);
        break;
    case bluetooth::VisibilityOff:
        setVisibility(false);
        break;
    case bluetooth::ConnectAudio:
        establishAudioConnection();
        break;
    case bluetooth::DisconnectAudio:
        disconnectAudioConnection();
    case bluetooth::Command::PowerOff:
        controller->turnOff();
        break;
    case bluetooth::PowerOff:
    default:
        controller->processCommand(command);
        break;
    }
    return true;


@@ 186,12 167,9 @@ auto BluetoothWorker::handleBtStackTrigger(QueueHandle_t queue) -> bool
    }
    if (notification) {
        runLoop->process();

        return true;
    }
    else {
        return false;
    }
    return false;
}

auto BluetoothWorker::handleMessage(uint32_t queueID) -> bool


@@ 269,26 247,14 @@ auto BluetoothWorker::handleMessage(uint32_t queueID) -> bool
    return true;
}

auto BluetoothWorker::establishAudioConnection() -> bool
{
    currentProfile->setOwnerService(service);
    if (currentProfile->init() != bluetooth::Error::Success) {
        return false;
    }
    currentProfile->connect();
    return true;
}
auto BluetoothWorker::disconnectAudioConnection() -> bool
{
    currentProfile->disconnect();
    return true;
}
void BluetoothWorker::setDeviceAddress(bd_addr_t addr)
{
    bluetooth::GAP::do_pairing(addr);
    currentProfile->setDeviceAddress(addr);
}

auto BluetoothWorker::deinit() -> bool
{
    controller->turnOff();
    return Worker::deinit();
}

M module-bluetooth/Bluetooth/BluetoothWorker.hpp => module-bluetooth/Bluetooth/BluetoothWorker.hpp +12 -26
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 14,6 14,7 @@
#include "service-bluetooth/SettingsHolder.hpp"
#include "glucode/BluetoothRunLoop.hpp"
#include "interface/BluetoothDriver.hpp"
#include "WorkerController.hpp"
struct HCI;

/// debug option for HCI (uart) commands debugging


@@ 35,18 36,6 @@ namespace bluetooth
        EvtErrorRec,       /// there was error o queue receive
    };

    enum Command : std::uint8_t
    {
        StartScan,
        StopScan,
        VisibilityOn,
        VisibilityOff,
        ConnectAudio,
        DisconnectAudio,
        PowerOn,
        PowerOff,
    };

    inline const char *MessageCstr(Message what)
    {
        switch (what) {


@@ 88,9 77,11 @@ class BluetoothWorker : private sys::Worker
        queueRunloopTrigger // btstack run_loop queue
    };

    TaskHandle_t bt_worker_task = nullptr;
    int is_running              = false;
    sys::Service *service       = nullptr;
    bool isRunning              = false;

    void registerQueues();
    void onLinkKeyAdded(const std::string &deviceAddress);

  public:
    enum Error


@@ 104,23 95,18 @@ class BluetoothWorker : private sys::Worker
    ~BluetoothWorker() override;

    auto handleMessage(uint32_t queueID) -> bool override;

    auto handleCommand(QueueHandle_t queue) -> bool;
    auto handleBtStackTrigger(QueueHandle_t queue) -> bool;
    bool run();
    auto scan() -> bool;
    void setVisibility(bool visibility);
    auto start_pan() -> bool;
    auto establishAudioConnection() -> bool;
    auto disconnectAudioConnection() -> bool;
    /// bluetooth stack id in use
    unsigned long active_features;
    void stopScan();

    bool run() override;
    void setDeviceAddress(bd_addr_t addr);
    auto deinit() -> bool override;

    /// bluetooth stack id in use
    unsigned long active_features;
    std::shared_ptr<bluetooth::Profile> currentProfile;
    std::shared_ptr<bluetooth::SettingsHolder> settings;
    std::vector<Devicei> pairedDevices;
    std::unique_ptr<bluetooth::RunLoop> runLoop;
    std::unique_ptr<bluetooth::Driver> driver;
    std::unique_ptr<bluetooth::AbstractController> controller;
};

A module-bluetooth/Bluetooth/CommandHandler.cpp => module-bluetooth/Bluetooth/CommandHandler.cpp +102 -0
@@ 0,0 1,102 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "CommandHandler.hpp"

#include "BtCommand.hpp"
#include "Device.hpp"

namespace bluetooth
{
    namespace
    {
        [[nodiscard]] auto toString(bluetooth::Error::Code code) -> std::string
        {
            return utils::enumToString(code);
        }
    } // namespace

    CommandHandler::CommandHandler(sys::Service *service,
                                   std::shared_ptr<bluetooth::SettingsHolder> settings,
                                   std::shared_ptr<bluetooth::Profile> currentProfile)
        : service{service}, settings{std::move(settings)}, currentProfile{std::move(currentProfile)}
    {}

    Error::Code CommandHandler::handle(Command command)
    {
        switch (command) {
        case bluetooth::PowerOn:
            return Error::Success;
        case bluetooth::StartScan:
            return scan();
        case bluetooth::StopScan:
            return stopScan();
        case bluetooth::StartPan:
            return startPan();
        case bluetooth::VisibilityOn:
            return setVisibility(true);
        case bluetooth::VisibilityOff:
            return setVisibility(false);
        case bluetooth::ConnectAudio:
            return establishAudioConnection();
        case bluetooth::DisconnectAudio:
            return disconnectAudioConnection();
        case bluetooth::PowerOff:
            return Error::Success;
        }
        return Error::LibraryError;
    }

    Error::Code CommandHandler::scan()
    {
        bluetooth::GAP::setOwnerService(service);
        if (const auto ret = bluetooth::GAP::scan(); ret.err != bluetooth::Error::Success) {
            LOG_ERROR("Cant start scan!: %s %" PRIu32 "", toString(ret.err).c_str(), ret.lib_code);
            return ret.err;
        }

        LOG_INFO("Scan started!");
        // open new scan window
        return Error::Success;
    }

    Error::Code CommandHandler::stopScan()
    {
        bluetooth::GAP::stop_scan();
        return Error::Success;
    }

    Error::Code CommandHandler::startPan()
    {
        bluetooth::PAN::bnep_setup();
        if (const auto err = bluetooth::PAN::bnep_start(); err.err != bluetooth::Error::Success) {
            LOG_ERROR("PAN setup error: %s %" PRIu32, toString(err.err).c_str(), err.lib_code);
            return err.err;
        }
        return bluetooth::Error::Success;
    }

    Error::Code CommandHandler::setVisibility(bool visibility)
    {
        const auto status = bluetooth::GAP::set_visibility(visibility);
        settings->setValue(bluetooth::Settings::Visibility, static_cast<int>(visibility));
        return status.err;
    }

    Error::Code CommandHandler::establishAudioConnection()
    {
        currentProfile->setOwnerService(service);
        if (const auto status = currentProfile->init(); status != bluetooth::Error::Success) {
            return status;
        }

        currentProfile->connect();
        return Error::Success;
    }

    Error::Code CommandHandler::disconnectAudioConnection()
    {
        currentProfile->disconnect();
        return Error::Success;
    }
} // namespace bluetooth

A module-bluetooth/Bluetooth/CommandHandler.hpp => module-bluetooth/Bluetooth/CommandHandler.hpp +58 -0
@@ 0,0 1,58 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "Error.hpp"
#include "interface/profiles/Profile.hpp"

#include <service-bluetooth/SettingsHolder.hpp>
#include <Service/Service.hpp>

#include <cstdint>

namespace bluetooth
{
    enum Command : std::uint8_t
    {
        StartScan,
        StopScan,
        StartPan,
        VisibilityOn,
        VisibilityOff,
        ConnectAudio,
        DisconnectAudio,
        PowerOn,
        PowerOff,
    };

    class AbstractCommandHandler
    {
      public:
        virtual ~AbstractCommandHandler() noexcept = default;

        virtual auto handle(Command command) -> Error::Code = 0;
    };

    class CommandHandler : public AbstractCommandHandler
    {
      public:
        explicit CommandHandler(sys::Service *service,
                                std::shared_ptr<bluetooth::SettingsHolder> settings,
                                std::shared_ptr<bluetooth::Profile> currentProfile);

        auto handle(Command command) -> Error::Code override;

      private:
        Error::Code scan();
        Error::Code stopScan();
        Error::Code startPan();
        Error::Code setVisibility(bool visibility);
        Error::Code establishAudioConnection();
        Error::Code disconnectAudioConnection();

        sys::Service *service;
        std::shared_ptr<bluetooth::SettingsHolder> settings;
        std::shared_ptr<bluetooth::Profile> currentProfile;
    };
} // namespace bluetooth

A module-bluetooth/Bluetooth/WorkerController.cpp => module-bluetooth/Bluetooth/WorkerController.cpp +219 -0
@@ 0,0 1,219 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "WorkerController.hpp"

#include "Device.hpp"
#include "interface/profiles/Profile.hpp"

#include <module-utils/log/log.hpp>
#include <module-utils/sml/include/boost/sml.hpp>
#include <module-utils/magic_enum/include/magic_enum.hpp>

#include <queue>
#include <deque>

namespace bluetooth
{
    namespace sml = boost::sml;

    namespace
    {
        struct Logger
        {
            template <class SM, class TEvent> void log_process_event(const TEvent &)
            {
                LOG_INFO("[%s][process_event] %s", sml::aux::get_type_name<SM>(), sml::aux::get_type_name<TEvent>());
            }

            template <class SM, class TGuard, class TEvent> void log_guard(const TGuard &, const TEvent &, bool result)
            {
                LOG_INFO("[%s][guard] %s %s %s",
                         sml::aux::get_type_name<SM>(),
                         sml::aux::get_type_name<TGuard>(),
                         sml::aux::get_type_name<TEvent>(),
                         (result ? "[OK]" : "[Reject]"));
            }

            template <class SM, class TAction, class TEvent> void log_action(const TAction &, const TEvent &)
            {
                LOG_INFO("[%s][action] %s %s",
                         sml::aux::get_type_name<SM>(),
                         sml::aux::get_type_name<TAction>(),
                         sml::aux::get_type_name<TEvent>());
            }

            template <class SM, class TSrcState, class TDstState>
            void log_state_change(const TSrcState &src, const TDstState &dst)
            {
                LOG_INFO("[%s][transition] %s -> %s", sml::aux::get_type_name<SM>(), src.c_str(), dst.c_str());
            }
        };

        struct TurnOn
        {};
        struct TurnOff
        {};
        struct ShutDown
        {};
        struct ProcessCommand
        {
            Command command;
        };

        class InitializationError : public std::runtime_error
        {
          public:
            using std::runtime_error::runtime_error;
        };

        class ProcessingError : public std::runtime_error
        {
          public:
            using std::runtime_error::runtime_error;
        };

        struct InitializationState
        {
            bool isInitDone = false;
        };

        struct Setup
        {
          public:
            auto operator()() const
            {
                auto isInit = [](InitializationState &data) { return data.isInitDone; };
                auto init   = [](std::shared_ptr<AbstractDriver> &driver) {
                    if (const auto status = driver->init(); status != Error::Success) {
                        throw InitializationError{"Unable to initialize a bluetooth driver."};
                    }
                };
                auto setup = [](DeviceRegistrationFunction &registerDevice, InitializationState &data) {
                    if (const auto status = registerDevice(); status != Error::Success) {
                        throw InitializationError{"Unable to initialize bluetooth"};
                    }
                    data.isInitDone = true;
                };
                auto startDriver = [](std::shared_ptr<AbstractDriver> &driver) {
                    if (const auto status = driver->run(); status != Error::Success) {
                        throw InitializationError{"Unable to run the bluetooth driver"};
                    }
                };

                using namespace sml;
                // clang-format off
                return make_transition_table(*"Setup"_s / startDriver = "StartingDriver"_s,
                                             "Setup"_s + on_entry<_> [ !isInit ] / ( init, setup ),
                                             "StartingDriver"_s = X);
                // clang-format on
            }
        };

        struct On
        {
            auto operator()() const
            {
                auto isInit        = [](InitializationState &data) { return data.isInitDone; };
                auto handleCommand = [](std::shared_ptr<AbstractCommandHandler> &processor,
                                        const ProcessCommand &processCommand) {
                    if (const auto status = processor->handle(processCommand.command); status != Error::Success) {
                        throw ProcessingError{"Failed to process command"};
                    }
                };

                using namespace sml;
                // clang-format off
                return make_transition_table(*"Idle"_s + event<ProcessCommand> [ isInit ] / handleCommand = "Processing"_s,
                                             "Processing"_s = "Idle"_s);
                // clang-format on
            }
        };

        class StateMachine
        {
          public:
            auto operator()() const
            {
                auto turnOff              = [](std::shared_ptr<AbstractDriver> &driver) { driver->stop(); };
                auto printInitError       = [](const InitializationError &error) { LOG_ERROR("%s", error.what()); };
                auto printProcessingError = [](const ProcessingError &error) { LOG_ERROR("%s", error.what()); };

                using namespace sml;
                // clang-format off
                return make_transition_table(*"Off"_s + event<TurnOn> = state<Setup>,
                                             state<Setup> = state<On>,
                                             state<Setup> + exception<InitializationError> / printInitError = "Off"_s,
                                             state<On> + event<TurnOff> / turnOff = "Off"_s,
                                             state<On> + exception<ProcessingError> / ( printProcessingError, turnOff ) = "Restart"_s,
                                             "Restart"_s = state<Setup>,
                                             "Off"_s + event<ShutDown> = X);
                // clang-format on
            }
        };
    } // namespace

    class StatefulController::Impl
    {
      public:
        Impl(Logger logger,
             std::shared_ptr<AbstractDriver> &&driver,
             std::shared_ptr<AbstractCommandHandler> &&handler,
             DeviceRegistrationFunction &&registerDevice);

        using SM =
            sml::sm<StateMachine, sml::logger<Logger>, sml::defer_queue<std::deque>, sml::process_queue<std::queue>>;
        SM sm;
    };

    StatefulController::Impl::Impl(Logger logger,
                                   std::shared_ptr<AbstractDriver> &&driver,
                                   std::shared_ptr<AbstractCommandHandler> &&handler,
                                   DeviceRegistrationFunction &&registerDevice)
        : sm{std::move(logger), std::move(driver), std::move(handler), std::move(registerDevice), InitializationState{}}
    {}

    StatefulController::StatefulController(std::shared_ptr<AbstractDriver> &&driver,
                                           std::shared_ptr<AbstractCommandHandler> &&handler,
                                           DeviceRegistrationFunction &&registerDevice)
        : pimpl(std::make_unique<Impl>(Logger{}, std::move(driver), std::move(handler), std::move(registerDevice)))
    {}

    StatefulController::~StatefulController() noexcept = default;

    void StatefulController::turnOn()
    {
        pimpl->sm.process_event(TurnOn{});
    }

    void StatefulController::turnOff()
    {
        pimpl->sm.process_event(TurnOff{});
    }

    void StatefulController::shutdown()
    {
        if (isOn()) {
            turnOff();
        }
        pimpl->sm.process_event(ShutDown{});
    }

    auto StatefulController::isOn() const -> bool
    {
        using namespace sml;
        return !pimpl->sm.is("Off"_s) && !isTerminated();
    }

    auto StatefulController::isTerminated() const -> bool
    {
        using namespace sml;
        return pimpl->sm.is(X);
    }

    void StatefulController::processCommand(Command command)
    {
        LOG_INFO("Process command: %s", magic_enum::enum_name(command).data());
        pimpl->sm.process_event(ProcessCommand{command});
    }
} // namespace bluetooth

A module-bluetooth/Bluetooth/WorkerController.hpp => module-bluetooth/Bluetooth/WorkerController.hpp +50 -0
@@ 0,0 1,50 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "CommandHandler.hpp"
#include "interface/BluetoothDriver.hpp"

#include <functional>
#include <memory>

namespace bluetooth
{
    using DeviceRegistrationFunction = std::function<Error::Code()>;

    class AbstractController
    {
      public:
        virtual ~AbstractController() noexcept = default;

        virtual void turnOn()                     = 0;
        virtual void turnOff()                    = 0;
        virtual void shutdown()                   = 0;
        virtual auto isOn() const -> bool         = 0;
        virtual auto isTerminated() const -> bool = 0;

        virtual void processCommand(Command command) = 0;
    };

    class StatefulController : public AbstractController
    {
      public:
        StatefulController(std::shared_ptr<AbstractDriver> &&driver,
                           std::shared_ptr<AbstractCommandHandler> &&handler,
                           DeviceRegistrationFunction &&registerDevice);
        ~StatefulController() noexcept override;

        void turnOn() override;
        void turnOff() override;
        void shutdown() override;
        [[nodiscard]] auto isOn() const -> bool override;
        [[nodiscard]] auto isTerminated() const -> bool override;

        void processCommand(Command command) override;

      private:
        class Impl;
        std::unique_ptr<Impl> pimpl;
    };
} // namespace bluetooth

M module-bluetooth/Bluetooth/interface/BluetoothDriver.cpp => module-bluetooth/Bluetooth/interface/BluetoothDriver.cpp +10 -7
@@ 35,15 35,15 @@ namespace bluetooth
    hci_transport_config_uart_t Driver::config;

#ifdef TARGET_RT1051
    [[maybe_unused]] auto Driver::runLoopInitTarget(const btstack_run_loop *runLoop) -> const btstack_uart_block_t *
    [[maybe_unused]] auto Driver::runLoopInitTarget(const btstack_run_loop *loop) -> const btstack_uart_block_t *
    {
        btstack_run_loop_init(runLoop);
        btstack_run_loop_init(loop);
        const btstack_uart_block_t *uartDriver = btstack_uart_block_rt1051_instance();
        return uartDriver;
    }
#else

    [[maybe_unused]] auto Driver::runLoopInitLinux(const btstack_run_loop *runLoop) -> const btstack_uart_block_t *
    [[maybe_unused]] auto Driver::runLoopInitLinux(const btstack_run_loop *) -> const btstack_uart_block_t *
    {
        btstack_run_loop_init(btstack_run_loop_posix_get_instance());
        config.device_name = "/dev/telit";


@@ 53,7 53,10 @@ namespace bluetooth
    }
#endif

    auto Driver::init(const btstack_run_loop *runLoop) -> Error::Code
    Driver::Driver(const btstack_run_loop *runLoop) : runLoop{runLoop}
    {}

    auto Driver::init() -> Error::Code
    {
        btstack_memory_init();
        config = {


@@ 164,10 167,10 @@ namespace bluetooth
        return Error::Success;
    }

    void Driver::registerHardwareErrorCallback(std::function<void(uint8_t)> new_callback)
    void Driver::registerErrorCallback(const ErrorCallback &newCallback)
    {
        static std::function<void(uint8_t)> callback = nullptr;
        callback                                     = new_callback;
        static ErrorCallback callback;
        callback = newCallback;
        hci_set_hardware_error_callback([](uint8_t val) -> void {
            LOG_ERROR("Bluetooth HW ERROR! %d", val);
            if (callback) {

M module-bluetooth/Bluetooth/interface/BluetoothDriver.hpp => module-bluetooth/Bluetooth/interface/BluetoothDriver.hpp +22 -5
@@ 9,21 9,38 @@

namespace bluetooth
{
    class AbstractDriver
    {
      public:
        using ErrorCallback                = std::function<void(uint8_t)>;
        virtual ~AbstractDriver() noexcept = default;

    class Driver
        [[nodiscard]] virtual auto init() -> Error::Code                     = 0;
        [[nodiscard]] virtual auto run() -> Error::Code                      = 0;
        [[nodiscard]] virtual auto stop() -> Error::Code                     = 0;
        virtual void registerErrorCallback(const ErrorCallback &newCallback) = 0;
    };

    class Driver : public AbstractDriver
    {
      private:
        static hci_transport_config_uart_t config;
        const btstack_run_loop *runLoop;
        btstack_packet_callback_registration_t hci_event_callback_registration;
        static void hci_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
        static void local_version_information_handler(uint8_t *packet);
#ifdef TARGET_RT1051
        [[maybe_unused]] auto runLoopInitTarget(const btstack_run_loop *runLoop) -> const btstack_uart_block_t *;
#else
        [[maybe_unused]] auto runLoopInitLinux(const btstack_run_loop *runLoop) -> const btstack_uart_block_t *;
#endif

      public:
        auto init(const btstack_run_loop *runLoop) -> Error::Code;
        auto run() -> Error::Code;
        auto stop() -> Error::Code;
        void registerHardwareErrorCallback(std::function<void(uint8_t)> new_callback);
        explicit Driver(const btstack_run_loop *runLoop);

        [[nodiscard]] auto init() -> Error::Code override;
        [[nodiscard]] auto run() -> Error::Code override;
        [[nodiscard]] auto stop() -> Error::Code override;
        void registerErrorCallback(const ErrorCallback &newCallback) override;
    };
} // namespace bluetooth

M module-bluetooth/CMakeLists.txt => module-bluetooth/CMakeLists.txt +6 -0
@@ 6,6 6,8 @@ set(CMAKE_CXX_STANDARD 17)

set(SOURCES
    ${CMAKE_CURRENT_SOURCE_DIR}/Bluetooth/BluetoothWorker.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/Bluetooth/WorkerController.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/Bluetooth/CommandHandler.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/Bluetooth/glucode/BluetoothRunLoop.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/Bluetooth/interface/BluetoothDriver.cpp
    ${CMAKE_CURRENT_SOURCE_DIR}/Bluetooth/BtKeysStorage.cpp


@@ 68,3 70,7 @@ target_link_libraries(${PROJECT_NAME}
    service-audio
    ${BOARD_DIR_LIBRARIES}
    )

if (${ENABLE_TESTS})
    add_subdirectory(tests)
endif()

M module-bluetooth/tests/CMakeLists.txt => module-bluetooth/tests/CMakeLists.txt +9 -0
@@ 0,0 1,9 @@
add_catch2_executable(
    NAME
        StatefulController-tests
    SRCS
        tests-main.cpp
        tests-StatefulController.cpp
    LIBS
        module-bluetooth
)

A module-bluetooth/tests/tests-StatefulController.cpp => module-bluetooth/tests/tests-StatefulController.cpp +171 -0
@@ 0,0 1,171 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>

#include "WorkerController.hpp"
#include "interface/BluetoothDriver.hpp"

#include <iostream>

using namespace bluetooth;

class DriverMock : public AbstractDriver
{
  public:
    Error::Code init() override
    {
        return initReturnCode;
    }
    Error::Code run() override
    {
        return runReturnCode;
    }
    Error::Code stop() override
    {
        return stopReturnCode;
    }
    void registerErrorCallback(const ErrorCallback &) override
    {}

    Error::Code initReturnCode = Error::Success;
    Error::Code runReturnCode  = Error::Success;
    Error::Code stopReturnCode = Error::Success;
};

auto InitializerMock = []() { return Error::Success; };

class HandlerMock : public AbstractCommandHandler
{
  public:
    Error::Code handle(Command command) override
    {
        return returnCode;
    }

    Error::Code returnCode = Error::Success;
};

TEST_CASE("Given StatefulController when turn on then turned on")
{
    auto driver    = std::make_unique<DriverMock>();
    auto processor = std::make_unique<HandlerMock>();
    StatefulController controller{std::move(driver), std::move(processor), InitializerMock};
    controller.turnOn();
    REQUIRE(controller.isOn());
}

TEST_CASE("Given StatefulController when error during device registration then turned off")
{
    auto driver    = std::make_unique<DriverMock>();
    auto processor = std::make_unique<HandlerMock>();
    StatefulController controller{std::move(driver), std::move(processor), []() { return Error::SystemError; }};
    controller.turnOn();
    REQUIRE(!controller.isOn());
}

TEST_CASE("Given StatefulController when error during driver init then turned off")
{
    auto driver            = std::make_unique<DriverMock>();
    driver->initReturnCode = Error::SystemError;

    auto processor = std::make_unique<HandlerMock>();
    StatefulController controller{std::move(driver), std::move(processor), InitializerMock};
    controller.turnOn();
    REQUIRE(!controller.isOn());
}

TEST_CASE("Given StatefulController when error during driver run then turned off")
{
    auto driver           = std::make_unique<DriverMock>();
    driver->runReturnCode = Error::SystemError;

    auto processor = std::make_unique<HandlerMock>();
    StatefulController controller{std::move(driver), std::move(processor), InitializerMock};
    controller.turnOn();
    REQUIRE(!controller.isOn());
}

TEST_CASE("Given StatefulController when restart then don't init twice")
{
    auto driver    = std::make_shared<DriverMock>();
    auto processor = std::make_unique<HandlerMock>();
    StatefulController controller{driver, std::move(processor), InitializerMock};
    controller.turnOn();
    REQUIRE(controller.isOn());

    controller.turnOff();
    REQUIRE(!controller.isOn());

    driver->initReturnCode = Error::SystemError;
    controller.turnOn();
    REQUIRE(controller.isOn());
}

TEST_CASE("Given StatefulController when turn off in off state then turned off")
{
    auto driver    = std::make_unique<DriverMock>();
    auto processor = std::make_unique<HandlerMock>();
    StatefulController controller{std::move(driver), std::move(processor), InitializerMock};
    controller.turnOff();
    REQUIRE(!controller.isOn());
}

TEST_CASE("Given StatefulController when turn off in on state then turned off")
{
    auto driver    = std::make_unique<DriverMock>();
    auto processor = std::make_unique<HandlerMock>();
    StatefulController controller{std::move(driver), std::move(processor), InitializerMock};
    controller.turnOn();
    REQUIRE(controller.isOn());

    controller.turnOff();
    REQUIRE(!controller.isOn());
}

TEST_CASE("Given StatefulController when shutdown in off state then terminated")
{
    auto driver    = std::make_unique<DriverMock>();
    auto processor = std::make_unique<HandlerMock>();
    StatefulController controller{std::move(driver), std::move(processor), InitializerMock};
    controller.shutdown();
    REQUIRE(controller.isTerminated());
}

TEST_CASE("Given StatefulController when shutdown in on state then terminated")
{
    auto driver    = std::make_unique<DriverMock>();
    auto processor = std::make_unique<HandlerMock>();
    StatefulController controller{std::move(driver), std::move(processor), InitializerMock};
    controller.turnOn();
    REQUIRE(controller.isOn());

    controller.shutdown();
    REQUIRE(controller.isTerminated());
}

TEST_CASE("Given StatefulController when process command successfully then turned on")
{
    auto driver    = std::make_unique<DriverMock>();
    auto processor = std::make_unique<HandlerMock>();
    StatefulController controller{std::move(driver), std::move(processor), InitializerMock};
    controller.turnOn();
    REQUIRE(controller.isOn());

    controller.processCommand(Command::PowerOn);
    REQUIRE(controller.isOn());
}

TEST_CASE("Given StatefulController when processing command failed then restarted and turned on")
{
    auto driver           = std::make_unique<DriverMock>();
    auto processor        = std::make_unique<HandlerMock>();
    processor->returnCode = Error::SystemError;
    StatefulController controller{std::move(driver), std::move(processor), InitializerMock};
    controller.turnOn();
    REQUIRE(controller.isOn());

    controller.processCommand(Command::PowerOn);
    controller.processCommand(Command::PowerOn);
    REQUIRE(controller.isOn());
}

A module-bluetooth/tests/tests-main.cpp => module-bluetooth/tests/tests-main.cpp +5 -0
@@ 0,0 1,5 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
#include <catch2/catch.hpp>

M module-services/service-bluetooth/ServiceBluetooth.cpp => module-services/service-bluetooth/ServiceBluetooth.cpp +13 -19
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "Constants.hpp"


@@ 42,6 42,7 @@ sys::ReturnCodes ServiceBluetooth::InitHandler()
{
    LOG_ERROR("Bluetooth experimental!");
    worker = std::make_unique<BluetoothWorker>(this);
    worker->run();

    connect(message::bluetooth::RequestBondedDevices(), [&](sys::Message *msg) {
        auto bondedDevicesStr =


@@ 65,21 66,20 @@ sys::ReturnCodes ServiceBluetooth::InitHandler()

    connect(typeid(message::bluetooth::SetStatus), [&](sys::Message *msg) {
        auto setStatusMsg = static_cast<message::bluetooth::SetStatus *>(msg);
        auto btStatus     = setStatusMsg->getStatus();
        worker->setVisibility(btStatus.visibility);
        auto newBtStatus  = setStatusMsg->getStatus();

        switch (btStatus.state) {
        switch (newBtStatus.state) {
        case BluetoothStatus::State::On:
            worker->run();
            sendWorkerCommand(bluetooth::Command::PowerOn);
            break;
        case BluetoothStatus::State::Off:
            // TODO
            sendWorkerCommand(bluetooth::Command::PowerOff);
            break;
        default:
            break;
        }

        return std::make_shared<message::bluetooth::ResponseStatus>(btStatus);
        sendWorkerCommand(newBtStatus.visibility ? bluetooth::VisibilityOn : bluetooth::VisibilityOff);
        return sys::MessageNone{};
    });

    connect(sdesktop::developerMode::DeveloperModeRequest(), [&](sys::Message *msg) {


@@ 99,7 99,6 @@ sys::ReturnCodes ServiceBluetooth::InitHandler()
        auto initialState = std::visit(bluetooth::IntVisitor(), settingsHolder->getValue(bluetooth::Settings::State));
        if (static_cast<BluetoothStatus::State>(initialState) == BluetoothStatus::State::On) {
            settingsHolder->setValue(bluetooth::Settings::State, static_cast<int>(BluetoothStatus::State::Off));
            worker->run();
        }
    };



@@ 122,15 121,10 @@ sys::MessagePointer ServiceBluetooth::DataReceivedHandler(sys::DataMessage *msg,
            switch (lmsg->req) {
            case BluetoothMessage::Start:
                worker->run();

                break;
            case BluetoothMessage::Scan:
                if (worker->scan()) {
                    return std::make_shared<sys::ResponseMessage>(sys::ReturnCodes::Success);
                }
                else {
                    return std::make_shared<sys::ResponseMessage>(sys::ReturnCodes::Failure);
                }
                sendWorkerCommand(bluetooth::StartScan);
                break;
            case BluetoothMessage::StopScan:
                sendWorkerCommand(bluetooth::StopScan);
                break;


@@ 145,19 139,19 @@ sys::MessagePointer ServiceBluetooth::DataReceivedHandler(sys::DataMessage *msg,
                //                    else {
                /// TODO request PPP
                LOG_INFO("Start PAN");
                worker->start_pan();
                sendWorkerCommand(bluetooth::StartPan);
                //                    }
            } break;
            case BluetoothMessage::Visible: {
                static bool visibility = true;
                worker->setVisibility(visibility);
                sendWorkerCommand(visibility ? bluetooth::VisibilityOn : bluetooth::VisibilityOff);
                visibility = !visibility;
            } break;

            case BluetoothMessage::Play:
                sendWorkerCommand(bluetooth::ConnectAudio);
                break;
            case BluetoothMessage::Stop:
            case BluetoothMessage::StopPlayback:
                sendWorkerCommand(bluetooth::DisconnectAudio);
                break;


M module-services/service-bluetooth/service-bluetooth/BluetoothMessage.hpp => module-services/service-bluetooth/service-bluetooth/BluetoothMessage.hpp +2 -2
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 43,7 43,7 @@ class BluetoothMessage : public sys::DataMessage
        PAN,
        Visible,
        Play,
        Stop
        StopPlayback
    };
    enum Request req = Request::None;
    BluetoothMessage(enum Request req = None) : sys::DataMessage(MessageType::BluetoothRequest), req(req){};

M module-services/service-bluetooth/service-bluetooth/SettingsHolder.cpp => module-services/service-bluetooth/service-bluetooth/SettingsHolder.cpp +3 -2
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "SettingsHolder.hpp"


@@ 20,7 20,8 @@ namespace bluetooth
    {
        settingsMap[newSetting] = value;

        settingsProvider->setValue(settingString[newSetting], std::visit(StringVisitor(), value));
        settingsProvider->setValue(
            settingString[newSetting], std::visit(StringVisitor(), value), ::settings::SettingsScope::Global);
        LOG_INFO("setting %s set: %s", settingString[newSetting].c_str(), std::visit(StringVisitor(), value).c_str());
    }
    SettingsHolder::SettingsHolder(std::unique_ptr<settings::Settings> settingsPtr)

M module-services/service-bluetooth/service-bluetooth/SettingsHolder.hpp => module-services/service-bluetooth/service-bluetooth/SettingsHolder.hpp +2 -2
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md
#pragma once
#include <json/json11.hpp>


@@ 82,7 82,7 @@ namespace bluetooth
        auto getValue(const Settings setting) -> SettingEntry;
        void setValue(const Settings &newSetting, const SettingEntry &value);
        std::function<void()> onStateChange;
        std::function<void(std::string addr)> onLinkKeyAdded;
        std::function<void(const std::string &)> onLinkKeyAdded;

      private:
        static std::map<Settings, std::string> settingString;

M module-services/service-desktop/endpoints/developerMode/DeveloperModeHelper.cpp => module-services/service-desktop/endpoints/developerMode/DeveloperModeHelper.cpp +1 -1
@@ 62,7 62,7 @@ auto DeveloperModeHelper::processPutRequest(Context &context) -> sys::ReturnCode
            LOG_INFO("turning on BT from harness!");
        }
        else {
            request = BluetoothMessage::Request::Stop;
            request = BluetoothMessage::Request::StopPlayback;
            LOG_INFO("turning off BT from harness!");
        }
        std::shared_ptr<BluetoothMessage> msg = std::make_shared<BluetoothMessage>(request);