2626from .. import Service
2727
2828try :
29- from typing import Dict , Optional
29+ from typing import Dict , Optional , Sequence
3030except ImportError :
3131 pass
3232
@@ -248,6 +248,69 @@ def report(self) -> Dict:
248248 return self ._characteristic .value
249249
250250
251+ class Device :
252+ """Container that groups multiple ReportIn and ReportOut objects for a given
253+ usage_page/usage.
254+
255+ Each device may have multiple report IDs. This class keeps mappings from
256+ report_id -> ReportIn/ReportOut and provides convenience methods that use
257+ the first-added report_id when none is specified.
258+ """
259+
260+ def __init__ (self , usage_page , usage ):
261+ self ._usage_page = usage_page
262+ self ._usage = usage
263+ # Maintain insertion order of report_ids
264+ self ._report_id_order = [] # list of report_id in insertion order
265+ self ._report_ins = {} # report_id -> ReportIn
266+ self ._report_outs = {} # report_id -> ReportOut
267+
268+ @property
269+ def usage_page (self ):
270+ return self ._usage_page
271+
272+ @property
273+ def usage (self ):
274+ return self ._usage
275+
276+ def add_report_in (self , report_id : int , report_in : ReportIn ) -> None :
277+ """Register a ReportIn instance for a report_id."""
278+ if report_id not in self ._report_id_order :
279+ self ._report_id_order .append (report_id )
280+ self ._report_ins [report_id ] = report_in
281+
282+ def add_report_out (self , report_id : int , report_out : ReportOut ) -> None :
283+ """Register a ReportOut instance for a report_id."""
284+ if report_id not in self ._report_id_order :
285+ self ._report_id_order .append (report_id )
286+ self ._report_outs [report_id ] = report_out
287+
288+ def _first_report_id (self ):
289+ return self ._report_id_order [0 ] if self ._report_id_order else None
290+
291+ def send_report (self , report : Dict , report_id : int | None = None ) -> None :
292+ """Send a report via the ReportIn class.
293+
294+ If report_id is None, uses the first-added report_id for this device.
295+ Raises RuntimeError if no matching ReportIn exists.
296+ """
297+ if report_id is None :
298+ report_id = self ._first_report_id ()
299+ if report_id is None or report_id not in self ._report_ins :
300+ raise RuntimeError (f"No input report available for report_id { report_id } " )
301+ self ._report_ins [report_id ].send_report (report )
302+
303+ def get_last_received_report (self , report_id : int | None = None ):
304+ """Return the last OUT report received.
305+ If report_id is None, uses the first-added report_id for this device.
306+ """
307+ if report_id is None :
308+ report_id = self ._first_report_id ()
309+ if report_id is None or report_id not in self ._report_outs :
310+ return None
311+ return self ._report_outs [report_id ].report
312+
313+
251314_ITEM_TYPE_MAIN = const (0 )
252315_ITEM_TYPE_GLOBAL = const (1 )
253316_ITEM_TYPE_LOCAL = const (2 )
@@ -268,9 +331,9 @@ class HIDService(Service):
268331
269332 Example::
270333
271- from adafruit_ble.hid_server import HIDServer
334+ from adafruit_ble.services.standard.hid import HIDService
272335
273- hid = HIDServer ()
336+ hid = HIDService ()
274337 """
275338
276339 uuid = StandardUUID (0x1812 )
@@ -429,25 +492,39 @@ def get_report_info(collection: Dict, reports: Dict) -> None:
429492 get_report_info (collection , reports )
430493 for report_id , report in reports .items ():
431494 output_size = report ["output_size" ]
495+ # Group ReportIn and ReportOut by usage_page and usage into a Devices
496+ input_size = report ["input_size" ]
497+
498+ # Find an existing device with same usage_page and usage
499+ device = None
500+ for d in self .devices :
501+ # Device instances expose usage_page and usage properties
502+ if d .usage_page == usage_page and d .usage == usage :
503+ device = d
504+ break
505+
506+ if device is None :
507+ device = Device (usage_page , usage )
508+ self .devices .append (device )
509+
432510 if output_size > 0 :
433- self .devices .append (
434- ReportOut (
435- self ,
436- report_id ,
437- usage_page ,
438- usage ,
439- max_length = output_size // 8 ,
440- )
511+ # Create ReportOut and attach to device
512+ report_out = ReportOut (
513+ self ,
514+ report_id ,
515+ usage_page ,
516+ usage ,
517+ max_length = output_size // 8 ,
441518 )
519+ device .add_report_out (report_id , report_out )
442520
443- input_size = report ["input_size" ]
444521 if input_size > 0 :
445- self .devices .append (
446- ReportIn (
447- self ,
448- report_id ,
449- usage_page ,
450- usage ,
451- max_length = input_size // 8 ,
452- )
522+ # Create ReportIn and attach to device
523+ report_in = ReportIn (
524+ self ,
525+ report_id ,
526+ usage_page ,
527+ usage ,
528+ max_length = input_size // 8 ,
453529 )
530+ device .add_report_in (report_id , report_in )
0 commit comments