ir_user.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. // Copyright 2015 Citra Emulator Project
  2. // Licensed under GPLv2 or any later version
  3. // Refer to the license.txt file included.
  4. #include <memory>
  5. #include <boost/crc.hpp>
  6. #include <boost/optional.hpp>
  7. #include "common/string_util.h"
  8. #include "common/swap.h"
  9. #include "core/hle/ipc_helpers.h"
  10. #include "core/hle/kernel/event.h"
  11. #include "core/hle/kernel/shared_memory.h"
  12. #include "core/hle/service/ir/extra_hid.h"
  13. #include "core/hle/service/ir/ir.h"
  14. #include "core/hle/service/ir/ir_user.h"
  15. namespace Service {
  16. namespace IR {
  17. // This is a header that will present in the ir:USER shared memory if it is initialized with
  18. // InitializeIrNopShared service function. Otherwise the shared memory doesn't have this header if
  19. // it is initialized with InitializeIrNop service function.
  20. struct SharedMemoryHeader {
  21. u32_le latest_receive_error_result;
  22. u32_le latest_send_error_result;
  23. // TODO(wwylele): for these fields below, make them enum when the meaning of values is known.
  24. u8 connection_status;
  25. u8 trying_to_connect_status;
  26. u8 connection_role;
  27. u8 machine_id;
  28. u8 connected;
  29. u8 network_id;
  30. u8 initialized;
  31. u8 unknown;
  32. // This is not the end of the shared memory. It is followed by a receive buffer and a send
  33. // buffer. We handle receive buffer in the BufferManager class. For the send buffer, because
  34. // games usually don't access it, we don't emulate it.
  35. };
  36. static_assert(sizeof(SharedMemoryHeader) == 16, "SharedMemoryHeader has wrong size!");
  37. /**
  38. * A manager of the send/receive buffers in the shared memory. Currently it is only used for the
  39. * receive buffer.
  40. *
  41. * A buffer consists of three parts:
  42. * - BufferInfo: stores available count of packets, and their position in the PacketInfo
  43. * circular queue.
  44. * - PacketInfo circular queue: stores the position of each avaiable packets in the Packet data
  45. * buffer. Each entry is a pair of {offset, size}.
  46. * - Packet data circular buffer: stores the actual data of packets.
  47. *
  48. * IR packets can be put into and get from the buffer.
  49. *
  50. * When a new packet is put into the buffer, its data is put into the data circular buffer,
  51. * following the end of previous packet data. A new entry is also added to the PacketInfo circular
  52. * queue pointing to the added packet data. Then BufferInfo is updated.
  53. *
  54. * Packets can be released from the other end of the buffer. When releasing a packet, the front
  55. * entry in thePacketInfo circular queue is removed, and as a result the corresponding memory in the
  56. * data circular buffer is also released. BufferInfo is updated as well.
  57. *
  58. * The client application usually has a similar manager constructed over the same shared memory
  59. * region, performing the same put/get/release operation. This way the client and the service
  60. * communicate via a pair of manager of the same buffer.
  61. *
  62. * TODO(wwylele): implement Get function, which is used by ReceiveIrnop service function.
  63. */
  64. class BufferManager {
  65. public:
  66. BufferManager(Kernel::SharedPtr<Kernel::SharedMemory> shared_memory_, u32 info_offset_,
  67. u32 buffer_offset_, u32 max_packet_count_, u32 buffer_size)
  68. : shared_memory(shared_memory_), info_offset(info_offset_), buffer_offset(buffer_offset_),
  69. max_packet_count(max_packet_count_),
  70. max_data_size(buffer_size - sizeof(PacketInfo) * max_packet_count_) {
  71. UpdateBufferInfo();
  72. }
  73. /**
  74. * Puts a packet to the head of the buffer.
  75. * @params packet The data of the packet to put.
  76. * @returns whether the operation is successful.
  77. */
  78. bool Put(const std::vector<u8>& packet) {
  79. if (info.packet_count == max_packet_count)
  80. return false;
  81. u32 write_offset;
  82. // finds free space offset in data buffer
  83. if (info.packet_count == 0) {
  84. write_offset = 0;
  85. if (packet.size() > max_data_size)
  86. return false;
  87. } else {
  88. const u32 last_index = (info.end_index + max_packet_count - 1) % max_packet_count;
  89. const PacketInfo first = GetPacketInfo(info.begin_index);
  90. const PacketInfo last = GetPacketInfo(last_index);
  91. write_offset = (last.offset + last.size) % max_data_size;
  92. const u32 free_space = (first.offset + max_data_size - write_offset) % max_data_size;
  93. if (packet.size() > free_space)
  94. return false;
  95. }
  96. // writes packet info
  97. PacketInfo packet_info{write_offset, static_cast<u32>(packet.size())};
  98. SetPacketInfo(info.end_index, packet_info);
  99. // writes packet data
  100. for (size_t i = 0; i < packet.size(); ++i) {
  101. *GetDataBufferPointer((write_offset + i) % max_data_size) = packet[i];
  102. }
  103. // updates buffer info
  104. info.end_index++;
  105. info.end_index %= max_packet_count;
  106. info.packet_count++;
  107. UpdateBufferInfo();
  108. return true;
  109. }
  110. /**
  111. * Release packets from the tail of the buffer
  112. * @params count Numbers of packets to release.
  113. * @returns whether the operation is successful.
  114. */
  115. bool Release(u32 count) {
  116. if (info.packet_count < count)
  117. return false;
  118. info.packet_count -= count;
  119. info.begin_index += count;
  120. info.begin_index %= max_packet_count;
  121. UpdateBufferInfo();
  122. return true;
  123. }
  124. private:
  125. struct BufferInfo {
  126. u32_le begin_index;
  127. u32_le end_index;
  128. u32_le packet_count;
  129. u32_le unknown;
  130. };
  131. static_assert(sizeof(BufferInfo) == 16, "BufferInfo has wrong size!");
  132. struct PacketInfo {
  133. u32_le offset;
  134. u32_le size;
  135. };
  136. static_assert(sizeof(PacketInfo) == 8, "PacketInfo has wrong size!");
  137. u8* GetPacketInfoPointer(u32 index) {
  138. return shared_memory->GetPointer(buffer_offset + sizeof(PacketInfo) * index);
  139. }
  140. void SetPacketInfo(u32 index, const PacketInfo& packet_info) {
  141. memcpy(GetPacketInfoPointer(index), &packet_info, sizeof(PacketInfo));
  142. }
  143. PacketInfo GetPacketInfo(u32 index) {
  144. PacketInfo packet_info;
  145. memcpy(&packet_info, GetPacketInfoPointer(index), sizeof(PacketInfo));
  146. return packet_info;
  147. }
  148. u8* GetDataBufferPointer(u32 offset) {
  149. return shared_memory->GetPointer(buffer_offset + sizeof(PacketInfo) * max_packet_count +
  150. offset);
  151. }
  152. void UpdateBufferInfo() {
  153. if (info_offset) {
  154. memcpy(shared_memory->GetPointer(info_offset), &info, sizeof(info));
  155. }
  156. }
  157. BufferInfo info{0, 0, 0, 0};
  158. Kernel::SharedPtr<Kernel::SharedMemory> shared_memory;
  159. u32 info_offset;
  160. u32 buffer_offset;
  161. u32 max_packet_count;
  162. u32 max_data_size;
  163. };
  164. static Kernel::SharedPtr<Kernel::Event> conn_status_event, send_event, receive_event;
  165. static Kernel::SharedPtr<Kernel::SharedMemory> shared_memory;
  166. static std::unique_ptr<ExtraHID> extra_hid;
  167. static IRDevice* connected_device;
  168. static boost::optional<BufferManager> receive_buffer;
  169. /// Wraps the payload into packet and puts it to the receive buffer
  170. static void PutToReceive(const std::vector<u8>& payload) {
  171. LOG_TRACE(Service_IR, "called, data=%s",
  172. Common::ArrayToString(payload.data(), payload.size()).c_str());
  173. size_t size = payload.size();
  174. std::vector<u8> packet;
  175. // Builds packet header. For the format info:
  176. // https://www.3dbrew.org/wiki/IRUSER_Shared_Memory#Packet_structure
  177. // fixed value
  178. packet.push_back(0xA5);
  179. // destination network ID
  180. u8 network_id = *(shared_memory->GetPointer(offsetof(SharedMemoryHeader, network_id)));
  181. packet.push_back(network_id);
  182. // puts the size info.
  183. // The highest bit of the first byte is unknown, which is set to zero here. The second highest
  184. // bit is a flag that determines whether the size info is in extended form. If the packet size
  185. // can be represent within 6 bits, the short form (1 byte) of size info is chosen, the size is
  186. // put to the lower bits of this byte, and the flag is clear. If the packet size cannot be
  187. // represent within 6 bits, the extended form (2 bytes) is chosen, the lower 8 bits of the size
  188. // is put to the second byte, the higher bits of the size is put to the lower bits of the first
  189. // byte, and the flag is set. Note that the packet size must be within 14 bits due to this
  190. // format restriction, or it will overlap with the flag bit.
  191. if (size < 0x40) {
  192. packet.push_back(static_cast<u8>(size));
  193. } else if (size < 0x4000) {
  194. packet.push_back(static_cast<u8>(size >> 8) | 0x40);
  195. packet.push_back(static_cast<u8>(size));
  196. } else {
  197. ASSERT(false);
  198. }
  199. // puts the payload
  200. packet.insert(packet.end(), payload.begin(), payload.end());
  201. // calculates CRC and puts to the end
  202. packet.push_back(boost::crc<8, 0x07, 0, 0, false, false>(packet.data(), packet.size()));
  203. if (receive_buffer->Put(packet)) {
  204. receive_event->Signal();
  205. } else {
  206. LOG_ERROR(Service_IR, "receive buffer is full!");
  207. }
  208. }
  209. /**
  210. * IR::InitializeIrNopShared service function
  211. * Initializes ir:USER service with a user provided shared memory. The shared memory is configured
  212. * to shared mode (with SharedMemoryHeader at the beginning of the shared memory).
  213. * Inputs:
  214. * 1 : Size of shared memory
  215. * 2 : Recv buffer size
  216. * 3 : Recv buffer packet count
  217. * 4 : Send buffer size
  218. * 5 : Send buffer packet count
  219. * 6 : BaudRate (u8)
  220. * 7 : 0 (Handle descriptor)
  221. * 8 : Handle of shared memory
  222. * Outputs:
  223. * 1 : Result of function, 0 on success, otherwise error code
  224. */
  225. static void InitializeIrNopShared(Interface* self) {
  226. IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x18, 6, 2);
  227. const u32 shared_buff_size = rp.Pop<u32>();
  228. const u32 recv_buff_size = rp.Pop<u32>();
  229. const u32 recv_buff_packet_count = rp.Pop<u32>();
  230. const u32 send_buff_size = rp.Pop<u32>();
  231. const u32 send_buff_packet_count = rp.Pop<u32>();
  232. const u8 baud_rate = rp.Pop<u8>();
  233. const Kernel::Handle handle = rp.PopHandle();
  234. IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
  235. shared_memory = Kernel::g_handle_table.Get<Kernel::SharedMemory>(handle);
  236. if (!shared_memory) {
  237. LOG_CRITICAL(Service_IR, "invalid shared memory handle 0x%08X", handle);
  238. rb.Push(IPC::ERR_INVALID_HANDLE);
  239. return;
  240. }
  241. shared_memory->name = "IR_USER: shared memory";
  242. receive_buffer =
  243. BufferManager(shared_memory, 0x10, 0x20, recv_buff_packet_count, recv_buff_size);
  244. SharedMemoryHeader shared_memory_init{};
  245. shared_memory_init.initialized = 1;
  246. std::memcpy(shared_memory->GetPointer(), &shared_memory_init, sizeof(SharedMemoryHeader));
  247. rb.Push(RESULT_SUCCESS);
  248. LOG_INFO(Service_IR, "called, shared_buff_size=%u, recv_buff_size=%u, "
  249. "recv_buff_packet_count=%u, send_buff_size=%u, "
  250. "send_buff_packet_count=%u, baud_rate=%u, handle=0x%08X",
  251. shared_buff_size, recv_buff_size, recv_buff_packet_count, send_buff_size,
  252. send_buff_packet_count, baud_rate, handle);
  253. }
  254. /**
  255. * IR::RequireConnection service function
  256. * Searches for an IR device and connects to it. After connecting to the device, applications can
  257. * use SendIrNop function, ReceiveIrNop function (or read from the buffer directly) to communicate
  258. * with the device.
  259. * Inputs:
  260. * 1 : device ID? always 1 for circle pad pro
  261. * Outputs:
  262. * 1 : Result of function, 0 on success, otherwise error code
  263. */
  264. static void RequireConnection(Interface* self) {
  265. IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x06, 1, 0);
  266. const u8 device_id = rp.Pop<u8>();
  267. u8* shared_memory_ptr = shared_memory->GetPointer();
  268. if (device_id == 1) {
  269. // These values are observed on a New 3DS. The meaning of them is unclear.
  270. // TODO (wwylele): should assign network_id a (random?) number
  271. shared_memory_ptr[offsetof(SharedMemoryHeader, connection_status)] = 2;
  272. shared_memory_ptr[offsetof(SharedMemoryHeader, connection_role)] = 2;
  273. shared_memory_ptr[offsetof(SharedMemoryHeader, connected)] = 1;
  274. connected_device = extra_hid.get();
  275. connected_device->OnConnect();
  276. conn_status_event->Signal();
  277. } else {
  278. LOG_WARNING(Service_IR, "unknown device id %u. Won't connect.", device_id);
  279. shared_memory_ptr[offsetof(SharedMemoryHeader, connection_status)] = 1;
  280. shared_memory_ptr[offsetof(SharedMemoryHeader, trying_to_connect_status)] = 2;
  281. }
  282. IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
  283. rb.Push(RESULT_SUCCESS);
  284. LOG_INFO(Service_IR, "called, device_id = %u", device_id);
  285. }
  286. /**
  287. * IR::GetReceiveEvent service function
  288. * Gets an event that is signaled when a packet is received from the IR device.
  289. * Outputs:
  290. * 1 : Result of function, 0 on success, otherwise error code
  291. * 2 : 0 (Handle descriptor)
  292. * 3 : Receive event handle
  293. */
  294. void GetReceiveEvent(Interface* self) {
  295. IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x0A, 1, 2);
  296. rb.Push(RESULT_SUCCESS);
  297. rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::receive_event).Unwrap());
  298. LOG_INFO(Service_IR, "called");
  299. }
  300. /**
  301. * IR::GetSendEvent service function
  302. * Gets an event that is signaled when the sending of a packet is complete
  303. * Outputs:
  304. * 1 : Result of function, 0 on success, otherwise error code
  305. * 2 : 0 (Handle descriptor)
  306. * 3 : Send event handle
  307. */
  308. void GetSendEvent(Interface* self) {
  309. IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x0B, 1, 2);
  310. rb.Push(RESULT_SUCCESS);
  311. rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::send_event).Unwrap());
  312. LOG_INFO(Service_IR, "called");
  313. }
  314. /**
  315. * IR::Disconnect service function
  316. * Disconnects from the current connected IR device.
  317. * Outputs:
  318. * 1 : Result of function, 0 on success, otherwise error code
  319. */
  320. static void Disconnect(Interface* self) {
  321. if (connected_device) {
  322. connected_device->OnDisconnect();
  323. connected_device = nullptr;
  324. conn_status_event->Signal();
  325. }
  326. u8* shared_memory_ptr = shared_memory->GetPointer();
  327. shared_memory_ptr[offsetof(SharedMemoryHeader, connection_status)] = 0;
  328. shared_memory_ptr[offsetof(SharedMemoryHeader, connected)] = 0;
  329. IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x09, 1, 0);
  330. rb.Push(RESULT_SUCCESS);
  331. LOG_INFO(Service_IR, "called");
  332. }
  333. /**
  334. * IR::GetConnectionStatusEvent service function
  335. * Gets an event that is signaled when the connection status is changed
  336. * Outputs:
  337. * 1 : Result of function, 0 on success, otherwise error code
  338. * 2 : 0 (Handle descriptor)
  339. * 3 : Connection Status Event handle
  340. */
  341. static void GetConnectionStatusEvent(Interface* self) {
  342. IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x0C, 1, 2);
  343. rb.Push(RESULT_SUCCESS);
  344. rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::conn_status_event).Unwrap());
  345. LOG_INFO(Service_IR, "called");
  346. }
  347. /**
  348. * IR::FinalizeIrNop service function
  349. * Finalize ir:USER service.
  350. * Outputs:
  351. * 1 : Result of function, 0 on success, otherwise error code
  352. */
  353. static void FinalizeIrNop(Interface* self) {
  354. if (connected_device) {
  355. connected_device->OnDisconnect();
  356. connected_device = nullptr;
  357. }
  358. shared_memory = nullptr;
  359. receive_buffer = boost::none;
  360. IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x02, 1, 0);
  361. rb.Push(RESULT_SUCCESS);
  362. LOG_INFO(Service_IR, "called");
  363. }
  364. /**
  365. * IR::SendIrNop service function
  366. * Sends a packet to the connected IR device
  367. * Inpus:
  368. * 1 : Size of data to send
  369. * 2 : 2 + (size << 14) (Static buffer descriptor)
  370. * 3 : Data buffer address
  371. * Outputs:
  372. * 1 : Result of function, 0 on success, otherwise error code
  373. */
  374. static void SendIrNop(Interface* self) {
  375. IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0D, 1, 2);
  376. const u32 size = rp.Pop<u32>();
  377. const VAddr address = rp.PopStaticBuffer();
  378. std::vector<u8> buffer(size);
  379. Memory::ReadBlock(address, buffer.data(), size);
  380. IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
  381. if (connected_device) {
  382. connected_device->OnReceive(buffer);
  383. send_event->Signal();
  384. rb.Push(RESULT_SUCCESS);
  385. } else {
  386. LOG_ERROR(Service_IR, "not connected");
  387. rb.Push(ResultCode(static_cast<ErrorDescription>(13), ErrorModule::IR,
  388. ErrorSummary::InvalidState, ErrorLevel::Status));
  389. }
  390. LOG_TRACE(Service_IR, "called, data=%s", Common::ArrayToString(buffer.data(), size).c_str());
  391. }
  392. /**
  393. * IR::ReleaseReceivedData function
  394. * Release a specified amount of packet from the receive buffer. This is called after the
  395. * application reads received packet from the buffer directly, to release the buffer space for
  396. * future packets.
  397. * Inpus:
  398. * 1 : Number of packets to release
  399. * Outputs:
  400. * 1 : Result of function, 0 on success, otherwise error code
  401. */
  402. static void ReleaseReceivedData(Interface* self) {
  403. IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x19, 1, 0);
  404. u32 count = rp.Pop<u32>();
  405. IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
  406. if (receive_buffer->Release(count)) {
  407. rb.Push(RESULT_SUCCESS);
  408. } else {
  409. LOG_ERROR(Service_IR, "failed to release %u packets", count);
  410. rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::IR, ErrorSummary::NotFound,
  411. ErrorLevel::Status));
  412. }
  413. LOG_TRACE(Service_IR, "called, count=%u", count);
  414. }
  415. const Interface::FunctionInfo FunctionTable[] = {
  416. {0x00010182, nullptr, "InitializeIrNop"},
  417. {0x00020000, FinalizeIrNop, "FinalizeIrNop"},
  418. {0x00030000, nullptr, "ClearReceiveBuffer"},
  419. {0x00040000, nullptr, "ClearSendBuffer"},
  420. {0x000500C0, nullptr, "WaitConnection"},
  421. {0x00060040, RequireConnection, "RequireConnection"},
  422. {0x000702C0, nullptr, "AutoConnection"},
  423. {0x00080000, nullptr, "AnyConnection"},
  424. {0x00090000, Disconnect, "Disconnect"},
  425. {0x000A0000, GetReceiveEvent, "GetReceiveEvent"},
  426. {0x000B0000, GetSendEvent, "GetSendEvent"},
  427. {0x000C0000, GetConnectionStatusEvent, "GetConnectionStatusEvent"},
  428. {0x000D0042, SendIrNop, "SendIrNop"},
  429. {0x000E0042, nullptr, "SendIrNopLarge"},
  430. {0x000F0040, nullptr, "ReceiveIrnop"},
  431. {0x00100042, nullptr, "ReceiveIrnopLarge"},
  432. {0x00110040, nullptr, "GetLatestReceiveErrorResult"},
  433. {0x00120040, nullptr, "GetLatestSendErrorResult"},
  434. {0x00130000, nullptr, "GetConnectionStatus"},
  435. {0x00140000, nullptr, "GetTryingToConnectStatus"},
  436. {0x00150000, nullptr, "GetReceiveSizeFreeAndUsed"},
  437. {0x00160000, nullptr, "GetSendSizeFreeAndUsed"},
  438. {0x00170000, nullptr, "GetConnectionRole"},
  439. {0x00180182, InitializeIrNopShared, "InitializeIrNopShared"},
  440. {0x00190040, ReleaseReceivedData, "ReleaseReceivedData"},
  441. {0x001A0040, nullptr, "SetOwnMachineId"},
  442. };
  443. IR_User_Interface::IR_User_Interface() {
  444. Register(FunctionTable);
  445. }
  446. void InitUser() {
  447. using namespace Kernel;
  448. shared_memory = nullptr;
  449. conn_status_event = Event::Create(ResetType::OneShot, "IR:ConnectionStatusEvent");
  450. send_event = Event::Create(ResetType::OneShot, "IR:SendEvent");
  451. receive_event = Event::Create(ResetType::OneShot, "IR:ReceiveEvent");
  452. receive_buffer = boost::none;
  453. extra_hid = std::make_unique<ExtraHID>(PutToReceive);
  454. connected_device = nullptr;
  455. }
  456. void ShutdownUser() {
  457. if (connected_device) {
  458. connected_device->OnDisconnect();
  459. connected_device = nullptr;
  460. }
  461. extra_hid = nullptr;
  462. receive_buffer = boost::none;
  463. shared_memory = nullptr;
  464. conn_status_event = nullptr;
  465. send_event = nullptr;
  466. receive_event = nullptr;
  467. }
  468. void ReloadInputDevicesUser() {
  469. if (extra_hid)
  470. extra_hid->RequestInputDevicesReload();
  471. }
  472. IRDevice::IRDevice(SendFunc send_func_) : send_func(send_func_) {}
  473. IRDevice::~IRDevice() = default;
  474. void IRDevice::Send(const std::vector<u8>& data) {
  475. send_func(data);
  476. }
  477. } // namespace IR
  478. } // namespace Service