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