diff options
| author | Joel Klinghed <the_jk@spawned.biz> | 2025-10-19 20:59:07 +0200 |
|---|---|---|
| committer | Joel Klinghed <the_jk@spawned.biz> | 2025-10-20 09:15:50 +0200 |
| commit | 99527bdf3a12433936d2ab74c78284f59aaaaeed (patch) | |
| tree | 026bb1b48d65c21910166329aacc594fd84ebf28 | |
| parent | c0e24cc461cb60cde2791cd89d59a1ff1be0f6b2 (diff) | |
bt & main: Add more properties for adapter
Allow controller to be set in discoverable state.
| -rw-r--r-- | src/bt.cc | 87 | ||||
| -rw-r--r-- | src/bt.hh | 16 | ||||
| -rw-r--r-- | src/main.cc | 79 |
3 files changed, 168 insertions, 14 deletions
@@ -368,6 +368,10 @@ class AdapterProxy : public BaseProxy, public Adapter { {sdbus::PropertyName{"Discoverable"}, 3}, {sdbus::PropertyName{"Pairable"}, 4}, {sdbus::PropertyName{"Pairing"}, 5}, + {sdbus::PropertyName{"Connectable"}, 6}, + {sdbus::PropertyName{"Powered"}, 7}, + {sdbus::PropertyName{"DiscoverableTimeout"}, 8}, + {sdbus::PropertyName{"PairableTimeout"}, 9}, }; public: @@ -399,20 +403,71 @@ class AdapterProxy : public BaseProxy, public Adapter { } [[nodiscard]] + uint32_t discoverable_timeout_seconds() const override { + return discoverable_timeout_; + } + + [[nodiscard]] bool pairable() const override { return pairable_; } [[nodiscard]] + uint32_t pairable_timeout_seconds() const override { + return pairable_timeout_; + } + + [[nodiscard]] bool pairing() const override { return pairing_; } [[nodiscard]] + bool powered() const override { + return powered_; + } + + [[nodiscard]] + bool connectable() const override { + return connectable_; + } + + [[nodiscard]] std::vector<Device*> devices() const override { return manager_.get_devices(getProxy().getObjectPath()); } + void set_discoverable(bool discoverable) override { + if (discoverable == discoverable_) + return; + + SetAsync(kAdapterInterface, sdbus::PropertyName{"Discoverable"}, + sdbus::Variant{discoverable}, + [this](std::optional<sdbus::Error> err) { + set_callback(std::move(err)); + }); + } + + void set_discoverable_timeout_seconds(uint32_t seconds) override { + if (discoverable_timeout_ == seconds) + return; + + SetAsync(kAdapterInterface, sdbus::PropertyName{"DiscoverableTimeout"}, + sdbus::Variant{seconds}, [this](std::optional<sdbus::Error> err) { + set_callback(std::move(err)); + }); + } + + void set_pairable_timeout_seconds(uint32_t seconds) override { + if (pairable_timeout_ == seconds) + return; + + SetAsync(kAdapterInterface, sdbus::PropertyName{"PairableTimeout"}, + sdbus::Variant{seconds}, [this](std::optional<sdbus::Error> err) { + set_callback(std::move(err)); + }); + } + private: void update(size_t field, sdbus::Variant const& value) override { switch (field) { @@ -430,16 +485,35 @@ class AdapterProxy : public BaseProxy, public Adapter { break; case 4: pairable_ = value.get<bool>(); - manager_.logger().dbg(std::format("pairable: {}", pairable_)); break; case 5: pairing_ = value.get<bool>(); break; + case 6: + connectable_ = value.get<bool>(); + break; + case 7: + powered_ = value.get<bool>(); + break; + case 8: + discoverable_timeout_ = value.get<uint32_t>(); + break; + case 9: + pairable_timeout_ = value.get<uint32_t>(); + break; default: std::unreachable(); } } + void set_callback(std::optional<sdbus::Error> err) { + if (err.has_value()) { + manager_.logger().warn(std::format("Failed to set property: {} {}", + std::string(err->getName()), + err->getMessage())); + } + } + void notify_updated() override { manager_.update_adapter(getProxy().getObjectPath()); } @@ -451,6 +525,10 @@ class AdapterProxy : public BaseProxy, public Adapter { bool discoverable_{false}; bool pairable_{false}; bool pairing_{false}; + bool connectable_{false}; + bool powered_{false}; + uint32_t discoverable_timeout_{180}; + uint32_t pairable_timeout_{0}; }; class AgentManagerProxy { @@ -914,16 +992,17 @@ class ManagerImpl : public Manager, public IManager, looper::Hook { auto cfg_adapter = cfg_.get("bluetooth.adapter"); if (cfg_adapter.has_value()) { for (auto const& pair : adapters_) { - if (pair.second->address() == cfg_adapter.value()) { + if (pair.second->address() == cfg_adapter.value() && + pair.second->powered()) { primary_adapter_ = pair.first; delegate_.new_adapter(pair.second.get()); return; } } } else { - // Pick first (map is sorted) that is pairable. + // Pick first (map is sorted) that is pairable and powered. for (auto const& pair : adapters_) { - if (pair.second->pairable()) { + if (pair.second->powered() && pair.second->pairable()) { primary_adapter_ = pair.first; delegate_.new_adapter(pair.second.get()); return; @@ -38,14 +38,30 @@ class Adapter { virtual bool discoverable() const = 0; [[nodiscard]] + virtual uint32_t discoverable_timeout_seconds() const = 0; + + [[nodiscard]] virtual bool pairable() const = 0; [[nodiscard]] + virtual uint32_t pairable_timeout_seconds() const = 0; + + [[nodiscard]] virtual bool pairing() const = 0; [[nodiscard]] + virtual bool powered() const = 0; + + [[nodiscard]] + virtual bool connectable() const = 0; + + [[nodiscard]] virtual std::vector<Device*> devices() const = 0; + virtual void set_discoverable(bool discoverable) = 0; + virtual void set_discoverable_timeout_seconds(uint32_t timeout) = 0; + virtual void set_pairable_timeout_seconds(uint32_t timeout) = 0; + protected: Adapter() = default; Adapter(Adapter const&) = delete; diff --git a/src/main.cc b/src/main.cc index 494c49c..4555473 100644 --- a/src/main.cc +++ b/src/main.cc @@ -54,6 +54,14 @@ class Signaler { Signaler() = default; }; +std::optional<bool> match_bool(std::string_view str) { + if (str == "true") + return true; + if (str == "false") + return false; + return std::nullopt; +} + class HttpServerDelegate : public http::Server::Delegate { public: HttpServerDelegate(Api& api, Signaler& signaler) @@ -64,22 +72,21 @@ class HttpServerDelegate : public http::Server::Delegate { std::unique_ptr<http::Response> handle( http::Request const& request) override { - if (request.method() != "GET") { - return http::Response::status(http::StatusCode::kMethodNotAllowed); - } - if (request.path().starts_with("/api/v1/")) { auto path = request.path().substr(8); if (path == "status") { - json_writer_->clear(); - json_writer_->start_object(); - json_writer_->key("status"); - json_writer_->value("OK"); - json_writer_->end_object(); - return http::Response::content(json_tmp_, *json_mimetype_); + if (request.method() != "GET") { + return http::Response::status(http::StatusCode::kMethodNotAllowed); + } + + return status_ok(); } if (path == "controller") { + if (request.method() != "GET") { + return http::Response::status(http::StatusCode::kMethodNotAllowed); + } + auto* adapter = api_.adapter(); json_writer_->clear(); json_writer_->start_object(); @@ -87,6 +94,11 @@ class HttpServerDelegate : public http::Server::Delegate { json_writer_->value(adapter ? adapter->name() : "unknown"); json_writer_->key("pairable"); json_writer_->value(adapter ? adapter->pairable() : false); + json_writer_->key("discoverable"); + json_writer_->value(adapter ? adapter->discoverable() : false); + json_writer_->key("discoverable_timeout_seconds"); + json_writer_->value(adapter ? adapter->discoverable_timeout_seconds() + : 0); json_writer_->key("pairing"); json_writer_->value(adapter ? adapter->pairing() : false); json_writer_->end_object(); @@ -94,7 +106,24 @@ class HttpServerDelegate : public http::Server::Delegate { return http::Response::content(json_tmp_, *json_mimetype_); } + if (path == "controller/discoverable") { + if (request.method() != "POST") { + return http::Response::status(http::StatusCode::kMethodNotAllowed); + } + std::optional<bool> value = match_bool(request.body()); + auto* adapter = api_.adapter(); + if (adapter && value.has_value()) { + adapter->set_discoverable(value.value()); + return status_ok(); + } + return status_error("Bad state"); + } + if (path == "events") { + if (request.method() != "GET") { + return http::Response::status(http::StatusCode::kMethodNotAllowed); + } + auto resp = signaler_.handle(request); if (resp) return resp; @@ -106,6 +135,26 @@ class HttpServerDelegate : public http::Server::Delegate { } private: + std::unique_ptr<http::Response> status_ok() { + json_writer_->clear(); + json_writer_->start_object(); + json_writer_->key("status"); + json_writer_->value("OK"); + json_writer_->end_object(); + return http::Response::content(json_tmp_, *json_mimetype_); + } + + std::unique_ptr<http::Response> status_error(std::string_view message) { + json_writer_->clear(); + json_writer_->start_object(); + json_writer_->key("status"); + json_writer_->value("error"); + json_writer_->key("message"); + json_writer_->value(message); + json_writer_->end_object(); + return http::Response::content(json_tmp_, *json_mimetype_); + } + Api& api_; Signaler& signaler_; std::unique_ptr<json::Writer> json_writer_; @@ -131,6 +180,16 @@ class BluetoothManagerDelegate : public bt::Manager::Delegate, public Api { if (adapter) { logger_.info(std::format("New adapter: {} [{}]", adapter->name(), adapter->address())); + + // Assuming pairable doesn't have a timeout - make it so. + if (adapter_->pairable_timeout_seconds() > 0) { + adapter_->set_pairable_timeout_seconds(0); + } + + // Assuming discoverable has one + if (adapter_->discoverable_timeout_seconds() == 0) { + adapter_->set_discoverable_timeout_seconds(180); + } } else { logger_.info("No adapter"); } |
