ble nano hid over gatt

Dependencies:   BLE_API mbed-dev nRF51822

Committer:
cho45
Date:
Sat Sep 03 23:07:36 2016 +0900
Revision:
81:fbe7358e38a4
Parent:
80:3beb0293b384
Child:
82:af52d37b1946
comment

Who changed what in which revision?

UserRevisionLine numberNew contents of line
cho45 42:2c3be8694896 1
cho45 5:65d4e94735b6 2 /* mbed Microcontroller Library
cho45 5:65d4e94735b6 3 * Copyright (c) 2015 ARM Limited
cho45 5:65d4e94735b6 4 *
cho45 5:65d4e94735b6 5 * Licensed under the Apache License, Version 2.0 (the "License");
cho45 5:65d4e94735b6 6 * you may not use this file except in compliance with the License.
cho45 5:65d4e94735b6 7 * You may obtain a copy of the License at
cho45 5:65d4e94735b6 8 *
cho45 5:65d4e94735b6 9 * http://www.apache.org/licenses/LICENSE-2.0
cho45 5:65d4e94735b6 10 *
cho45 5:65d4e94735b6 11 * Unless required by applicable law or agreed to in writing, software
cho45 5:65d4e94735b6 12 * distributed under the License is distributed on an "AS IS" BASIS,
cho45 5:65d4e94735b6 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
cho45 5:65d4e94735b6 14 * See the License for the specific language governing permissions and
cho45 5:65d4e94735b6 15 * limitations under the License.
cho45 5:65d4e94735b6 16 */
cho45 5:65d4e94735b6 17
cho45 5:65d4e94735b6 18 #include "HIDServiceBase.h"
cho45 5:65d4e94735b6 19 #include "keyboard.h"
cho45 7:b9270a37345b 20 #include "CircularBuffer.h"
cho45 5:65d4e94735b6 21
cho45 5:65d4e94735b6 22 /**
cho45 5:65d4e94735b6 23 * Report descriptor for a standard 101 keys keyboard, following the HID specification example:
cho45 5:65d4e94735b6 24 * - 8 bytes input report (1 byte for modifiers and 6 for keys)
cho45 5:65d4e94735b6 25 * - 1 byte output report (LEDs)
cho45 5:65d4e94735b6 26 */
cho45 59:2d6c0bff2151 27 const report_map_t KEYBOARD_REPORT_MAP = {
cho45 5:65d4e94735b6 28 USAGE_PAGE(1), 0x01, // Generic Desktop Ctrls
cho45 5:65d4e94735b6 29 USAGE(1), 0x06, // Keyboard
cho45 5:65d4e94735b6 30 COLLECTION(1), 0x01, // Application
cho45 75:351d7ffe81d1 31 USAGE_PAGE(1), 0x07, // Kbrd/Keypad
cho45 75:351d7ffe81d1 32 USAGE_MINIMUM(1), 0xE0,
cho45 75:351d7ffe81d1 33 USAGE_MAXIMUM(1), 0xE7,
cho45 75:351d7ffe81d1 34 LOGICAL_MINIMUM(1), 0x00,
cho45 75:351d7ffe81d1 35 LOGICAL_MAXIMUM(1), 0x01,
cho45 75:351d7ffe81d1 36 REPORT_SIZE(1), 0x01, // 1 byte (Modifier)
cho45 75:351d7ffe81d1 37 REPORT_COUNT(1), 0x08,
cho45 75:351d7ffe81d1 38 INPUT(1), 0x02, // Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position
cho45 75:351d7ffe81d1 39
cho45 75:351d7ffe81d1 40 REPORT_COUNT(1), 0x01, // 1 byte (Reserved)
cho45 75:351d7ffe81d1 41 REPORT_SIZE(1), 0x08,
cho45 75:351d7ffe81d1 42 INPUT(1), 0x01, // Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position
cho45 75:351d7ffe81d1 43
cho45 75:351d7ffe81d1 44 REPORT_COUNT(1), 0x05, // 5 bits (Num lock, Caps lock, Scroll lock, Compose, Kana)
cho45 75:351d7ffe81d1 45 REPORT_SIZE(1), 0x01,
cho45 75:351d7ffe81d1 46 USAGE_PAGE(1), 0x08, // LEDs
cho45 75:351d7ffe81d1 47 USAGE_MINIMUM(1), 0x01, // Num Lock
cho45 75:351d7ffe81d1 48 USAGE_MAXIMUM(1), 0x05, // Kana
cho45 75:351d7ffe81d1 49 OUTPUT(1), 0x02, // Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile
cho45 75:351d7ffe81d1 50
cho45 75:351d7ffe81d1 51 REPORT_COUNT(1), 0x01, // 3 bits (Padding)
cho45 75:351d7ffe81d1 52 REPORT_SIZE(1), 0x03,
cho45 75:351d7ffe81d1 53 OUTPUT(1), 0x01, // Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile
cho45 75:351d7ffe81d1 54
cho45 75:351d7ffe81d1 55 REPORT_COUNT(1), 0x06, // 6 bytes (Keys)
cho45 75:351d7ffe81d1 56 REPORT_SIZE(1), 0x08,
cho45 75:351d7ffe81d1 57 LOGICAL_MINIMUM(1), 0x00,
cho45 75:351d7ffe81d1 58 LOGICAL_MAXIMUM(1), 0x65, // 101 keys
cho45 75:351d7ffe81d1 59 USAGE_PAGE(1), 0x07, // Kbrd/Keypad
cho45 75:351d7ffe81d1 60 USAGE_MINIMUM(1), 0x00,
cho45 75:351d7ffe81d1 61 USAGE_MAXIMUM(1), 0x65,
cho45 75:351d7ffe81d1 62 INPUT(1), 0x00, // Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position
cho45 75:351d7ffe81d1 63
cho45 75:351d7ffe81d1 64 USAGE_PAGE(1), 0x00, // Undefined
cho45 75:351d7ffe81d1 65 USAGE_MINIMUM(1), 0x00,
cho45 75:351d7ffe81d1 66 USAGE_MAXIMUM(1), 0xFF,
cho45 75:351d7ffe81d1 67 REPORT_COUNT(1), 0x01, // 1 byte
cho45 75:351d7ffe81d1 68 REPORT_SIZE(1), 0x08,
cho45 75:351d7ffe81d1 69 FEATURE(1), 0x02, // Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile
cho45 5:65d4e94735b6 70 END_COLLECTION(0),
cho45 5:65d4e94735b6 71 };
cho45 5:65d4e94735b6 72
cho45 75:351d7ffe81d1 73 class KeyboardService : public HIDServiceBase {
cho45 60:b899414e1d34 74
cho45 75:351d7ffe81d1 75 union InputReportData {
cho45 75:351d7ffe81d1 76 uint8_t raw[8];
cho45 75:351d7ffe81d1 77 struct {
cho45 75:351d7ffe81d1 78 uint8_t modifier;
cho45 75:351d7ffe81d1 79 uint8_t padding;
cho45 75:351d7ffe81d1 80 uint8_t keycode[6];
cho45 75:351d7ffe81d1 81 } data;
cho45 75:351d7ffe81d1 82 };
cho45 60:b899414e1d34 83
cho45 75:351d7ffe81d1 84 union OutputReportData {
cho45 75:351d7ffe81d1 85 uint8_t raw[1];
cho45 75:351d7ffe81d1 86 };
cho45 75:351d7ffe81d1 87
cho45 75:351d7ffe81d1 88 union FeatureReportData {
cho45 75:351d7ffe81d1 89 uint8_t raw[1];
cho45 75:351d7ffe81d1 90 };
cho45 7:b9270a37345b 91
cho45 81:fbe7358e38a4 92 /**
cho45 81:fbe7358e38a4 93 * Boot Protocol
cho45 81:fbe7358e38a4 94 * Share input/output report with Report Protocol for memmory saving
cho45 81:fbe7358e38a4 95 */
cho45 79:0095bfb18c57 96 GattCharacteristic bootKeyboardInputReportCharacteristic;
cho45 79:0095bfb18c57 97 GattCharacteristic bootKeyboardOutputReportCharacteristic;
cho45 79:0095bfb18c57 98
cho45 9:d1daefbf1fbd 99 InputReportData inputReportDataPublished;
cho45 7:b9270a37345b 100 InputReportData inputReportData;
cho45 5:65d4e94735b6 101
cho45 60:b899414e1d34 102 // 5ms ごとのスイッチ判定・20ms ごとの送信
cho45 60:b899414e1d34 103 // 人間の連打限界を16press/secとし keydown/keyup それぞれでレポートを送ることを考えると
cho45 60:b899414e1d34 104 // 32report/sec = 31msec で、限界で連打しても20msごとの送信にまにあう
cho45 60:b899414e1d34 105 // ただし上記は1キーに対してであり、複数キーを全力で押すとキューに溜まることがある
cho45 60:b899414e1d34 106 // 実際にカウントして測ってみたところ、どんなに乱暴に連打しても8ぐらいまでしかいかない
cho45 60:b899414e1d34 107 CircularBuffer<InputReportData, 8, uint8_t> inputReportBuffer;
cho45 7:b9270a37345b 108 InputReportData inputReportDataSending;
cho45 75:351d7ffe81d1 109 OutputReportData outputReportData;
cho45 75:351d7ffe81d1 110 FeatureReportData featureReportData;
cho45 7:b9270a37345b 111 bool isSending;
cho45 7:b9270a37345b 112
cho45 5:65d4e94735b6 113 static const uint8_t MODIFIER_LEFT_CONTROL = 1<<0;
cho45 5:65d4e94735b6 114 static const uint8_t MODIFIER_LEFT_SHIFT = 1<<1;
cho45 5:65d4e94735b6 115 static const uint8_t MODIFIER_LEFT_ALT = 1<<2;
cho45 5:65d4e94735b6 116 static const uint8_t MODIFIER_LEFT_GUI = 1<<3;
cho45 5:65d4e94735b6 117 static const uint8_t MODIFIER_RIGHT_CONTROL = 1<<4;
cho45 5:65d4e94735b6 118 static const uint8_t MODIFIER_RIGHT_SHIFT = 1<<5;
cho45 5:65d4e94735b6 119 static const uint8_t MODIFIER_RIGHT_ALT = 1<<6;
cho45 5:65d4e94735b6 120 static const uint8_t MODIFIER_RIGHT_GUI = 1<<7;
cho45 5:65d4e94735b6 121 public:
cho45 45:f4be69c936f6 122 bool sendAvailable;
cho45 60:b899414e1d34 123 uint8_t bufferCount;
cho45 45:f4be69c936f6 124
cho45 5:65d4e94735b6 125 KeyboardService(BLE& _ble) :
cho45 5:65d4e94735b6 126 HIDServiceBase(
cho45 5:65d4e94735b6 127 _ble,
cho45 5:65d4e94735b6 128 KEYBOARD_REPORT_MAP,
cho45 5:65d4e94735b6 129 sizeof(KEYBOARD_REPORT_MAP),
cho45 9:d1daefbf1fbd 130 inputReport = inputReportDataPublished.raw,
cho45 5:65d4e94735b6 131 outputReport = outputReportData.raw,
cho45 75:351d7ffe81d1 132 featureReport = featureReportData.raw,
cho45 5:65d4e94735b6 133 inputReportLength = sizeof(inputReportData),
cho45 5:65d4e94735b6 134 outputReportLength = sizeof(outputReportData),
cho45 75:351d7ffe81d1 135 featureReportLength = sizeof(featureReportData),
cho45 45:f4be69c936f6 136 reportTickerDelay = 20
cho45 7:b9270a37345b 137 ),
cho45 79:0095bfb18c57 138 bootKeyboardInputReportCharacteristic(GattCharacteristic::UUID_BOOT_KEYBOARD_INPUT_REPORT_CHAR,
cho45 79:0095bfb18c57 139 (uint8_t *)inputReport, inputReportLength, inputReportLength,
cho45 79:0095bfb18c57 140 GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ
cho45 79:0095bfb18c57 141 | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_NOTIFY
cho45 79:0095bfb18c57 142 | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE
cho45 79:0095bfb18c57 143 ),
cho45 79:0095bfb18c57 144
cho45 79:0095bfb18c57 145 bootKeyboardOutputReportCharacteristic(GattCharacteristic::UUID_BOOT_KEYBOARD_OUTPUT_REPORT_CHAR,
cho45 79:0095bfb18c57 146 (uint8_t *)outputReport, outputReportLength, outputReportLength,
cho45 79:0095bfb18c57 147 GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_READ
cho45 79:0095bfb18c57 148 | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE_WITHOUT_RESPONSE
cho45 79:0095bfb18c57 149 | GattCharacteristic::BLE_GATT_CHAR_PROPERTIES_WRITE
cho45 79:0095bfb18c57 150 ),
cho45 45:f4be69c936f6 151 isSending(false),
cho45 60:b899414e1d34 152 sendAvailable(false),
cho45 60:b899414e1d34 153 bufferCount(0)
cho45 5:65d4e94735b6 154 {
cho45 6:f1c3ea8bc850 155 for (int i = 0; i < 8; i++) {
cho45 6:f1c3ea8bc850 156 inputReportData.raw[i] = 0;
cho45 6:f1c3ea8bc850 157 }
cho45 6:f1c3ea8bc850 158 outputReportData.raw[0] = 0;
cho45 7:b9270a37345b 159
cho45 7:b9270a37345b 160 inputReportBuffer.reset();
cho45 5:65d4e94735b6 161 }
cho45 5:65d4e94735b6 162
cho45 79:0095bfb18c57 163 virtual void addExtraCharacteristics(GattCharacteristic** characteristics, uint8_t& charIndex) {
cho45 79:0095bfb18c57 164 DEBUG_PRINTF_BLE("addExtraCharacteristics %d\r\n", charIndex);
cho45 79:0095bfb18c57 165 characteristics[charIndex++] = &bootKeyboardInputReportCharacteristic;
cho45 79:0095bfb18c57 166 characteristics[charIndex++] = &bootKeyboardOutputReportCharacteristic;
cho45 79:0095bfb18c57 167 }
cho45 79:0095bfb18c57 168
cho45 79:0095bfb18c57 169 virtual ble_error_t send(const report_t report) {
cho45 79:0095bfb18c57 170 if (protocolMode == REPORT_PROTOCOL) { // this is default
cho45 79:0095bfb18c57 171 return ble.gattServer().write(
cho45 80:3beb0293b384 172 inputReportCharacteristic.getValueHandle(),
cho45 79:0095bfb18c57 173 report,
cho45 79:0095bfb18c57 174 inputReportLength
cho45 79:0095bfb18c57 175 );
cho45 79:0095bfb18c57 176 } else {
cho45 79:0095bfb18c57 177 return ble.gattServer().write(
cho45 80:3beb0293b384 178 bootKeyboardInputReportCharacteristic.getValueHandle(),
cho45 79:0095bfb18c57 179 report,
cho45 79:0095bfb18c57 180 inputReportLength
cho45 79:0095bfb18c57 181 );
cho45 79:0095bfb18c57 182 }
cho45 79:0095bfb18c57 183 }
cho45 79:0095bfb18c57 184
cho45 48:d6938de02f62 185 void appendReportData(const uint8_t keycode) {
cho45 5:65d4e94735b6 186 uint8_t modifier = toModifierBit(keycode);
cho45 5:65d4e94735b6 187 if (modifier) {
cho45 5:65d4e94735b6 188 inputReportData.data.modifier |= modifier;
cho45 5:65d4e94735b6 189 return;
cho45 5:65d4e94735b6 190 }
cho45 5:65d4e94735b6 191
cho45 5:65d4e94735b6 192
cho45 5:65d4e94735b6 193 for (int i = 0; i < 6; i++) {
cho45 5:65d4e94735b6 194 if (inputReportData.data.keycode[i] == 0) {
cho45 5:65d4e94735b6 195 inputReportData.data.keycode[i] = keycode;
cho45 5:65d4e94735b6 196 return;
cho45 5:65d4e94735b6 197 }
cho45 5:65d4e94735b6 198 }
cho45 5:65d4e94735b6 199
cho45 5:65d4e94735b6 200 // TODO: report data is full
cho45 5:65d4e94735b6 201 }
cho45 5:65d4e94735b6 202
cho45 48:d6938de02f62 203 void deleteReportData(const uint8_t keycode) {
cho45 5:65d4e94735b6 204 uint8_t modifier = toModifierBit(keycode);
cho45 5:65d4e94735b6 205 if (modifier) {
cho45 5:65d4e94735b6 206 inputReportData.data.modifier &= ~modifier;
cho45 5:65d4e94735b6 207 return;
cho45 5:65d4e94735b6 208 }
cho45 5:65d4e94735b6 209
cho45 5:65d4e94735b6 210 for (int i = 0; i < 6; i++) {
cho45 5:65d4e94735b6 211 if (inputReportData.data.keycode[i] == keycode) {
cho45 5:65d4e94735b6 212 inputReportData.data.keycode[i] = 0;
cho45 5:65d4e94735b6 213 return;
cho45 5:65d4e94735b6 214 }
cho45 5:65d4e94735b6 215 }
cho45 5:65d4e94735b6 216 }
cho45 5:65d4e94735b6 217
cho45 9:d1daefbf1fbd 218 void queueCurrentReportData() {
cho45 80:3beb0293b384 219 DEBUG_PRINTF_BLE("Q %d\r\n", bufferCount);
cho45 60:b899414e1d34 220 bufferCount++;
cho45 9:d1daefbf1fbd 221 inputReportBuffer.push(inputReportData);
cho45 9:d1daefbf1fbd 222 startReportTicker();
cho45 9:d1daefbf1fbd 223 }
cho45 9:d1daefbf1fbd 224
cho45 48:d6938de02f62 225 uint8_t toModifierBit(const uint8_t keycode) const {
cho45 5:65d4e94735b6 226 switch (keycode) {
cho45 5:65d4e94735b6 227 case KEY_LeftControl: return MODIFIER_LEFT_CONTROL;
cho45 5:65d4e94735b6 228 case KEY_LeftShift: return MODIFIER_LEFT_SHIFT;
cho45 5:65d4e94735b6 229 case KEY_LeftAlt: return MODIFIER_LEFT_ALT;
cho45 5:65d4e94735b6 230 case KEY_LeftGUI: return MODIFIER_LEFT_GUI;
cho45 5:65d4e94735b6 231 case KEY_RightControl: return MODIFIER_RIGHT_CONTROL;
cho45 5:65d4e94735b6 232 case KEY_RightShift: return MODIFIER_RIGHT_SHIFT;
cho45 5:65d4e94735b6 233 case KEY_RightAlt: return MODIFIER_RIGHT_ALT;
cho45 5:65d4e94735b6 234 case KEY_RightGUI: return MODIFIER_RIGHT_GUI;
cho45 5:65d4e94735b6 235 }
cho45 5:65d4e94735b6 236 return 0;
cho45 5:65d4e94735b6 237 }
cho45 5:65d4e94735b6 238
cho45 5:65d4e94735b6 239 bool isKeyPressed() {
cho45 5:65d4e94735b6 240 for (int i = 0; i < 8; i++) {
cho45 5:65d4e94735b6 241 if (inputReportData.raw[i]) {
cho45 5:65d4e94735b6 242 return 1;
cho45 5:65d4e94735b6 243 }
cho45 5:65d4e94735b6 244 }
cho45 5:65d4e94735b6 245 return 0;
cho45 5:65d4e94735b6 246 }
cho45 5:65d4e94735b6 247
cho45 5:65d4e94735b6 248 virtual void sendCallback(void) {
cho45 7:b9270a37345b 249 // do not call printf in this function... it cause BLE_STACK_BUSY
cho45 45:f4be69c936f6 250 sendAvailable = true;
cho45 45:f4be69c936f6 251 }
cho45 45:f4be69c936f6 252
cho45 45:f4be69c936f6 253 void processSend() {
cho45 45:f4be69c936f6 254 if (!sendAvailable) {
cho45 45:f4be69c936f6 255 return;
cho45 45:f4be69c936f6 256 }
cho45 45:f4be69c936f6 257 sendAvailable = false;
cho45 45:f4be69c936f6 258
cho45 45:f4be69c936f6 259 // isSending の場合現在送信中の report があり、再送中である可能性がある
cho45 45:f4be69c936f6 260 // そうではない場合のみ queue から pop して新しく send する
cho45 9:d1daefbf1fbd 261 if (!isSending) {
cho45 9:d1daefbf1fbd 262 if (!inputReportBuffer.pop(inputReportDataSending)) {
cho45 51:d9297f8a60b7 263 // 送るデータがないなら送信をやめる
cho45 51:d9297f8a60b7 264 stopReportTicker();
cho45 51:d9297f8a60b7 265 return;
cho45 7:b9270a37345b 266 }
cho45 60:b899414e1d34 267 bufferCount--;
cho45 7:b9270a37345b 268 }
cho45 7:b9270a37345b 269
cho45 7:b9270a37345b 270 static uint8_t busyCount = 0;
cho45 7:b9270a37345b 271 isSending = true;
cho45 80:3beb0293b384 272 ble_error_t error = send(inputReportDataSending.raw);
cho45 5:65d4e94735b6 273 if (error == BLE_STACK_BUSY) {
cho45 7:b9270a37345b 274 if (busyCount++ > 10) {
cho45 80:3beb0293b384 275 DEBUG_PRINTF_BLE("BLE_STACK_BUSY\r\n");
cho45 7:b9270a37345b 276 busyCount = 0;
cho45 7:b9270a37345b 277 stopReportTicker();
cho45 7:b9270a37345b 278 }
cho45 5:65d4e94735b6 279 // retry after
cho45 5:65d4e94735b6 280 return;
cho45 5:65d4e94735b6 281 }
cho45 7:b9270a37345b 282 isSending = false;
cho45 7:b9270a37345b 283 }
cho45 5:65d4e94735b6 284
cho45 7:b9270a37345b 285 virtual void onDataSent(unsigned int count) {
cho45 7:b9270a37345b 286 startReportTicker();
cho45 5:65d4e94735b6 287 }
cho45 79:0095bfb18c57 288
cho45 79:0095bfb18c57 289 virtual void onDataWritten(const GattWriteCallbackParams *params) {
cho45 79:0095bfb18c57 290 if (params->handle == inputReportCharacteristic.getValueHandle()) {
cho45 79:0095bfb18c57 291 DEBUG_PRINTF_BLE_INTERRUPT("onDataWritten: inputReport %d bytes\r\n", params->len);
cho45 79:0095bfb18c57 292 } else
cho45 79:0095bfb18c57 293 if (params->handle == outputReportCharacteristic.getValueHandle()) {
cho45 79:0095bfb18c57 294 DEBUG_PRINTF_BLE_INTERRUPT("onDataWritten: outputReport %d bytes\r\n", params->len);
cho45 79:0095bfb18c57 295 } else
cho45 79:0095bfb18c57 296 if (params->handle == featureReportCharacteristic.getValueHandle()) {
cho45 79:0095bfb18c57 297 DEBUG_PRINTF_BLE_INTERRUPT("onDataWritten: featureReport %d bytes\r\n", params->len);
cho45 79:0095bfb18c57 298 } else
cho45 79:0095bfb18c57 299 if (params->handle == protocolModeCharacteristic.getValueHandle()) {
cho45 79:0095bfb18c57 300 DEBUG_PRINTF_BLE_INTERRUPT("onDataWritten: protocolMode %d bytes %d\r\n", params->len, params->data[0]);
cho45 79:0095bfb18c57 301 protocolMode = params->data[0];
cho45 79:0095bfb18c57 302 }
cho45 79:0095bfb18c57 303 }
cho45 79:0095bfb18c57 304
cho45 21:d801c32231b0 305 virtual void stopReportTicker(void) {
cho45 34:7da766a8aa96 306 if (reportTickerIsActive) {
cho45 34:7da766a8aa96 307 HIDServiceBase::stopReportTicker();
cho45 34:7da766a8aa96 308 }
cho45 21:d801c32231b0 309 }
cho45 5:65d4e94735b6 310 };
cho45 5:65d4e94735b6 311
cho45 5:65d4e94735b6 312