• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 HIMSA II K/S - www.himsa.com.
3  * Represented by EHIMA - www.ehima.com
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 #pragma once
19 
20 #include <list>
21 #include <optional>
22 
23 #include "hardware/bt_has.h"
24 #include "has_preset.h"
25 #include "osi/include/alarm.h"
26 
27 namespace le_audio {
28 namespace has {
29 /* HAS control point Change Id */
30 enum class PresetCtpChangeId : uint8_t {
31   PRESET_GENERIC_UPDATE = 0,
32   PRESET_DELETED,
33   PRESET_AVAILABLE,
34   PRESET_UNAVAILABLE,
35   /* NOTICE: Values below are for internal use only of this particular
36    * implementation, and do not correspond to any bluetooth specification.
37    */
38   CHANGE_ID_MAX_ = PRESET_UNAVAILABLE,
39 };
40 std::ostream& operator<<(std::ostream& out, const PresetCtpChangeId value);
41 
42 /* HAS control point Opcodes */
43 enum class PresetCtpOpcode : uint8_t {
44   READ_PRESETS = 1,
45   READ_PRESET_RESPONSE,
46   PRESET_CHANGED,
47   WRITE_PRESET_NAME,
48   SET_ACTIVE_PRESET,
49   SET_NEXT_PRESET,
50   SET_PREV_PRESET,
51   SET_ACTIVE_PRESET_SYNC,
52   SET_NEXT_PRESET_SYNC,
53   SET_PREV_PRESET_SYNC,
54   /* NOTICE: Values below are for internal use only of this particular
55    * implementation, and do not correspond to any bluetooth specification.
56    */
57   OP_MAX_ = SET_PREV_PRESET_SYNC,
58   OP_NONE_ = OP_MAX_ + 1,
59 };
60 std::ostream& operator<<(std::ostream& out, const PresetCtpOpcode value);
61 
PresetCtpOpcode2Bitmask(PresetCtpOpcode op)62 static constexpr uint16_t PresetCtpOpcode2Bitmask(PresetCtpOpcode op) {
63   return ((uint16_t)0b1 << static_cast<std::underlying_type_t<PresetCtpOpcode>>(
64               op));
65 }
66 
67 /* Mandatory opcodes if control point characteristic exists */
68 static constexpr uint16_t kControlPointMandatoryOpcodesBitmask =
69     PresetCtpOpcode2Bitmask(PresetCtpOpcode::READ_PRESETS) |
70     PresetCtpOpcode2Bitmask(PresetCtpOpcode::SET_ACTIVE_PRESET) |
71     PresetCtpOpcode2Bitmask(PresetCtpOpcode::SET_NEXT_PRESET) |
72     PresetCtpOpcode2Bitmask(PresetCtpOpcode::SET_PREV_PRESET);
73 
74 /* Optional coordinated operation opcodes */
75 static constexpr uint16_t kControlPointSynchronizedOpcodesBitmask =
76     PresetCtpOpcode2Bitmask(PresetCtpOpcode::SET_ACTIVE_PRESET_SYNC) |
77     PresetCtpOpcode2Bitmask(PresetCtpOpcode::SET_NEXT_PRESET_SYNC) |
78     PresetCtpOpcode2Bitmask(PresetCtpOpcode::SET_PREV_PRESET_SYNC);
79 
80 /* Represents HAS Control Point value notification */
81 struct HasCtpNtf {
82   PresetCtpOpcode opcode;
83   PresetCtpChangeId change_id;
84   bool is_last;
85   union {
86     uint8_t index;
87     uint8_t prev_index;
88   };
89   std::optional<HasPreset> preset;
90 
91   static std::optional<HasCtpNtf> FromCharacteristicValue(uint16_t len,
92                                                           const uint8_t* value);
93 };
94 std::ostream& operator<<(std::ostream& out, const HasCtpNtf& value);
95 
96 /* Represents HAS Control Point operation request */
97 struct HasCtpOp {
98   std::variant<RawAddress, int> addr_or_group;
99   PresetCtpOpcode opcode;
100   uint8_t index;
101   uint8_t num_of_indices;
102   std::optional<std::string> name;
103   uint16_t op_id;
104 
105   HasCtpOp(std::variant<RawAddress, int> addr_or_group_id, PresetCtpOpcode op,
106            uint8_t index = bluetooth::has::kHasPresetIndexInvalid,
107            uint8_t num_of_indices = 1,
108            std::optional<std::string> name = std::nullopt)
addr_or_groupHasCtpOp109       : addr_or_group(addr_or_group_id),
110         opcode(op),
111         index(index),
112         num_of_indices(num_of_indices),
113         name(name) {
114     /* Skip 0 on roll-over */
115     last_op_id_ += 1;
116     if (last_op_id_ == 0) last_op_id_ = 1;
117     op_id = last_op_id_;
118   }
119 
120   std::vector<uint8_t> ToCharacteristicValue(void) const;
121 
IsGroupRequestHasCtpOp122   bool IsGroupRequest() const {
123     return std::holds_alternative<int>(addr_or_group);
124   }
125 
GetGroupIdHasCtpOp126   int GetGroupId() const {
127     return std::holds_alternative<int>(addr_or_group)
128                ? std::get<int>(addr_or_group)
129                : -1;
130   }
131 
GetDeviceAddrHasCtpOp132   RawAddress GetDeviceAddr() const {
133     return std::holds_alternative<RawAddress>(addr_or_group)
134                ? std::get<RawAddress>(addr_or_group)
135                : RawAddress::kEmpty;
136   }
137 
IsSyncedOperationHasCtpOp138   bool IsSyncedOperation() const {
139     return (opcode == PresetCtpOpcode::SET_ACTIVE_PRESET_SYNC) ||
140            (opcode == PresetCtpOpcode::SET_NEXT_PRESET_SYNC) ||
141            (opcode == PresetCtpOpcode::SET_PREV_PRESET_SYNC);
142   }
143 
144  private:
145   /* It's fine for this to roll-over eventually */
146   static uint16_t last_op_id_;
147 };
148 std::ostream& operator<<(std::ostream& out, const HasCtpOp& value);
149 
150 /* Used to track group operations. SetCompleted() allows to mark
151  * a single device as operation-completed when notification is received.
152  * When all the devices are SetComplete'd, timeout timer is being canceled and
153  * a group operation can be considered completed (IsFullyCompleted() == true).
154  *
155  * NOTICE: A single callback and reference counter is being used for all the
156  *         coordinator instances, therefore creating more instances result
157  *         in timeout timer being rescheduled. User should remove all the
158  *         pending op. coordinators in the timer timeout callback.
159  */
160 struct HasCtpGroupOpCoordinator {
161   std::list<RawAddress> devices;
162   HasCtpOp operation;
163   std::list<bluetooth::has::PresetInfo> preset_info_verification_list;
164 
165   static size_t ref_cnt;
166   static alarm_t* operation_timeout_timer;
167   static constexpr uint16_t kOperationTimeoutMs = 10000u;
168   static alarm_callback_t cb;
169 
170   static void Initialize(alarm_callback_t c = nullptr) {
171     operation_timeout_timer = nullptr;
172     ref_cnt = 0;
173     cb = c;
174   }
175 
CleanupHasCtpGroupOpCoordinator176   static void Cleanup() {
177     if (operation_timeout_timer != nullptr) {
178       if (alarm_is_scheduled(operation_timeout_timer)) {
179         DLOG(INFO) << __func__ << +ref_cnt;
180         alarm_cancel(operation_timeout_timer);
181       }
182       alarm_free(operation_timeout_timer);
183       operation_timeout_timer = nullptr;
184     }
185 
186     ref_cnt = 0;
187   }
188 
IsFullyCompletedHasCtpGroupOpCoordinator189   static bool IsFullyCompleted() { return ref_cnt == 0; }
IsPendingHasCtpGroupOpCoordinator190   static bool IsPending() { return ref_cnt != 0; }
191 
192   HasCtpGroupOpCoordinator() = delete;
193   HasCtpGroupOpCoordinator& operator=(const HasCtpGroupOpCoordinator&) = delete;
194   /* NOTICE: It cannot be non-copyable if we want to put it into the std::map.
195    * The default copy constructor and copy assignment operator would break the
196    * reference counting, so we must increment ref_cnt for all the temporary
197    * copies.
198    */
HasCtpGroupOpCoordinatorHasCtpGroupOpCoordinator199   HasCtpGroupOpCoordinator(const HasCtpGroupOpCoordinator& other)
200       : devices(other.devices),
201         operation(other.operation),
202         preset_info_verification_list(other.preset_info_verification_list) {
203     ref_cnt += other.devices.size();
204   }
205 
HasCtpGroupOpCoordinatorHasCtpGroupOpCoordinator206   HasCtpGroupOpCoordinator(const std::vector<RawAddress>& targets,
207                            HasCtpOp operation)
208       : operation(operation) {
209     LOG_ASSERT(targets.size() != 0) << " Empty device list error.";
210     if (targets.size() != 1) {
211       LOG_ASSERT(operation.IsGroupRequest()) << " Must be a group operation!";
212       LOG_ASSERT(operation.GetGroupId() != -1) << " Must set valid group_id!";
213     }
214 
215     devices = std::list<RawAddress>(targets.cbegin(), targets.cend());
216 
217     ref_cnt += devices.size();
218     if (operation_timeout_timer == nullptr) {
219       operation_timeout_timer = alarm_new("GroupOpTimer");
220     }
221 
222     if (alarm_is_scheduled(operation_timeout_timer))
223       alarm_cancel(operation_timeout_timer);
224 
225     LOG_ASSERT(cb != nullptr) << " Timeout timer callback not set!";
226     alarm_set_on_mloop(operation_timeout_timer, kOperationTimeoutMs, cb,
227                        nullptr);
228   }
229 
~HasCtpGroupOpCoordinatorHasCtpGroupOpCoordinator230   ~HasCtpGroupOpCoordinator() {
231     /* Check if cleanup wasn't already called */
232     if (ref_cnt != 0) {
233       ref_cnt -= devices.size();
234       if (ref_cnt == 0) {
235         Cleanup();
236       }
237     }
238   }
239 
SetCompletedHasCtpGroupOpCoordinator240   bool SetCompleted(RawAddress addr) {
241     auto result = false;
242 
243     auto it = std::find(devices.begin(), devices.end(), addr);
244     if (it != devices.end()) {
245       devices.erase(it);
246       --ref_cnt;
247       result = true;
248     }
249 
250     if (ref_cnt == 0) {
251       alarm_cancel(operation_timeout_timer);
252       alarm_free(operation_timeout_timer);
253       operation_timeout_timer = nullptr;
254     }
255 
256     return result;
257   }
258 };
259 
260 }  // namespace has
261 }  // namespace le_audio
262