├── hidparser ├── UsagePages │ ├── __init__.py │ ├── VirtualReality.py │ ├── GenericDeviceControls.py │ ├── Button.py │ ├── GenericDesktop.py │ ├── Digitizers.py │ ├── Power.py │ ├── Battery.py │ └── Sensor.py ├── __init__.py ├── helper.py ├── enums.py ├── ItemMain.py ├── ItemLocal.py ├── Item.py ├── UsagePage.py ├── ItemGlobal.py ├── Physical.py ├── DeviceBuilder.py └── Device.py ├── LICENSE ├── setup.py ├── .gitignore ├── examples ├── mouse.py └── dual-shock-3.py └── Readme.md /hidparser/UsagePages/__init__.py: -------------------------------------------------------------------------------- 1 | from hidparser.UsagePages.GenericDesktop import GenericDesktop 2 | from hidparser.UsagePages.VirtualReality import VirtualReality 3 | from hidparser.UsagePages.GenericDeviceControls import GenericDeviceControls 4 | from hidparser.UsagePages.Button import Button 5 | from hidparser.UsagePages.Digitizers import Digitizers 6 | 7 | from hidparser.UsagePages.Sensor import Sensor 8 | from hidparser.UsagePages.Power import Power 9 | from hidparser.UsagePages.Battery import Battery -------------------------------------------------------------------------------- /hidparser/UsagePages/VirtualReality.py: -------------------------------------------------------------------------------- 1 | from hidparser.UsagePage import UsagePage, Usage, UsageType 2 | 3 | 4 | class VirtualReality(UsagePage): 5 | 6 | @classmethod 7 | def _get_usage_page_index(cls): 8 | return 0x03 9 | 10 | BELT = Usage(0x01, UsageType.COLLECTION_APPLICATION) 11 | BODY_SUIT = Usage(0x02, UsageType.COLLECTION_APPLICATION) 12 | FLEXOR = Usage(0x03, UsageType.COLLECTION_PHYSICAL) 13 | GLOVE = Usage(0x04, UsageType.COLLECTION_APPLICATION) 14 | HEAD_TRACKER = Usage(0x05, UsageType.COLLECTION_PHYSICAL) 15 | HEAD_MOUNTED_DISPLAY = Usage(0x06, UsageType.COLLECTION_APPLICATION) 16 | HAND_TRACKER = Usage(0x07, UsageType.COLLECTION_APPLICATION) 17 | OCULOMETER = Usage(0x08, UsageType.COLLECTION_APPLICATION) 18 | VEST = Usage(0x09, UsageType.COLLECTION_APPLICATION) 19 | ANIMATRONIC_DEVICE = Usage(0x0A, UsageType.COLLECTION_APPLICATION) 20 | 21 | STEREO_ENABLE = Usage(0x20, UsageType.CONTROL_ON_OFF) 22 | DISPLAY_ENABLE = Usage(0x21, UsageType.CONTROL_ON_OFF) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Roman Vaughan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /hidparser/UsagePages/GenericDeviceControls.py: -------------------------------------------------------------------------------- 1 | from hidparser.UsagePage import Usage, UsagePage, UsageType 2 | 3 | 4 | class GenericDeviceControls(UsagePage): 5 | 6 | BATTERY_STRENGTH = Usage(0x20,UsageType.DATA_DYNAMIC_VALUE) 7 | WIRELESS_CHANNEL = Usage(0x21,UsageType.DATA_DYNAMIC_VALUE) 8 | WIRELESS_ID = Usage(0x21,UsageType.DATA_DYNAMIC_VALUE) 9 | DISCOVER_WIRELESS_CONTROL = Usage(0x23,UsageType.CONTROL_ONE_SHOT) 10 | SECURITY_CODE_CHARACTER_ENTERED = Usage(0x24,UsageType.CONTROL_ONE_SHOT) 11 | SECURITY_CODE_CHARACTER_ERASED = Usage(0x25,UsageType.CONTROL_ONE_SHOT) 12 | SECURITY_CODE_CLEARED = Usage(0x26,UsageType.CONTROL_ONE_SHOT) 13 | 14 | # HID Usage Table Request 61: Version Information Usages 15 | SOFTWARE_VERSION = Usage(0x2A,UsageType.COLLECTION_LOGICAL) 16 | PROTOCOL_VERSION = Usage(0x2B,UsageType.COLLECTION_LOGICAL) 17 | HARDWARE_VERSION = Usage(0x2C, UsageType.COLLECTION_LOGICAL) 18 | MAJOR = Usage(0x2D,UsageType.DATA_STATIC_VALUE) 19 | MINOR = Usage(0x2E,UsageType.DATA_STATIC_VALUE) 20 | REVISION = Usage(0x2F,UsageType.DATA_STATIC_VALUE) 21 | 22 | @classmethod 23 | def _get_usage_page_index(cls): 24 | return 0x06 25 | -------------------------------------------------------------------------------- /hidparser/UsagePages/Button.py: -------------------------------------------------------------------------------- 1 | from hidparser.UsagePage import UsagePage, UsageType, Usage 2 | 3 | 4 | class Button(UsagePage): 5 | 6 | @classmethod 7 | def get_usage(cls, value): 8 | try: 9 | return cls._value2member_map_[value] 10 | except KeyError: 11 | return Button(value) 12 | 13 | @classmethod 14 | def _get_usage_page_index(cls): 15 | return 0x09 16 | 17 | 18 | # Because Enum overrides the __new__ method in teh metaclass, so, it's getting overridden externally 19 | def _create_button_usage(cls, value): 20 | if isinstance(value, Usage): 21 | return super(Button, cls).__new__(cls, value) 22 | if (value & ~0xFFFF) > 0: 23 | raise ValueError() 24 | button = Usage(value, [ 25 | UsageType.DATA_SELECTOR, 26 | UsageType.CONTROL_ON_OFF, 27 | UsageType.CONTROL_MOMENTARY, 28 | UsageType.CONTROL_ONE_SHOT 29 | ]) 30 | button_enum = object.__new__(cls) 31 | button_enum._value_ = button 32 | button_enum._name_ = "BUTTON{}".format(value) 33 | button_enum.__objclass__ = cls 34 | button_enum.__init__(button) 35 | cls._member_names_.append(button_enum._name_) 36 | cls._member_map_[button_enum._name_] = button_enum 37 | cls._value2member_map_[value] = button_enum 38 | return button_enum 39 | 40 | setattr(Button, "__new__", _create_button_usage) -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from setuptools import setup, find_packages 4 | 5 | setup( 6 | name="hidparser", 7 | version="0.0.7", 8 | description="HID Descriptor Parser", 9 | 10 | license="MIT", 11 | 12 | author="Roman Vaughan", 13 | url="https://github.com/NZSmartie/PyHIDParser", 14 | 15 | classifiers=[ 16 | "Development Status :: 2 - Pre-Alpha", 17 | 18 | "Intended Audience :: Developers", 19 | 20 | "License :: OSI Approved :: MIT License", 21 | 22 | "Programming Language :: Python :: 3", 23 | "Programming Language :: Python :: 3.0", 24 | "Programming Language :: Python :: 3.1", 25 | "Programming Language :: Python :: 3.2", 26 | "Programming Language :: Python :: 3.3", 27 | "Programming Language :: Python :: 3.4", 28 | "Programming Language :: Python :: 3.5", 29 | "Programming Language :: Python :: 3 :: Only", 30 | 31 | "Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator", 32 | "Topic :: Scientific/Engineering :: Human Machine Interfaces", 33 | "Topic :: Software Development :: Embedded Systems", 34 | "Topic :: System :: Hardware", 35 | "Topic :: System :: Hardware :: Hardware Drivers", 36 | "Topic :: Utilities" 37 | ], 38 | 39 | keywords="hid device usb parse descriptor", 40 | 41 | packages=["hidparser"], 42 | 43 | install_requires=[ 44 | "bitstring>=3.1.4" 45 | ] 46 | ) -------------------------------------------------------------------------------- /hidparser/__init__.py: -------------------------------------------------------------------------------- 1 | from hidparser.Item import Item, ItemType 2 | from hidparser.ItemMain import * 3 | from hidparser.ItemGlobal import * 4 | from hidparser.ItemLocal import * 5 | 6 | from hidparser.DeviceBuilder import DeviceBuilder 7 | from hidparser.Device import Device, Collection, ReportGroup, Report 8 | 9 | from hidparser.UsagePage import UsagePage, Usage, UsageRange, UsageType 10 | import hidparser.UsagePages as UsagePages 11 | 12 | def parse(data): 13 | items = get_items(data) 14 | 15 | descriptor_builder = DeviceBuilder() 16 | for item in items: 17 | item.visit(descriptor_builder) 18 | 19 | return descriptor_builder.build() 20 | 21 | 22 | def get_items(data): 23 | import array 24 | 25 | if isinstance(data, bytes): 26 | data = array.array('B', data) 27 | 28 | # grab the next len bytes and return an array from the iterator 29 | get_bytes = lambda it, len: [next(it) for x in range(len)] 30 | 31 | byte_iter = iter(data) 32 | while True: 33 | try: 34 | item = next(byte_iter) 35 | # Check if the item is "Long" 36 | if item is 0xFE: 37 | size = next(byte_iter) 38 | tag = next(byte_iter) 39 | if tag not in range(0xF0, 0xFF): 40 | raise ValueError("Long Items are only supported by Vender defined tags as of Version 1.11") 41 | # Yield a long item, There are no tags defined in HID as of Version 1.11 42 | yield Item(tag=tag, data=get_bytes(byte_iter, size), long=True) 43 | 44 | # Short item's size is the first two bits (eitehr 0,1,2 or 4) 45 | size = item & 0x03 46 | if size is 3: 47 | size = 4 48 | 49 | # Get the item tag type from bits 3 - 2 50 | item_type = ItemType(item & 0x0C) 51 | if item_type is ItemType.RESERVED: 52 | raise ValueError("Invalid bType in short item") 53 | 54 | yield Item.create(tag=item & 0xFC, data=get_bytes(byte_iter, size)) 55 | 56 | except StopIteration: 57 | break 58 | pass 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/python,pycharm 3 | 4 | ### Python ### 5 | # Byte-compiled / optimized / DLL files 6 | __pycache__/ 7 | *.py[cod] 8 | *$py.class 9 | 10 | # C extensions 11 | *.so 12 | 13 | # Distribution / packaging 14 | .Python 15 | env/ 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | downloads/ 20 | eggs/ 21 | .eggs/ 22 | lib/ 23 | lib64/ 24 | parts/ 25 | sdist/ 26 | var/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *,cover 50 | .hypothesis/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | 60 | # Flask instance folder 61 | instance/ 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # IPython Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # dotenv 82 | .env 83 | 84 | # virtualenv 85 | venv/ 86 | ENV/ 87 | 88 | # Spyder project settings 89 | .spyderproject 90 | 91 | # Rope project settings 92 | .ropeproject 93 | 94 | 95 | ### PyCharm ### 96 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 97 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 98 | 99 | .idea 100 | 101 | ## File-based project format: 102 | *.iws 103 | 104 | ## Plugin-specific files: 105 | 106 | # IntelliJ 107 | /out/ 108 | 109 | # mpeltonen/sbt-idea plugin 110 | .idea_modules/ 111 | 112 | # JIRA plugin 113 | atlassian-ide-plugin.xml 114 | 115 | # Crashlytics plugin (for Android Studio and IntelliJ) 116 | com_crashlytics_export_strings.xml 117 | crashlytics.properties 118 | crashlytics-build.properties 119 | fabric.properties 120 | -------------------------------------------------------------------------------- /hidparser/helper.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class ValueRange: 5 | def __init__(self, minimum=None, maximum=None): 6 | self.minimum = minimum if minimum is not None else ~0x7FFFFFFF 7 | self.maximum = maximum if maximum is not None else 0x7FFFFFFF 8 | 9 | def __repr__(self): 10 | return "<{}: minimum: {}, maximum: {}>".format(self.__class__.__name__, self.minimum, self.maximum) 11 | 12 | def in_range(self, value): 13 | return self.minimum <= value <= self.maximum 14 | 15 | def __eq__(self, other): 16 | if not isinstance(other, self.__class__): 17 | raise ValueRange("other is not of type {}".format(self.__class__.__name__)) 18 | return self.minimum == other.minimum and self.maximum == other.maximum 19 | 20 | def scale_to(self, new_range, value): 21 | if not isinstance(new_range, ValueRange): 22 | raise ValueError("new_range is not ValueRange") 23 | if type(value) not in [int, float]: 24 | raise ValueError("{} is not a numeric value".format(type(value))) 25 | if not self.in_range(value): 26 | raise ArithmeticError("value is outside of accepted range") 27 | value = (value - self.minimum) / (self.maximum - self.minimum) 28 | return new_range.minimum + value * (new_range.maximum - new_range.minimum) 29 | 30 | 31 | class EnumMask(object): 32 | def __init__(self, enum, value): 33 | self._enum=enum 34 | self._value=value 35 | 36 | def __and__(self, other): 37 | assert isinstance(other,self._enum) 38 | return self._value&other.value 39 | 40 | def __or__(self, other): 41 | assert isinstance(other,self._enum) 42 | return EnumMask(self._enum, self._value|other.value) 43 | 44 | def __repr__(self): 45 | return "<{} for {}: {}>".format( 46 | self.__class__.__name__, 47 | self._enum, 48 | self._enum.__repr_members__(self._value) 49 | ) 50 | 51 | 52 | class FlagEnum(Enum): 53 | def __or__(self, other): 54 | return EnumMask(self.__class__, self.value|other.value) 55 | 56 | def __and__(self, other): 57 | if isinstance(other, self.__class__): 58 | return self.value&other.value 59 | elif isinstance(other, EnumMask): 60 | return other&self 61 | else: 62 | raise ValueError("Not a valid {0}".format(self.__class__.__name__)) 63 | 64 | @classmethod 65 | def __repr_members__(cls, value): 66 | return [member for member in cls.__members__ if cls.__members__[member].value & value > 0] 67 | -------------------------------------------------------------------------------- /hidparser/enums.py: -------------------------------------------------------------------------------- 1 | from array import array as _array 2 | from hidparser.helper import FlagEnum, EnumMask, Enum 3 | 4 | 5 | class ReportType(Enum): 6 | INPUT = 1 7 | OUTPUT = 2 8 | FEATURE = 3 9 | 10 | 11 | class ReportFlags(FlagEnum): 12 | CONSTANT = 0x01 13 | VARIABLE = 0x02 14 | RELATIVE = 0x04 15 | WRAP = 0x08 16 | NON_LINEAR = 0x10 17 | NO_PREFERRED = 0x20 18 | NULL_STATE = 0x40 19 | # reserved = 0x80 20 | BUFFERED_BYTES = 0x100 21 | 22 | @classmethod 23 | def from_bytes(cls, data: _array): 24 | result = 0 25 | if data is None: 26 | return EnumMask(cls,result) 27 | if len(data)>0: 28 | result |= data[0] 29 | if len(data)>1: 30 | result |= data[1] << 8 31 | 32 | return EnumMask(cls, result) 33 | 34 | @classmethod 35 | def __repr_members__(cls, value): 36 | return [ 37 | "Data" if not value & ReportFlags.CONSTANT.value else "Constant", 38 | "Array" if not value & ReportFlags.VARIABLE.value else "Variable", 39 | "Absolute" if not value & ReportFlags.RELATIVE.value else "Relative", 40 | "No Wrap" if not value & ReportFlags.WRAP.value else "Wrap", 41 | "Linear" if not value & ReportFlags.NON_LINEAR.value else "Non Linear", 42 | "Prefered State" if not value & ReportFlags.NO_PREFERRED.value else "No Prefered", 43 | "No Null position" if not value & ReportFlags.NULL_STATE.value else "Null state", 44 | "Bit Field" if not value & ReportFlags.BUFFERED_BYTES.value else "Buffered Bytes", 45 | ] 46 | 47 | 48 | # TODO Support vendor defined functions 49 | class CollectionType(Enum): 50 | PHYSICAL = 0 51 | APPLICATION = 1 52 | LOGICAL = 2 53 | REPORT = 3 54 | NAMED_ARRAY = 4 55 | USAGE_SWITCH = 5 56 | USAGE_MODIFIER = 6 57 | 58 | 59 | class UnitSystem(Enum): 60 | NONE = 0x0 61 | SI_LINEAR = 0x01 62 | SI_ROTATION = 0x02 63 | ENGLISH_LINEAR = 0x03 64 | ENGLISH_ROTATION = 0x04 65 | VENDOR_DEFINED = 0x0F 66 | 67 | def __repr__(self): 68 | return "<{}: {}>".format(self.__class__.name, self._name_.replace("_", " ").title()) 69 | 70 | 71 | # TODO Support printing out SI units ("cm", "mm", "km" etc...) 72 | # TODO Support SI Linear, SI Rotation, English Linear and English Rotation 73 | class UnitLength(Enum): 74 | NONE = 0 75 | CENTIMETERS = 1 76 | METERS = 3 77 | KILOMETERS = 6 78 | MILLIMETERS = -1 79 | MICROMETERS = -4 80 | NANOMETERS = -7 81 | 82 | 83 | class UnitMass(Enum): 84 | NONE = 0 85 | GRAMS = 1 86 | KILOGRAMS = 4 87 | TON = 7 88 | MILLIGRAMS = -2 89 | MICROGRAMS = -5 90 | NANOGRAMS = -8 91 | -------------------------------------------------------------------------------- /hidparser/ItemMain.py: -------------------------------------------------------------------------------- 1 | from hidparser.Item import ItemType, Item 2 | from hidparser.enums import CollectionType, ReportFlags, ReportType 3 | from hidparser.DeviceBuilder import DeviceBuilder 4 | 5 | 6 | class InputItem(Item): 7 | flags = None # type: ReportFlags 8 | 9 | def visit(self, descriptor: DeviceBuilder): 10 | descriptor.add_report(ReportType.INPUT, self.flags) 11 | 12 | @classmethod 13 | def _get_tag(cls): 14 | return 0x80 15 | 16 | @classmethod 17 | def _get_type(cls): 18 | return ItemType.MAIN 19 | 20 | def __init__(self, **kwargs): 21 | super(InputItem, self).__init__(**kwargs) 22 | 23 | self.flags = ReportFlags.from_bytes(self.data) 24 | 25 | def __repr__(self): 26 | return "<{0}: {1}>".format(self.__class__.__name__, self.flags) 27 | 28 | 29 | class OutputItem(Item): 30 | flags = None 31 | 32 | def visit(self, descriptor: DeviceBuilder): 33 | descriptor.add_report(ReportType.OUTPUT, self.flags) 34 | 35 | @classmethod 36 | def _get_tag(cls): 37 | return 0x90 38 | 39 | @classmethod 40 | def _get_type(cls): 41 | return ItemType.MAIN 42 | 43 | def __init__(self, **kwargs): 44 | super(OutputItem, self).__init__(**kwargs) 45 | 46 | self.flags = ReportFlags.from_bytes(self.data) 47 | 48 | def __repr__(self): 49 | return "<{0}: {1}>".format(self.__class__.__name__, self.flags) 50 | 51 | 52 | class FeatureItem(Item): 53 | flags = None 54 | 55 | def visit(self, descriptor: DeviceBuilder): 56 | descriptor.add_report(ReportType.FEATURE, self.flags) 57 | 58 | @classmethod 59 | def _get_tag(cls): 60 | return 0xB0 61 | 62 | @classmethod 63 | def _get_type(cls): 64 | return ItemType.MAIN 65 | 66 | def __init__(self, **kwargs): 67 | super(FeatureItem, self).__init__(**kwargs) 68 | 69 | self.flags = ReportFlags.from_bytes(self.data) 70 | 71 | def __repr__(self): 72 | return "<{0}: {1}>".format(self.__class__.__name__, self.flags) 73 | 74 | 75 | class CollectionItem(Item): 76 | collection = None 77 | 78 | @classmethod 79 | def _get_tag(cls): 80 | return 0xA0 81 | 82 | @classmethod 83 | def _get_type(cls): 84 | return ItemType.MAIN 85 | 86 | def visit(self, descriptor: DeviceBuilder): 87 | if not isinstance(self.collection, CollectionType): 88 | raise ValueError("CollectionItem does not have a valid collection set") 89 | descriptor.push_collection(self.collection) 90 | 91 | def __init__(self, **kwargs): 92 | super(CollectionItem, self).__init__(**kwargs) 93 | 94 | if self.data is None or len(self.data) is not 1: 95 | raise ValueError("Collection must contain one byte of data") 96 | self.collection = CollectionType(self.data[0]) 97 | 98 | def __repr__(self): 99 | return "<{}: {}>".format(self.__class__.__name__, self.collection) 100 | 101 | 102 | class EndCollectionItem(Item): 103 | def visit(self, descriptor: DeviceBuilder): 104 | descriptor.pop_collection() 105 | 106 | @classmethod 107 | def _get_tag(cls): 108 | return 0xC0 109 | 110 | @classmethod 111 | def _get_type(cls): 112 | return ItemType.MAIN 113 | -------------------------------------------------------------------------------- /hidparser/ItemLocal.py: -------------------------------------------------------------------------------- 1 | from hidparser.DeviceBuilder import DeviceBuilder 2 | from hidparser.Item import Item, ItemType, ValueItem 3 | 4 | 5 | class UsageItem(ValueItem): 6 | def __init__(self, *args, **kwargs): 7 | kwargs["signed"] = False 8 | super(UsageItem, self).__init__(*args, **kwargs) 9 | 10 | def visit(self, descriptor: DeviceBuilder): 11 | descriptor.add_usage(self.value) 12 | 13 | @classmethod 14 | def _get_tag(cls): 15 | return 0x08 16 | 17 | @classmethod 18 | def _get_type(cls): 19 | return ItemType.LOCAL 20 | 21 | 22 | class UsageMinimumItem(ValueItem): 23 | def visit(self, descriptor: DeviceBuilder): 24 | descriptor.set_usage_range(minimum=self.value) 25 | 26 | @classmethod 27 | def _get_tag(cls): 28 | return 0x18 29 | 30 | @classmethod 31 | def _get_type(cls): 32 | return ItemType.LOCAL 33 | 34 | 35 | class UsageMaximumItem(ValueItem): 36 | def visit(self, descriptor: DeviceBuilder): 37 | descriptor.set_usage_range(maximum=self.value) 38 | 39 | @classmethod 40 | def _get_tag(cls): 41 | return 0x28 42 | 43 | @classmethod 44 | def _get_type(cls): 45 | return ItemType.LOCAL 46 | 47 | 48 | class DesignatorIndexItem(ValueItem): 49 | def visit(self, descriptor: DeviceBuilder): 50 | descriptor.set_designator_range(minimum=self.value, maximum=self.value) 51 | 52 | @classmethod 53 | def _get_tag(cls): 54 | return 0x38 55 | 56 | @classmethod 57 | def _get_type(cls): 58 | return ItemType.LOCAL 59 | 60 | 61 | class DesignatorMaximumItem(ValueItem): 62 | def visit(self, descriptor: DeviceBuilder): 63 | descriptor.set_designator_range(maximum=self.value) 64 | 65 | @classmethod 66 | def _get_tag(cls): 67 | return 0x48 68 | 69 | @classmethod 70 | def _get_type(cls): 71 | return ItemType.LOCAL 72 | 73 | 74 | class DesignatorMinimumItem(ValueItem): 75 | def visit(self, descriptor: DeviceBuilder): 76 | descriptor.set_designator_range(minimum=self.value) 77 | 78 | @classmethod 79 | def _get_tag(cls): 80 | return 0x58 81 | 82 | @classmethod 83 | def _get_type(cls): 84 | return ItemType.LOCAL 85 | 86 | 87 | class StringIndexItem(ValueItem): 88 | def visit(self, descriptor: DeviceBuilder): 89 | descriptor.set_string_range(minimum=self.value, maximum=self.value) 90 | 91 | @classmethod 92 | def _get_tag(cls): 93 | return 0x78 94 | 95 | @classmethod 96 | def _get_type(cls): 97 | return ItemType.LOCAL 98 | 99 | 100 | class StringMinimumItem(ValueItem): 101 | def visit(self, descriptor: DeviceBuilder): 102 | descriptor.set_string_range(minimum=self.value) 103 | 104 | @classmethod 105 | def _get_tag(cls): 106 | return 0x88 107 | 108 | @classmethod 109 | def _get_type(cls): 110 | return ItemType.LOCAL 111 | 112 | 113 | class StringMaximumItem(ValueItem): 114 | def visit(self, descriptor: DeviceBuilder): 115 | descriptor.set_string_range(maximum=self.value) 116 | 117 | @classmethod 118 | def _get_tag(cls): 119 | return 0x98 120 | 121 | @classmethod 122 | def _get_type(cls): 123 | return ItemType.LOCAL 124 | 125 | 126 | class DelimiterItem(Item): 127 | @classmethod 128 | def _get_tag(cls): 129 | return 0xA8 130 | 131 | @classmethod 132 | def _get_type(cls): 133 | return ItemType.LOCAL 134 | 135 | -------------------------------------------------------------------------------- /hidparser/Item.py: -------------------------------------------------------------------------------- 1 | from enum import Enum, IntEnum 2 | from hidparser.DeviceBuilder import DeviceBuilder 3 | 4 | # Toddo create a Enum like class that happily converts subclasses into byte arrays 5 | 6 | from abc import ABCMeta as _ABCMeta 7 | import struct as _struct 8 | import copy as _copy 9 | from array import array as _array 10 | 11 | 12 | class ItemType(IntEnum): 13 | MAIN = 0x00 14 | GLOBAL = 0x04 15 | LOCAL = 0x08 16 | RESERVED = 0x0C 17 | 18 | 19 | class Item(metaclass=_ABCMeta): 20 | from abc import abstractmethod 21 | _item_map = None 22 | 23 | data = None # type: _array 24 | 25 | # Abstract methods 26 | 27 | # @abstractmethod 28 | def visit(self, descriptor: DeviceBuilder): 29 | pass 30 | 31 | @classmethod 32 | @abstractmethod 33 | def _get_tag(cls): 34 | pass 35 | 36 | @classmethod 37 | @abstractmethod 38 | def _get_type(cls): 39 | pass 40 | 41 | # Concrete methods 42 | 43 | def __init__(self, data: _array = None, long: bool = False, *args, **kwargs): 44 | self.data = data 45 | 46 | def __repr__(self): 47 | return "<{}: {}>".format(self.__class__.__name__, repr(self.data)) 48 | 49 | @property 50 | def tag(self) -> int: 51 | """ 52 | Gets the item tag number including the item type (bTag | bType) 53 | :return int: 54 | """ 55 | return self._get_tag() 56 | 57 | @property 58 | def type(self) -> ItemType: 59 | """ 60 | Gets the tag type of the item 61 | :return TagType: 62 | """ 63 | return self._get_type() 64 | 65 | @classmethod 66 | def _map_subclasses(cls, parent_class): 67 | for c in parent_class.__subclasses__(): 68 | if not issubclass(c, cls): 69 | continue 70 | try: 71 | key = c._get_tag() 72 | cls._item_map[key] = c 73 | except NotImplementedError: 74 | cls._map_subclasses(c) 75 | 76 | @classmethod 77 | def create(cls, tag: int, item_type: ItemType = None, data: _array = [], long: bool = False): 78 | if cls._item_map is None: 79 | cls._item_map = {} 80 | cls._map_subclasses(cls) 81 | if long: 82 | raise NotImplementedError("Log items are not supported by this parser yet") 83 | 84 | if item_type is not None: 85 | if (tag & ~0x0F) > 0: 86 | raise ValueError("tag is not valid") 87 | tag = (tag << 4) | item_type.value 88 | 89 | if tag not in cls._item_map: 90 | raise ValueError("Unknown tag {0} ({1})".format(tag, hex(tag))) 91 | 92 | return cls._item_map[tag](data=data, long=long) 93 | 94 | 95 | class ValueItem(Item): 96 | value = None 97 | 98 | def __init__(self, **kwargs): 99 | super(ValueItem, self).__init__(**kwargs) 100 | signed = kwargs["signed"] if "signed" in kwargs.keys() else True 101 | if len(self.data) == 1: 102 | self.value = _struct.unpack("".format(self.__class__.__name__, repr(self.value)) 118 | -------------------------------------------------------------------------------- /examples/mouse.py: -------------------------------------------------------------------------------- 1 | import hidparser 2 | from hidparser.UsagePages import GenericDesktop, Button 3 | 4 | 5 | if __name__ == '__main__': 6 | mouse = bytes([ 7 | 0x05, 0x01, # USAGE_PAGE (Generic Desktop) 8 | 0x09, 0x02, # USAGE (Mouse) 9 | 0xa1, 0x01, # COLLECTION (Application) 10 | 0x09, 0x01, # USAGE (Pointer) 11 | 0xa1, 0x00, # COLLECTION (Physical) 12 | 0x05, 0x09, # USAGE_PAGE (Button) 13 | 0x19, 0x01, # USAGE_MINIMUM (Button 1) 14 | 0x29, 0x03, # USAGE_MAXIMUM (Button 3) 15 | 0x15, 0x00, # LOGICAL_MINIMUM (0) 16 | 0x25, 0x01, # LOGICAL_MAXIMUM (1) 17 | 0x95, 0x03, # REPORT_COUNT (3) 18 | 0x75, 0x01, # REPORT_SIZE (1) 19 | 0x81, 0x02, # INPUT (Data,Var,Abs) 20 | 0x95, 0x01, # REPORT_COUNT (1) 21 | 0x75, 0x05, # REPORT_SIZE (5) 22 | 0x81, 0x03, # INPUT (Cnst,Var,Abs) 23 | 0x05, 0x01, # USAGE_PAGE (Generic Desktop) 24 | 0x09, 0x30, # USAGE (X) 25 | 0x09, 0x31, # USAGE (Y) 26 | 0x15, 0x81, # LOGICAL_MINIMUM (-127) 27 | 0x25, 0x7f, # LOGICAL_MAXIMUM (127) 28 | 0x75, 0x08, # REPORT_SIZE (8) 29 | 0x95, 0x02, # REPORT_COUNT (2) 30 | 0x81, 0x06, # INPUT (Data,Var,Rel) 31 | 0xc0, # END_COLLECTION 32 | 0xc0 # END_COLLECTION 33 | ]) 34 | 35 | # Todo return a more useful Device object instead of a "builder" 36 | # This returns a Device 37 | mouse_from_desc = hidparser.parse(mouse) 38 | 39 | # Alternatively, create a mouse device through API instead of parsing bytes 40 | mouse_from_api = hidparser.Device() 41 | 42 | # TODO accessing the collections by Usage could be cleaned up some how 43 | # Index 0 is used as a fallback when no ReportID Items are used 44 | # otherwise, Report ID must start at 1 45 | 46 | mouse_from_api = hidparser.Device( 47 | hidparser.Collection( 48 | usage=GenericDesktop.MOUSE, 49 | items=hidparser.Collection( 50 | usage=GenericDesktop.POINTER, 51 | items=[ 52 | hidparser.Report( 53 | report_type=hidparser.ReportType.INPUT, 54 | usages=hidparser.UsageRange( 55 | minimum=Button(1), 56 | maximum=Button(3) 57 | ).get_range(), 58 | size=1, 59 | count=3, 60 | logical_range=(0, 1), 61 | flags=hidparser.ReportFlags.VARIABLE 62 | ), 63 | hidparser.Report( 64 | report_type=hidparser.ReportType.INPUT, 65 | usages=[], 66 | size=5, 67 | count=1, 68 | flags=hidparser.ReportFlags.CONSTANT | hidparser.ReportFlags.VARIABLE 69 | ), 70 | hidparser.Report( 71 | report_type=hidparser.ReportType.INPUT, 72 | usages=[ 73 | GenericDesktop.X, 74 | GenericDesktop.Y 75 | ], 76 | size=8, 77 | count=2, 78 | logical_range=(-127, 127), 79 | flags=hidparser.ReportFlags.VARIABLE | hidparser.ReportFlags.RELATIVE 80 | ) 81 | ] 82 | ) 83 | ) 84 | ) 85 | 86 | # Read from the physical device 87 | data = bytes([0x00, 0x12, 0x34]) 88 | # Deserialize the data and populate the object members 89 | mouse_from_api.deserialize(data) 90 | 91 | # Read x,y from mouse 92 | pointer = mouse_from_api.reports[0].inputs.mouse.pointer 93 | print("pointer: {}, {}".format(pointer.x, pointer.y)) 94 | # pointer 18.0, 52.0 95 | 96 | # The pass is for me to set a break point, so i can inspect the ds3 object with my debugger 97 | pass 98 | -------------------------------------------------------------------------------- /examples/dual-shock-3.py: -------------------------------------------------------------------------------- 1 | import usb 2 | import hidparser 3 | 4 | 5 | """ 6 | This example will find a Sony Dual Shock 3 controller continuously print out the button states and thumb stick values 7 | """ 8 | 9 | if __name__ is not "__main__": 10 | exit() 11 | 12 | 13 | class MyDevice: 14 | def __init__(self, interface: usb.core.Interface): 15 | # The usb device 16 | self.device = interface.device # type: usb.core.Device 17 | # The HID interface on the usb device 18 | self.interface = interface 19 | 20 | # locate the IN and OUT endpoints for the interface (typically interrupt, none may exist!) 21 | self.ep_in = None # type: usb.core.Endpoint 22 | self.ep_out = None # type: usb.core.Endpoint 23 | 24 | for ep in interface.endpoints(): #type: usb.core.Endpoint 25 | if ep.bEndpointAddress & 0x80: 26 | self.ep_in = ep 27 | else: 28 | self.ep_out = ep 29 | 30 | # Detatch the kernal driver, as it prevents our script from reading and writing 31 | if self.device.is_kernel_driver_active(interface.bInterfaceNumber): 32 | self.device.detach_kernel_driver(interface.bInterfaceNumber) 33 | 34 | # Request the HID Report Descriptor 35 | bmRequest = usb.util.build_request_type( 36 | direction=usb.util.CTRL_IN, 37 | type = usb.util.CTRL_TYPE_STANDARD, 38 | recipient=usb.util.CTRL_RECIPIENT_INTERFACE 39 | ) 40 | 41 | data = self.device.ctrl_transfer( 42 | bmRequestType=bmRequest, 43 | bRequest=0x06, # GET_DESCRIPTOR 44 | wValue=0x2200, # Input Report Type 45 | wIndex=interface.bInterfaceNumber, 46 | data_or_wLength=1000 # Assume it's huge. TODO get descriptor size from device first 47 | ) 48 | 49 | # Create a descriptor object from the device's HID Report Descriptor 50 | self.desc = hidparser.parse(data) 51 | 52 | # Find the largest report size for the input, output and feature reports 53 | self.max_input_size = max( 54 | [report_group.input_size for report_group in self.desc.reports.values()], 55 | default=0 56 | ) 57 | self.max_output_size = max( 58 | [report_group.output_size for report_group in self.desc.reports.values()], 59 | default=0 60 | ) 61 | self.max_feature_size = max( 62 | [report_group.feature_size for report_group in self.desc.reports.values()], 63 | default=0 64 | ) 65 | 66 | def read_interrupt(self): 67 | try: 68 | # add 1 to size for Report ID byte 69 | data = bytes(self.ep_in.read(self.max_input_size + 1)) 70 | self.desc.deserialize(data) 71 | except usb.core.USBError: 72 | pass 73 | 74 | # Find all Sony devices 75 | usb_devices = usb.core.find( 76 | find_all=True, 77 | custom_match=lambda dev: dev.idVendor == 0x054C and dev.idProduct == 0x0268 78 | ) 79 | 80 | # Find all HID interfaces in usb_devices and create a MyDevice object 81 | my_devices = [MyDevice(interface) for device in usb_devices 82 | for config in device.configurations() 83 | for interface in config 84 | if interface.bInterfaceClass == 3] 85 | 86 | 87 | running = True 88 | 89 | while running: 90 | # Loop through all our found devices 91 | for my_device in my_devices: # type: MyDevice 92 | 93 | my_device.read_interrupt() 94 | 95 | print("---") 96 | 97 | # Path to the buttons report 98 | buttons = my_device.desc.reports[1].inputs.joystick[0][1] 99 | thumb_sticks = my_device.desc.reports[1].inputs.joystick[0].pointer[0] 100 | 101 | 102 | print("Buttons: " 103 | + "".join(map( 104 | lambda value: "-" if value is 0 else "X", # Maps 0,1 to "-" or "X" repectively 105 | [int(value) for value in buttons.value])) 106 | ) 107 | 108 | print("Thumb sticks: {: <4}, {: <4}, {: <4}, {: <4}".format( 109 | thumb_sticks.x, 110 | thumb_sticks.y, 111 | thumb_sticks.z, 112 | thumb_sticks.r_z 113 | )) 114 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # PyHIDParser 2 | #### V0.0.7 3 | 4 | A python library for interpreting a HID descriptor to provide 5 | an application with byte structures for reading and writing to without the manual labour. 6 | 7 | #### Pre-Alpha 8 | 9 | At this stage, this library is still in early development and adoption is not recommended. 10 | 11 | #### Progress 12 | 13 | - [x] Parse HID Report descriptor from byte array 14 | - [ ] Support for HID spec 1.11 items *(See Issue [#1](https://github.com/NZSmartie/PyHIDParser/issues/1))* 15 | - [x] Main items (Collections, Inputs, Outputs and Features) 16 | - [x] Global items 17 | - [ ] Local items *(missing `delimiter`)* 18 | - [ ] ~~Support vender defined long items~~ *(not going to happen any time soon)* 19 | - [x] Parse HID Physical Descriptor 20 | - [x] Create an application API for handing HID items - *Don't want the application developer to deal with states, nesting or closing collections, etc* 21 | - [x] Access reports based on usages 22 | - [x] Serialize/Deserialize reports to/from the HID device 23 | - [x] Allow creating a HID descriptor from the API for configuring devices with 24 | 25 | ## Goals 26 | 27 | - Allow creating HID descriptors from byte arrays 28 | - Allow creating byte arrays from a HID descriptor 29 | - For those wanting an API approach to creating a descriptor 30 | - Or for anyone willing to create a new GUI tool 31 | - Provide a (de)serializer for reading and writing to devices 32 | - Support adding vendor defined usage pages through API 33 | - Provide meta data about reports, such as physical descriptor index, usage switches/modifiers 34 | 35 | ## Examples 36 | 37 | More examples int the [examples/](examples/) folder: 38 | - [mouse.py](examples/mouse.py) - Creating the example Mouse descriptor from the HID 1.11 spec 39 | - [dual-shock-3.py](examples/dual-shock-3.py) - Parse Sony's DualShock3 HID descriptor 40 | 41 | *Note: This is a ***working*** example. But it is subject to change* 42 | ```python 43 | import hidparser 44 | from hidparser.UsagePages import GenericDesktop, Button 45 | 46 | # ... 47 | 48 | mouse_desc = array('B', [ 49 | 0x05, 0x01, # USAGE_PAGE (Generic Desktop) 50 | 0x09, 0x02, # USAGE (Mouse) 51 | 0xa1, 0x01, # COLLECTION (Application) 52 | # ... 53 | 0xc0 # END_COLLECTION 54 | ]) 55 | 56 | # This returns a Device object from a descriptor 57 | mouse_from_desc = hidparser.parse(mouse) 58 | 59 | # Alternatively, create a mouse device through API instead of parsing bytes 60 | mouse_from_api = hidparser.Device( 61 | hidparser.Collection( 62 | usage=GenericDesktop.MOUSE, 63 | items=hidparser.Collection( 64 | usage=GenericDesktop.POINTER, 65 | items=[ 66 | hidparser.Report( 67 | report_type=hidparser.ReportType.INPUT, 68 | usages=hidparser.UsageRange( 69 | minimum=Button(1), 70 | maximum=Button(3) 71 | ).get_range(), 72 | size=1, 73 | count=3, 74 | logical_range=(0, 1), 75 | flags=hidparser.ReportFlags.VARIABLE 76 | ), 77 | hidparser.Report( 78 | report_type=hidparser.ReportType.INPUT, 79 | usages=[], 80 | size=5, 81 | count=1, 82 | flags=hidparser.ReportFlags.CONSTANT | hidparser.ReportFlags.VARIABLE 83 | ), 84 | hidparser.Report( 85 | report_type=hidparser.ReportType.INPUT, 86 | usages=[ 87 | GenericDesktop.X, 88 | GenericDesktop.Y 89 | ], 90 | size=8, 91 | count=2, 92 | logical_range=(-127, 127), 93 | flags=hidparser.ReportFlags.VARIABLE | hidparser.ReportFlags.RELATIVE 94 | ) 95 | ] 96 | ) 97 | ) 98 | ) 99 | 100 | # Read from the physical device 101 | data = bytes([0x00, 0x12, 0x34]) 102 | # Deserialize the data and populate the object members 103 | mouse_from_api.deserialize(data) 104 | 105 | # Read the x,y members from mouse after deserializing 106 | pointer = mouse_from_api.reports[0].inputs.mouse.pointer 107 | print("pointer: {}, {}".format(pointer.x, pointer.y)) 108 | # Example Output: 109 | # pointer: 18, 52 110 | 111 | ``` 112 | -------------------------------------------------------------------------------- /hidparser/UsagePages/GenericDesktop.py: -------------------------------------------------------------------------------- 1 | from hidparser.UsagePage import UsagePage, UsageType, Usage 2 | 3 | 4 | class GenericDesktop(UsagePage): 5 | POINTER = Usage(0x01, UsageType.COLLECTION_PHYSICAL) 6 | MOUSE = Usage(0x02, UsageType.COLLECTION_APPLICATION) 7 | JOYSTICK = Usage(0x04, UsageType.COLLECTION_APPLICATION) 8 | GAME_PAD = Usage(0x05, UsageType.COLLECTION_APPLICATION) 9 | KEYBOARD = Usage(0x06, UsageType.COLLECTION_APPLICATION) 10 | MULTI_AXIS_CONTROLLER = Usage(0x08, UsageType.COLLECTION_APPLICATION) 11 | TABLET_PC_SYSTEM_CONTROLS = Usage(0x09, UsageType.COLLECTION_APPLICATION) 12 | X = Usage(0x30, UsageType.DATA_DYNAMIC_VALUE) 13 | Y = Usage(0x31, UsageType.DATA_DYNAMIC_VALUE) 14 | Z = Usage(0x32, UsageType.DATA_DYNAMIC_VALUE) 15 | R_X = Usage(0x33, UsageType.DATA_DYNAMIC_VALUE) 16 | R_Y = Usage(0x34, UsageType.DATA_DYNAMIC_VALUE) 17 | R_Z = Usage(0x35, UsageType.DATA_DYNAMIC_VALUE) 18 | SLIDER = Usage(0x36, UsageType.DATA_DYNAMIC_VALUE) 19 | DIAL = Usage(0x37, UsageType.DATA_DYNAMIC_VALUE) 20 | WHEEL = Usage(0x38, UsageType.DATA_DYNAMIC_VALUE) 21 | HAT_SWITCH = Usage(0x39, UsageType.DATA_DYNAMIC_VALUE) 22 | COUNTED_BUFFER = Usage(0x3A, UsageType.COLLECTION_LOGICAL) 23 | BYTE_COUNT = Usage(0x3B, UsageType.DATA_DYNAMIC_VALUE) 24 | MOTION_WAKEUP = Usage(0x3C, UsageType.CONTROL_ONE_SHOT) 25 | START = Usage(0x3D, UsageType.CONTROL_ON_OFF) 26 | SELECT = Usage(0x3E, UsageType.CONTROL_ON_OFF) 27 | 28 | V_X = Usage(0x40, UsageType.DATA_DYNAMIC_VALUE) 29 | V_Y = Usage(0x41, UsageType.DATA_DYNAMIC_VALUE) 30 | V_Z = Usage(0x42, UsageType.DATA_DYNAMIC_VALUE) 31 | V_BRX = Usage(0x43, UsageType.DATA_DYNAMIC_VALUE) 32 | V_BRY = Usage(0x44, UsageType.DATA_DYNAMIC_VALUE) 33 | V_BRZ = Usage(0x45, UsageType.DATA_DYNAMIC_VALUE) 34 | V_NO = Usage(0x46, UsageType.DATA_DYNAMIC_VALUE) 35 | FEATURE_NOTIFICATION = Usage(0x47, [UsageType.DATA_DYNAMIC_VALUE, UsageType.DATA_DYNAMIC_FLAG]) 36 | RESOLUTION_MULTIPLIER = Usage(0x48, UsageType.DATA_DYNAMIC_VALUE) 37 | 38 | SYSTEM_CONTROL = Usage(0x80, UsageType.COLLECTION_APPLICATION) 39 | SYSTEM_POWER_DOWN = Usage(0x81, UsageType.CONTROL_ONE_SHOT) 40 | SYSTEM_SLEEP = Usage(0x82, UsageType.CONTROL_ONE_SHOT) 41 | SYSTEM_WAKE_UP = Usage(0x83, UsageType.CONTROL_ONE_SHOT) 42 | SYSTEM_CONTEXT_MENU = Usage(0x84, UsageType.CONTROL_ONE_SHOT) 43 | SYSTEM_MAIN_MENU = Usage(0x85, UsageType.CONTROL_ONE_SHOT) 44 | SYSTEM_APP_MENU = Usage(0x86, UsageType.CONTROL_ONE_SHOT) 45 | SYSTEM_MENU_HELP = Usage(0x87, UsageType.CONTROL_ONE_SHOT) 46 | SYSTEM_MENU_EXIT = Usage(0x88, UsageType.CONTROL_ONE_SHOT) 47 | SYSTEM_MENU_SELECT = Usage(0x89, UsageType.CONTROL_ONE_SHOT) 48 | SYSTEM_MENU_RIGHT = Usage(0x8A, UsageType.CONTROL_RE_TRIGGER) 49 | SYSTEM_MENU_LEFT = Usage(0x8B, UsageType.CONTROL_RE_TRIGGER) 50 | SYSTEM_MENU_UP = Usage(0x8C, UsageType.CONTROL_RE_TRIGGER) 51 | SYSTEM_MENU_DOWN = Usage(0x8D, UsageType.CONTROL_RE_TRIGGER) 52 | SYSTEM_COLD_RESTART = Usage(0x8E, UsageType.CONTROL_ONE_SHOT) 53 | SYSTEM_WARM_RESTART = Usage(0x8F, UsageType.CONTROL_ONE_SHOT) 54 | D_PAD_UP = Usage(0x90, UsageType.CONTROL_ON_OFF) 55 | D_PAY_DOWN = Usage(0x91, UsageType.CONTROL_ON_OFF) 56 | D_PAD_RIGHT = Usage(0x92, UsageType.CONTROL_ON_OFF) 57 | D_PAD_LEFT = Usage(0x93, UsageType.CONTROL_ON_OFF) 58 | 59 | SYSTEM_DOCK = Usage(0xA0, UsageType.CONTROL_ONE_SHOT) 60 | SYSTEM_UNDOCK = Usage(0xA1, UsageType.CONTROL_ONE_SHOT) 61 | SYSTEM_SETUP = Usage(0xA2, UsageType.CONTROL_ONE_SHOT) 62 | SYSTEM_BREAK = Usage(0xA3, UsageType.CONTROL_ONE_SHOT) 63 | SYSTEM_DEBUGGER_BREAK = Usage(0xA4, UsageType.CONTROL_ONE_SHOT) 64 | APPLICATION_BREAK = Usage(0xA5, UsageType.CONTROL_ONE_SHOT) 65 | APPLICATION_DEBUGGER_BREAK = Usage(0xA6, UsageType.CONTROL_ONE_SHOT) 66 | SYSTEM_SPEAKER_MUTE = Usage(0xA7, UsageType.CONTROL_ONE_SHOT) 67 | SYSTEM_HIBERNATE = Usage(0xA8, UsageType.CONTROL_ONE_SHOT) 68 | 69 | SYSTEM_DISPLAY_INVERT = Usage(0xB0, UsageType.CONTROL_ONE_SHOT) 70 | SYSTEM_DISPLAY_INTERNAL = Usage(0xB1, UsageType.CONTROL_ONE_SHOT) 71 | SYSTEM_DISPLAY_EXTERNAL = Usage(0xB2, UsageType.CONTROL_ONE_SHOT) 72 | SYSTEM_DISPLAY_BOTH = Usage(0xB3, UsageType.CONTROL_ONE_SHOT) 73 | SYSTEM_DISPLAY_DUAL = Usage(0xB4, UsageType.CONTROL_ONE_SHOT) 74 | SYSTEM_DISPLAY_TOGGLE_INT_EXT = Usage(0xB5, UsageType.CONTROL_ONE_SHOT) 75 | SYSTEM_DISPLAY_SWAP_PRIMARY_SECONDARY = Usage(0xB6, UsageType.CONTROL_ONE_SHOT) 76 | SYSTEM_DISPLAY_LCD_AUTOSCALE = Usage(0xB7, UsageType.CONTROL_ONE_SHOT) 77 | 78 | @classmethod 79 | def _get_usage_page_index(cls): 80 | return 0x01 81 | -------------------------------------------------------------------------------- /hidparser/UsagePage.py: -------------------------------------------------------------------------------- 1 | from enum import Enum as _Enum 2 | 3 | 4 | class UsageType(_Enum): 5 | CONTROL_LINEAR = () 6 | CONTROL_ON_OFF = () 7 | CONTROL_MOMENTARY = () 8 | CONTROL_ONE_SHOT = () 9 | CONTROL_RE_TRIGGER = () 10 | 11 | DATA_SELECTOR = () 12 | DATA_STATIC_VALUE = () 13 | DATA_STATIC_FLAG = () 14 | DATA_DYNAMIC_VALUE = () 15 | DATA_DYNAMIC_FLAG = () 16 | 17 | COLLECTION_NAMED_ARRAY = () 18 | COLLECTION_APPLICATION = () 19 | COLLECTION_LOGICAL = () 20 | COLLECTION_PHYSICAL = () 21 | COLLECTION_USAGE_SWITCH = () 22 | COLLECTION_USAGE_MODIFIER = () 23 | 24 | def __new__(cls): 25 | value = len(cls.__members__) + 1 26 | obj = object.__new__(cls) 27 | obj._value_ = value 28 | return obj 29 | 30 | @classmethod 31 | def control_usage_types(cls): 32 | return ( 33 | UsageType.CONTROL_LINEAR, 34 | UsageType.CONTROL_ON_OFF, 35 | UsageType.CONTROL_MOMENTARY, 36 | UsageType.CONTROL_ONE_SHOT, 37 | UsageType.CONTROL_RE_TRIGGER, 38 | ) 39 | 40 | @classmethod 41 | def data_usage_types(cls): 42 | return ( 43 | UsageType.DATA_SELECTOR, 44 | UsageType.DATA_STATIC_VALUE, 45 | UsageType.DATA_STATIC_FLAG, 46 | UsageType.DATA_DYNAMIC_VALUE, 47 | UsageType.DATA_DYNAMIC_FLAG, 48 | ) 49 | 50 | @classmethod 51 | def collection_usage_types(cls): 52 | return ( 53 | UsageType.COLLECTION_NAMED_ARRAY, 54 | # UsageType.collection_application, # Commented out as it is used for top level collections only 55 | UsageType.COLLECTION_LOGICAL, 56 | UsageType.COLLECTION_PHYSICAL, 57 | UsageType.COLLECTION_USAGE_SWITCH, 58 | UsageType.COLLECTION_USAGE_MODIFIER 59 | ) 60 | 61 | class Usage: 62 | def __init__(self, value, usage_types): 63 | if not isinstance(usage_types, list): 64 | usage_types = [usage_types,] 65 | for usage_type in usage_types: 66 | if not isinstance(usage_type, UsageType): 67 | raise ValueError("usage_type {} is not instance of {}".format( 68 | usage_type.__class__.__name__, 69 | UsageType.__name__) 70 | ) 71 | self.value = value 72 | self.usage_types = usage_types 73 | 74 | 75 | class UsagePage(_Enum): 76 | def __init__(self, item): 77 | if not isinstance(item, Usage): 78 | raise ValueError("{} is not a valid {}".format(item.__name__, self.__class__.__name__)) 79 | self.index = item.value & 0xFFFF 80 | self.usage = item 81 | self.usage_types = item.usage_types 82 | 83 | @classmethod 84 | def get_usage(cls, value): 85 | for key, member in cls.__members__.items(): 86 | if not isinstance(member.value, Usage): 87 | continue 88 | if member.index == value: 89 | return member 90 | raise ValueError("{} is not a valid {}".format(value, cls.__name__)) 91 | 92 | @classmethod 93 | def _get_usage_page_index(cls): 94 | raise NotImplementedError() 95 | 96 | @classmethod 97 | def find_usage_page(cls, value): 98 | if not hasattr(cls, "usage_page_map"): 99 | cls.usage_page_map = {usage_page._get_usage_page_index(): usage_page for usage_page in cls.__subclasses__()} 100 | if value in cls.usage_page_map.keys(): 101 | return cls.usage_page_map[value] 102 | if value not in range(0xFF00,0xFFFF): 103 | raise ValueError("Reserved or missing usage page 0x{:04X}".format(value)) 104 | raise NotImplementedError("Yet to support Vendor defined usage pages") 105 | 106 | 107 | class UsageRange: 108 | def __init__(self, usage_page: UsagePage.__class__ = None, minimum = None, maximum = None): 109 | self.usage_page = usage_page 110 | self.minimum = minimum 111 | self.maximum = maximum 112 | 113 | def get_range(self): 114 | if self.minimum is None or self.maximum is None: 115 | raise ValueError("Usage Minimum and Usage Maximum must be set") 116 | if isinstance(self.minimum, UsagePage): 117 | if not isinstance(self.maximum, UsagePage): 118 | raise ValueError("UsageRange type mismatch in minimum and maximum usages") 119 | self.usage_page = self.minimum.__class__ 120 | return [self.usage_page.get_usage(value) for value in range(self.minimum.index & 0xFFFF, (self.maximum.index & 0xFFFF) + 1)] 121 | if self.minimum & ~0xFFFF: 122 | self.usage_page = UsagePage.find_usage_page((self.minimum & ~0xFFFF) >> 16) 123 | return [self.usage_page.get_usage(value) for value in range(self.minimum & 0xFFFF, (self.maximum & 0xFFFF) + 1)] 124 | -------------------------------------------------------------------------------- /hidparser/UsagePages/Digitizers.py: -------------------------------------------------------------------------------- 1 | from hidparser.UsagePage import UsagePage, UsageType, Usage 2 | 3 | 4 | class Digitizers(UsagePage): 5 | 6 | @classmethod 7 | def _get_usage_page_index(cls): 8 | return 0x0D 9 | 10 | DIGITIZER = Usage(0x01,UsageType.COLLECTION_APPLICATION) 11 | PEN = Usage(0x02,UsageType.COLLECTION_APPLICATION) 12 | LIGHT_PEN = Usage(0x03,UsageType.COLLECTION_APPLICATION) 13 | TOUCH_SCREEN = Usage(0x04,UsageType.COLLECTION_APPLICATION) 14 | TOUCH_PAD = Usage(0x05,UsageType.COLLECTION_APPLICATION) 15 | WHITE_BOARD = Usage(0x06,UsageType.COLLECTION_APPLICATION) 16 | COORDINATE_MEASURING_MACHINE = Usage(0x07,UsageType.COLLECTION_APPLICATION) 17 | DIGITIZER_3D = Usage(0x08,UsageType.COLLECTION_APPLICATION) 18 | STEREO_PLOTTER = Usage(0x09,UsageType.COLLECTION_APPLICATION) 19 | ARTICULATED_ARM = Usage(0x0A,UsageType.COLLECTION_APPLICATION) 20 | ARMATURE = Usage(0x0B,UsageType.COLLECTION_APPLICATION) 21 | MULTIPLE_POINT_DIGITIZER = Usage(0x0C,UsageType.COLLECTION_APPLICATION) 22 | FREE_SPACE_WAND = Usage(0x0D,UsageType.COLLECTION_APPLICATION) 23 | 24 | STYLUS = Usage(0x20,UsageType.COLLECTION_LOGICAL) 25 | PUCK = Usage(0x21,UsageType.COLLECTION_LOGICAL) 26 | FINGER = Usage(0x22,UsageType.COLLECTION_LOGICAL) 27 | 28 | TIP_PRESSURE = Usage(0x30,UsageType.DATA_DYNAMIC_VALUE) 29 | BARREL_PRESSURE = Usage(0x31,UsageType.DATA_DYNAMIC_VALUE) 30 | IN_RANGE = Usage(0x32,UsageType.CONTROL_MOMENTARY) 31 | TOUCH = Usage(0x33,UsageType.CONTROL_MOMENTARY) 32 | UNTOUCH = Usage(0x34,UsageType.CONTROL_ONE_SHOT) 33 | TAP = Usage(0x35,UsageType.CONTROL_ONE_SHOT) 34 | QUALITY = Usage(0x36,UsageType.DATA_DYNAMIC_VALUE) 35 | DATA_VALID = Usage(0x37,UsageType.CONTROL_MOMENTARY) 36 | TRANSDUCER_INDEX = Usage(0x38,UsageType.DATA_DYNAMIC_VALUE) 37 | TABLET_FUNCTION_KEYS = Usage(0x39,UsageType.COLLECTION_LOGICAL) 38 | PROGRAM_CHANGE_KEYS = Usage(0x3A,UsageType.COLLECTION_LOGICAL) 39 | BATTERY_STRENGTH = Usage(0x3B,UsageType.DATA_DYNAMIC_VALUE) 40 | INVERT = Usage(0x3C,UsageType.CONTROL_MOMENTARY) 41 | X_TILT = Usage(0x3D,UsageType.DATA_DYNAMIC_VALUE) 42 | Y_TILT = Usage(0x3E,UsageType.DATA_DYNAMIC_VALUE) 43 | AZIMUTH = Usage(0x3F,UsageType.DATA_DYNAMIC_VALUE) 44 | ALTITUDE = Usage(0x40,UsageType.DATA_DYNAMIC_VALUE) 45 | TWIST = Usage(0x41,UsageType.DATA_DYNAMIC_VALUE) 46 | TIP_SWITCH = Usage(0x42,UsageType.CONTROL_MOMENTARY) 47 | SECONDARY_TIP_SWITCH = Usage(0x43,UsageType.CONTROL_MOMENTARY) 48 | BARREL_SWITCH = Usage(0x44,UsageType.CONTROL_MOMENTARY) 49 | ERASER = Usage(0x45,UsageType.CONTROL_MOMENTARY) 50 | TABLET_PICK = Usage(0x46,UsageType.CONTROL_MOMENTARY) 51 | 52 | # HID Usage Table Request 30: Addition of usages related to touch digitizers 53 | TOUCH_VALID = Usage(0x47, UsageType.CONTROL_MOMENTARY) 54 | WIDTH = Usage(0x48, UsageType.DATA_DYNAMIC_VALUE) 55 | HEIGHT = Usage(0x49, UsageType.DATA_DYNAMIC_VALUE) 56 | 57 | # HID Usage Table Request 34: Addition of usages related to multi-touch digitizers 58 | DEVICE_CONFIGURATION = Usage(0x0E, UsageType.COLLECTION_APPLICATION) 59 | DEVICE_SETTINGS = Usage(0x23, UsageType.COLLECTION_LOGICAL) 60 | 61 | CONTACT_IDENTIFIER = Usage(0x51, UsageType.DATA_DYNAMIC_VALUE) 62 | DEVICE_MODE = Usage(0x52, UsageType.DATA_DYNAMIC_VALUE) 63 | DEVICE_IDENTIFIER = Usage(0x53, UsageType.DATA_DYNAMIC_VALUE) 64 | CONTACT_COUNT = Usage(0x54, UsageType.DATA_DYNAMIC_VALUE) 65 | CONTACT_COUNT_MAXIMUM = Usage(0x55, UsageType.DATA_STATIC_VALUE) 66 | # Used by Microsoft... but not documented. Thanks! 67 | SCAN_TIME = Usage(0x56, UsageType.DATA_DYNAMIC_VALUE) 68 | 69 | # HID Usage Table Request 46: Additional Stylus Usages 70 | SECONDARY_BARREL_SWITCH = Usage(0x5A, UsageType.CONTROL_MOMENTARY) 71 | TRANSDUCER_SERIAL_NUMBER = Usage(0x5B, UsageType.DATA_STATIC_VALUE) 72 | 73 | # HID Usage Table Request 60: Stylus Width and Type Usages, Diagnostics, and Errors 74 | PREFERRED_COLOR_IS_LOCKED = Usage(0x5D, UsageType.CONTROL_MOMENTARY) 75 | PREFERRED_LINE_WIDTH = Usage(0x5E, UsageType.DATA_DYNAMIC_VALUE) 76 | PREFERRED_LINE_WIDTH_IS_LOCKED = Usage(0x5F, UsageType.CONTROL_MOMENTARY) 77 | PREFERRED_LINE_STYLE = Usage(0x70, UsageType.COLLECTION_NAMED_ARRAY) 78 | PREFERRED_LINE_STYLE_IS_LOCKED = Usage(0x71, UsageType.CONTROL_MOMENTARY) 79 | INK = Usage(0x72, UsageType.DATA_SELECTOR) 80 | PENCIL = Usage(0x73, UsageType.DATA_SELECTOR) 81 | HIGHLIGHTER = Usage(0x74, UsageType.DATA_SELECTOR) 82 | CHISEL_MARKER = Usage(0x75, UsageType.DATA_SELECTOR) 83 | BRUSH = Usage(0x76, UsageType.DATA_SELECTOR) 84 | NO_PREFERENCE = Usage(0x77, UsageType.DATA_SELECTOR) 85 | 86 | DIGITIZER_DIAGNOSTIC = Usage(0x80, UsageType.COLLECTION_LOGICAL) 87 | DIGITIZER_ERROR = Usage(0x81, UsageType.COLLECTION_NAMED_ARRAY) 88 | ERR_NORMAL_STATUS = Usage(0x82, UsageType.DATA_SELECTOR) 89 | ERR_TRANSDUCERS_EXCEEDED = Usage(0x83, UsageType.DATA_SELECTOR) 90 | ERR_FULL_TRANS_FEATURES_UNAVAIL = Usage(0x84, UsageType.DATA_SELECTOR) 91 | ERR_CHARGE_LOW = Usage(0x85, UsageType.DATA_SELECTOR) -------------------------------------------------------------------------------- /hidparser/ItemGlobal.py: -------------------------------------------------------------------------------- 1 | from hidparser import DeviceBuilder 2 | from hidparser.Item import ItemType, Item, ValueItem 3 | from hidparser.Device import Unit as _Unit 4 | from hidparser.UsagePage import UsagePage 5 | 6 | 7 | class UsagePageItem(ValueItem): 8 | usage_page = None 9 | 10 | def __init__(self, *args, **kwargs): 11 | kwargs["signed"] = False 12 | super(UsagePageItem, self).__init__(**kwargs) 13 | 14 | if len(self.data) not in [1,2]: 15 | raise ValueError("UsagePage has invalid length") 16 | 17 | self.usage_page = UsagePage.find_usage_page(self.value) 18 | 19 | def visit(self, descriptor: DeviceBuilder): 20 | descriptor.set_usage_page(UsagePage.find_usage_page(self.value)) 21 | 22 | @classmethod 23 | def _get_tag(cls): 24 | return 0x04 25 | 26 | @classmethod 27 | def _get_type(cls): 28 | return ItemType.GLOBAL 29 | 30 | def __repr__(self): 31 | return "<{}: {}>".format(self.__class__.__name__, self.usage_page.__name__) 32 | 33 | 34 | class LogicalMinimumItem(ValueItem): 35 | def visit(self, descriptor: DeviceBuilder): 36 | descriptor.set_logical_range(minimum=self.value) 37 | 38 | @classmethod 39 | def _get_tag(cls): 40 | return 0x14 41 | 42 | @classmethod 43 | def _get_type(cls): 44 | return ItemType.GLOBAL 45 | 46 | 47 | class LogicalMaximumItem(ValueItem): 48 | def visit(self, descriptor: DeviceBuilder): 49 | descriptor.set_logical_range(maximum=self.value) 50 | 51 | @classmethod 52 | def _get_tag(cls): 53 | return 0x24 54 | 55 | @classmethod 56 | def _get_type(cls): 57 | return ItemType.GLOBAL 58 | 59 | 60 | class PhysicalMinimumItem(ValueItem): 61 | def visit(self, descriptor: DeviceBuilder): 62 | descriptor.set_physical_range(minimum=self.value) 63 | 64 | @classmethod 65 | def _get_tag(cls): 66 | return 0x34 67 | 68 | @classmethod 69 | def _get_type(cls): 70 | return ItemType.GLOBAL 71 | 72 | 73 | class PhysicalMaximumItem(ValueItem): 74 | def visit(self, descriptor: DeviceBuilder): 75 | descriptor.set_physical_range(maximum=self.value) 76 | 77 | @classmethod 78 | def _get_tag(cls): 79 | return 0x44 80 | 81 | @classmethod 82 | def _get_type(cls): 83 | return ItemType.GLOBAL 84 | 85 | 86 | class UnitExponentItem(ValueItem): 87 | def visit(self, descriptor: DeviceBuilder): 88 | descriptor.unit_exponent = self.value 89 | 90 | @classmethod 91 | def _get_tag(cls): 92 | return 0x54 93 | 94 | @classmethod 95 | def _get_type(cls): 96 | return ItemType.GLOBAL 97 | 98 | 99 | class UnitItem(Item): 100 | def visit(self, descriptor: DeviceBuilder): 101 | descriptor.unit = _Unit.from_bytes(self.data) 102 | 103 | @classmethod 104 | def _get_tag(cls): 105 | return 0x64 106 | 107 | @classmethod 108 | def _get_type(cls): 109 | return ItemType.GLOBAL 110 | 111 | 112 | class ReportSizeItem(ValueItem): 113 | def __init__(self, *args, **kwargs): 114 | kwargs["signed"] = False 115 | super(ReportSizeItem, self).__init__(*args, **kwargs) 116 | 117 | def visit(self, descriptor: DeviceBuilder): 118 | descriptor.report_size = self.value 119 | 120 | @classmethod 121 | def _get_tag(cls): 122 | return 0x74 123 | 124 | @classmethod 125 | def _get_type(cls): 126 | return ItemType.GLOBAL 127 | 128 | 129 | class ReportIdItem(ValueItem): 130 | def __init__(self, *args, **kwargs): 131 | kwargs["signed"] = False 132 | super(ReportIdItem, self).__init__(*args, **kwargs) 133 | 134 | def visit(self, descriptor: DeviceBuilder): 135 | descriptor.set_report_id(self.value) 136 | 137 | @classmethod 138 | def _get_tag(cls): 139 | return 0x84 140 | 141 | @classmethod 142 | def _get_type(cls): 143 | return ItemType.GLOBAL 144 | 145 | 146 | class ReportCountItem(ValueItem): 147 | def __init__(self, *args, **kwargs): 148 | kwargs["signed"] = False 149 | super(ReportCountItem, self).__init__(*args, **kwargs) 150 | 151 | def visit(self, descriptor: DeviceBuilder): 152 | descriptor.report_count = self.value 153 | 154 | @classmethod 155 | def _get_tag(cls): 156 | return 0x94 157 | 158 | @classmethod 159 | def _get_type(cls): 160 | return ItemType.GLOBAL 161 | 162 | 163 | class PushItem(Item): 164 | def visit(self, descriptor: DeviceBuilder): 165 | descriptor.push() 166 | 167 | @classmethod 168 | def _get_tag(cls): 169 | return 0xA4 170 | 171 | @classmethod 172 | def _get_type(cls): 173 | return ItemType.GLOBAL 174 | 175 | 176 | class PopItem(Item): 177 | def visit(self, descriptor: DeviceBuilder): 178 | descriptor.pop() 179 | 180 | @classmethod 181 | def _get_tag(cls): 182 | return 0xB4 183 | 184 | @classmethod 185 | def _get_type(cls): 186 | return ItemType.GLOBAL -------------------------------------------------------------------------------- /hidparser/Physical.py: -------------------------------------------------------------------------------- 1 | from enum import Enum as _Enum 2 | import struct as _struct 3 | 4 | 5 | class Bias(_Enum): 6 | NOT_APPLICABLE = 0 7 | RIGHT_HAND = 1 8 | LEFT_HAND = 2 9 | BOTH_HANDS = 3 10 | EITHER_HAND = 4 11 | 12 | def __str__(self): 13 | return self._name_.replace("_", " ").title() 14 | 15 | 16 | class Qualifier(_Enum): 17 | NOT_APPLICABLE = 0 18 | RIGHT = 1 19 | LEFT = 2 20 | BOTH = 3 21 | EITHER = 4 22 | CENTER = 5 23 | 24 | def __str__(self): 25 | return self._name_.replace("_", " ").title() 26 | 27 | 28 | class Designator(_Enum): 29 | NONE = 0x00 30 | HAND = 0x01 31 | EYEBALL = 0x02 32 | EYEBROW = 0x03 33 | EYELID = 0x04 34 | EAR = 0x05 35 | NOSE = 0x06 36 | MOUTH = 0x07 37 | UPPER_LIP = 0x08 38 | LOWER_LIP = 0x09 39 | JAW = 0x0A 40 | NECK = 0x0B 41 | UPPER_ARM = 0x0C 42 | ELBOW = 0x0D 43 | FOREARM = 0x0E 44 | WRIST = 0x0F 45 | PALM = 0x10 46 | THUMB = 0x11 47 | INDEX_FINGER = 0x12 48 | MIDDLE_FINGER = 0x13 49 | RING_FINGER = 0x14 50 | LITTLE_FINGER = 0x15 51 | HEAD = 0x16 52 | SHOULDER = 0x17 53 | HIP = 0x18 54 | WAIST = 0x19 55 | THIGH = 0x1A 56 | KNEE = 0x1B 57 | CALF = 0x1C 58 | ANKLE = 0x1D 59 | FOOT = 0x1E 60 | HEEL = 0x1F 61 | BALL_OF_FOOT = 0x20 62 | BIG_TOE = 0x21 63 | SECOND_TOE = 0x22 64 | THIRD_TOE = 0x23 65 | FOURTH_TOE = 0x24 66 | LITTLE_TOE = 0x25 67 | BROW = 0x26 68 | CHEEK = 0x27 69 | 70 | def __str__(self): 71 | return self._name_.replace("_", " ").title() 72 | 73 | 74 | class PhysicalDescriptor: 75 | def __init__(self, designator: Designator, qualifier: Qualifier, effort: int=0): 76 | self.designator = designator 77 | self.qualifier = qualifier 78 | self.effort = effort 79 | 80 | def __repr__(self): 81 | return "<{}: {}, {}, Effort {:d}>".format( 82 | self.__class__.__name__, 83 | self.designator.__str__(), 84 | self.qualifier.__str__(), 85 | self.effort 86 | ) 87 | 88 | 89 | class PhysicalDescriptorSubSet: 90 | def __init__(self, bias: Bias, preference: int=0, descriptors=None): 91 | self.bias = bias 92 | if not 0 <= preference <= 31: 93 | raise ValueError("preference({}) can not be outside of range 0 to 31".format(preference)) 94 | self.preference = preference 95 | self.descriptors = [] 96 | 97 | if descriptors is not None: 98 | if type(descriptors) not in (list, tuple): 99 | descriptors = (descriptors,) 100 | self.extend(descriptors) 101 | 102 | def __iter__(self): 103 | return iter(self.descriptors) 104 | 105 | def __getitem__(self, item): 106 | return self.descriptors[item] 107 | 108 | def __setitem__(self, key, value): 109 | if not isinstance(value, PhysicalDescriptor): 110 | raise ValueError("Can not assign '{}' to descriptors".format(type(value))) 111 | self.descriptors[key] = value 112 | 113 | def append(self, item): 114 | self.extend((item,)) 115 | 116 | def extend(self, items): 117 | if len([descriptor for descriptor in items if not isinstance(descriptor, PhysicalDescriptor)]): 118 | raise ValueError("Can not assign item that's not a PhysicalDescriptor") 119 | self.descriptors.extend(items) 120 | 121 | def __repr__(self): 122 | return "<{}: Bias({}), Preference({}), [{}]>".format( 123 | self.__class__.__name__, 124 | self.bias.__str__(), 125 | self.preference, 126 | ", ".join([desc.__repr__() for desc in self.descriptors]) 127 | ) 128 | 129 | 130 | class PhysicalDescriptorSet: 131 | def __init__(self): 132 | self._total = 0 133 | self._length = 0 134 | self._sets = {} # type: Dict[int, PhysicalDescriptorSubSet] 135 | 136 | def parse(self, index, data): 137 | data = bytes(data) 138 | if index is 0: 139 | if len(data) != 3: 140 | raise ValueError("Physical descriptor at index 0 is 3 bytes in size") 141 | self._total, self._length = _struct.unpack(" self._total: 146 | raise IndexError("index({:d}) is out of bounds".format(index)) 147 | if index in self._sets.keys(): 148 | del self._sets[index] 149 | bias = Bias((data[0] & 0xE0) >> 5) 150 | preference = data[0] & 0x1F 151 | 152 | pdset = PhysicalDescriptorSubSet(bias, preference) 153 | self._sets[index] = pdset 154 | 155 | for i in range(self._length): 156 | offset = (i * 2) + 1 157 | designator, flags = _struct.unpack("> 5), 161 | flags & 0x1F 162 | )) 163 | 164 | def __getitem__(self, item): 165 | return self._sets[item] 166 | 167 | def __len__(self): 168 | return self._total 169 | -------------------------------------------------------------------------------- /hidparser/UsagePages/Power.py: -------------------------------------------------------------------------------- 1 | from hidparser.UsagePage import UsagePage, UsageType, Usage 2 | 3 | # Built from https://www.usb.org/sites/default/files/pdcv10.pdf 4 | 5 | class Power(UsagePage): 6 | 7 | @classmethod 8 | def _get_usage_page_index(cls): 9 | return 0x84 10 | 11 | I_NAME = Usage(0x01, UsageType.DATA_STATIC_VALUE) 12 | PRESENT_STATUS = Usage(0x02, UsageType.COLLECTION_LOGICAL) 13 | CHANGED_STATUS = Usage(0x03, UsageType.COLLECTION_LOGICAL) 14 | UPS = Usage(0x04, UsageType.COLLECTION_APPLICATION) 15 | POWER_SUPPLY = Usage(0x05, UsageType.COLLECTION_APPLICATION) 16 | 17 | BATTERY_SYSTEM = Usage(0x10, UsageType.COLLECTION_PHYSICAL) 18 | BATTERY_SYSTEM_ID = Usage(0x11, UsageType.DATA_STATIC_VALUE) 19 | 20 | BATTERY = Usage(0x12, UsageType.COLLECTION_PHYSICAL) 21 | BATTERY_ID = Usage(0x13, UsageType.DATA_STATIC_VALUE) 22 | 23 | CHARGER = Usage(0x14, UsageType.COLLECTION_PHYSICAL) 24 | CHARGER_ID = Usage(0x15, UsageType.DATA_STATIC_VALUE) 25 | 26 | POWER_CONVERTER = Usage(0x16, UsageType.COLLECTION_PHYSICAL) 27 | POWER_CONVERTER_ID = Usage(0x17, UsageType.DATA_STATIC_VALUE) 28 | 29 | OUTLET_SYSTEM = Usage(0x18, UsageType.COLLECTION_PHYSICAL) 30 | OUTLET_SYSTEM_ID = Usage(0x19, UsageType.DATA_STATIC_VALUE) 31 | 32 | INPUT = Usage(0x1A, UsageType.COLLECTION_PHYSICAL) 33 | INPUT_ID = Usage(0x1B, UsageType.DATA_STATIC_VALUE) 34 | 35 | OUTPUT = Usage(0x1C, UsageType.COLLECTION_PHYSICAL) 36 | OUTPUT_ID = Usage(0x1D, UsageType.DATA_STATIC_VALUE) 37 | 38 | FLOW = Usage(0x1E, UsageType.COLLECTION_PHYSICAL) 39 | FLOW_ID = Usage(0x1F, UsageType.DATA_STATIC_VALUE) 40 | 41 | OUTLET = Usage(0x20, UsageType.COLLECTION_PHYSICAL) 42 | OUTLET_ID = Usage(0x21, UsageType.DATA_STATIC_VALUE) 43 | 44 | GANG = Usage(0x22, [UsageType.COLLECTION_PHYSICAL, UsageType.COLLECTION_LOGICAL]) 45 | GANG_ID = Usage(0x23, UsageType.DATA_STATIC_VALUE) 46 | 47 | POWER_SUMMARY = Usage(0x24, [UsageType.COLLECTION_PHYSICAL, UsageType.COLLECTION_LOGICAL]) 48 | POWER_SUMMARY_ID = Usage(0x25, UsageType.DATA_STATIC_VALUE) 49 | 50 | VOLTAGE = Usage(0x30, UsageType.DATA_DYNAMIC_VALUE) 51 | CURRENT = Usage(0x31, UsageType.DATA_DYNAMIC_VALUE) 52 | FREQUENCY = Usage(0x32, UsageType.DATA_DYNAMIC_VALUE) 53 | APPARENT_POWER = Usage(0x33, UsageType.DATA_DYNAMIC_VALUE) 54 | ACTIVE_POWER = Usage(0x34, UsageType.DATA_DYNAMIC_VALUE) 55 | PERCENT_LOAD = Usage(0x35, UsageType.DATA_DYNAMIC_VALUE) 56 | TEMPERATURE = Usage(0x36, UsageType.DATA_DYNAMIC_VALUE) 57 | HUMIDITY = Usage(0x37, UsageType.DATA_DYNAMIC_VALUE) 58 | BAD_COUNT = Usage(0x38, UsageType.DATA_DYNAMIC_VALUE) 59 | 60 | CONFIG_VOLTAGE = Usage(0x40, [UsageType.DATA_DYNAMIC_VALUE, UsageType.DATA_STATIC_VALUE]) 61 | CONFIG_CURRENT = Usage(0x41, [UsageType.DATA_DYNAMIC_VALUE, UsageType.DATA_STATIC_VALUE]) 62 | CONFIG_FREQUENCY = Usage(0x42, [UsageType.DATA_DYNAMIC_VALUE, UsageType.DATA_STATIC_VALUE]) 63 | CONFIG_APPARENT_POWER = Usage(0x43, [UsageType.DATA_DYNAMIC_VALUE, UsageType.DATA_STATIC_VALUE]) 64 | CONFIG_ACTIVE_POWER = Usage(0x44, [UsageType.DATA_DYNAMIC_VALUE, UsageType.DATA_STATIC_VALUE]) 65 | CONFIG_PERCENT_LOAD = Usage(0x45, [UsageType.DATA_DYNAMIC_VALUE, UsageType.DATA_STATIC_VALUE]) 66 | CONFIG_TEMPERATURE = Usage(0x46, [UsageType.DATA_DYNAMIC_VALUE, UsageType.DATA_STATIC_VALUE]) 67 | CONFIG_HUMIDITY = Usage(0x47, [UsageType.DATA_DYNAMIC_VALUE, UsageType.DATA_STATIC_VALUE]) 68 | 69 | SWITCH_ON_CONTROL = Usage(0x50, UsageType.DATA_DYNAMIC_VALUE) 70 | SWITCH_OFF_CONTROL = Usage(0x51, UsageType.DATA_DYNAMIC_VALUE) 71 | TOGGLE_CONTROL = Usage(0x52, UsageType.DATA_DYNAMIC_VALUE) 72 | LOW_VOLTAGE_TRANSFER = Usage(0x53, UsageType.DATA_DYNAMIC_VALUE) 73 | HIGH_VOLTAGE_TRANSFER = Usage(0x54, UsageType.DATA_DYNAMIC_VALUE) 74 | DELAY_BEFORE_REBOOT = Usage(0x55, UsageType.DATA_DYNAMIC_VALUE) 75 | DELAY_BEFORE_STARTUP = Usage(0x56, UsageType.DATA_DYNAMIC_VALUE) 76 | DELAY_BEFORE_SHUTDOWN = Usage(0x57, UsageType.DATA_DYNAMIC_VALUE) 77 | TEST = Usage(0x58, UsageType.DATA_DYNAMIC_VALUE) 78 | MODULE_RESET = Usage(0x59, UsageType.DATA_DYNAMIC_VALUE) 79 | AUDIBLE_ALARM_CONTROL = Usage(0x5A, UsageType.DATA_DYNAMIC_VALUE) 80 | 81 | PRESENT = Usage(0x60, UsageType.DATA_DYNAMIC_FLAG) 82 | GOOD = Usage(0x61, UsageType.DATA_DYNAMIC_FLAG) 83 | INTERNAL_FAILURE = Usage(0x62, UsageType.DATA_DYNAMIC_FLAG) 84 | VOLTAGE_OUT_OF_RANGE = Usage(0x63, UsageType.DATA_DYNAMIC_FLAG) 85 | FREQUENCY_OUT_OF_RANGE = Usage(0x64, UsageType.DATA_DYNAMIC_FLAG) 86 | OVERLOAD = Usage(0x65, UsageType.DATA_DYNAMIC_FLAG) 87 | OVER_CHARGED = Usage(0x66, UsageType.DATA_DYNAMIC_FLAG) 88 | OVER_TEMPERATURE = Usage(0x67, UsageType.DATA_DYNAMIC_FLAG) 89 | SHUTDOWN_REQUESTED = Usage(0x68, UsageType.DATA_DYNAMIC_FLAG) 90 | SHUTDOWN_IMMINENT = Usage(0x69, UsageType.DATA_DYNAMIC_FLAG) 91 | 92 | SWITCH_ON_OFF = Usage(0x6B, UsageType.DATA_DYNAMIC_FLAG) 93 | SWITCHABLE = Usage(0x6C, UsageType.DATA_DYNAMIC_FLAG) 94 | USED = Usage(0x6D, UsageType.DATA_DYNAMIC_FLAG) 95 | BOOST = Usage(0x6E, UsageType.DATA_DYNAMIC_FLAG) 96 | BUCK = Usage(0x6F, UsageType.DATA_DYNAMIC_FLAG) 97 | 98 | INITIALIZED = Usage(0x70, UsageType.DATA_DYNAMIC_FLAG) 99 | TESTED = Usage(0x71, UsageType.DATA_DYNAMIC_FLAG) 100 | AWAITING_POWER = Usage(0x72, UsageType.DATA_DYNAMIC_FLAG) 101 | COMMUNICATION_LOST = Usage(0x73, UsageType.DATA_DYNAMIC_FLAG) 102 | 103 | I_MANUFACTURER = Usage(0xFD, UsageType.DATA_STATIC_VALUE) 104 | I_PRODUCT = Usage(0xFE, UsageType.DATA_DYNAMIC_FLAG) 105 | I_SERIAL_NUMBER = Usage(0xFF, UsageType.DATA_DYNAMIC_FLAG) 106 | -------------------------------------------------------------------------------- /hidparser/UsagePages/Battery.py: -------------------------------------------------------------------------------- 1 | from hidparser.UsagePage import UsagePage, UsageType, Usage 2 | 3 | # Built from https://www.usb.org/sites/default/files/pdcv10.pdf 4 | 5 | class Battery(UsagePage): 6 | 7 | @classmethod 8 | def _get_usage_page_index(cls): 9 | return 0x85 10 | 11 | SMB_BATTERY_MODE = Usage(0x01, UsageType.COLLECTION_LOGICAL) 12 | SMB_BATTERY_STATUS = Usage(0x02, UsageType.COLLECTION_LOGICAL) 13 | SMB_ALARM_WARNING = Usage(0x03, UsageType.COLLECTION_LOGICAL) 14 | SMB_CHARGER_MODE = Usage(0x04, UsageType.COLLECTION_APPLICATION) 15 | SMB_CHARGER_STATUS = Usage(0x05, UsageType.COLLECTION_APPLICATION) 16 | SMB_CHARGER_SPEC_INFO = Usage(0x06, UsageType.COLLECTION_APPLICATION) 17 | SMB_SELECTOR_STATE = Usage(0x07, UsageType.COLLECTION_APPLICATION) 18 | SMB_SELECTOR_PRESETS = Usage(0x07, UsageType.COLLECTION_APPLICATION) 19 | SMB_SELECTOR_INFO = Usage(0x07, UsageType.COLLECTION_APPLICATION) 20 | 21 | OPTIONAL_MFG_FUNCTION_1 = Usage(0x10, UsageType.DATA_DYNAMIC_VALUE) 22 | OPTIONAL_MFG_FUNCTION_2 = Usage(0x11, UsageType.DATA_DYNAMIC_VALUE) 23 | OPTIONAL_MFG_FUNCTION_3 = Usage(0x12, UsageType.DATA_DYNAMIC_VALUE) 24 | OPTIONAL_MFG_FUNCTION_4 = Usage(0x13, UsageType.DATA_DYNAMIC_VALUE) 25 | OPTIONAL_MFG_FUNCTION_5 = Usage(0x14, UsageType.DATA_DYNAMIC_VALUE) 26 | 27 | CONNECTION_TO_SMBUS = Usage(0x15, UsageType.DATA_DYNAMIC_FLAG) 28 | OUTPUT_CONNECTION = Usage(0x16, UsageType.DATA_DYNAMIC_FLAG) 29 | CHARGER_CONNECTION = Usage(0x17, UsageType.DATA_DYNAMIC_FLAG) 30 | BATTERY_INSERTION = Usage(0x18, UsageType.DATA_DYNAMIC_FLAG) 31 | USE_NEXT = Usage(0x19, UsageType.DATA_DYNAMIC_FLAG) 32 | OK_TO_USE = Usage(0x1A, UsageType.DATA_DYNAMIC_FLAG) 33 | BATTERY_SUPPORTED = Usage(0x1B, UsageType.DATA_DYNAMIC_FLAG) 34 | SELECTOR_REVISION = Usage(0x1C, UsageType.DATA_DYNAMIC_FLAG) 35 | CHARGING_INDICATOR = Usage(0x1D, UsageType.DATA_DYNAMIC_FLAG) 36 | 37 | MANUFACTURER_ACCESS = Usage(0x28, UsageType.DATA_DYNAMIC_VALUE) 38 | REMAINING_CAPACITY_LIMIT = Usage(0x29, UsageType.DATA_DYNAMIC_VALUE) 39 | REMAINING_TIME_LIMIT = Usage(0x2A, UsageType.DATA_DYNAMIC_VALUE) 40 | AT_RATE = Usage(0x2B, UsageType.DATA_DYNAMIC_VALUE) 41 | CAPACITY_MODE = Usage(0x2C, UsageType.DATA_DYNAMIC_VALUE) 42 | BROADCAST_TO_CHARGER = Usage(0x2D, UsageType.DATA_DYNAMIC_VALUE) 43 | PRIMARY_BATTERY = Usage(0x2E, UsageType.DATA_DYNAMIC_VALUE) 44 | CHARGE_CONTROLLER = Usage(0x2F, UsageType.DATA_DYNAMIC_VALUE) 45 | 46 | TERMINATE_CHARGE = Usage(0x40, UsageType.DATA_DYNAMIC_FLAG) 47 | TERMINATE_DISCHARGE = Usage(0x41, UsageType.DATA_DYNAMIC_FLAG) 48 | BELOW_REMAINING_CAPACITY_LIMIT = Usage(0x42, UsageType.DATA_DYNAMIC_FLAG) 49 | REMAINING_TIME_LIMIT_EXPIRED = Usage(0x43, UsageType.DATA_DYNAMIC_FLAG) 50 | CHARGING = Usage(0x44, UsageType.DATA_DYNAMIC_FLAG) 51 | DISCHARGING = Usage(0x45, UsageType.DATA_DYNAMIC_VALUE) 52 | FULLY_CHARGED = Usage(0x46, UsageType.DATA_DYNAMIC_FLAG) 53 | FULLY_DISCHARGED = Usage(0x47, UsageType.DATA_DYNAMIC_VALUE) 54 | CONDITIONING_FLAG = Usage(0x48, UsageType.DATA_DYNAMIC_VALUE) 55 | AT_RATE_OK = Usage(0x49, UsageType.DATA_DYNAMIC_VALUE) 56 | SMB_ERROR_CODE = Usage(0x4A, UsageType.DATA_DYNAMIC_FLAG) 57 | NEED_REPLACEMENT = Usage(0x4B, UsageType.DATA_DYNAMIC_FLAG) 58 | 59 | AT_RATE_TIME_TO_FULL = Usage(0x60, UsageType.DATA_DYNAMIC_VALUE) 60 | AT_RATE_TIME_TO_EMPTY = Usage(0x61, UsageType.DATA_DYNAMIC_VALUE) 61 | AVERAGE_CURRENT = Usage(0x62, UsageType.DATA_DYNAMIC_VALUE) 62 | MAX_ERROR = Usage(0x63, UsageType.DATA_DYNAMIC_VALUE) 63 | RELATIVE_STATE_OF_CHARGE = Usage(0x64, UsageType.DATA_DYNAMIC_VALUE) 64 | ABSOLUTE_STATE_OF_CHARGE = Usage(0x65, UsageType.DATA_DYNAMIC_VALUE) 65 | REMAINING_CAPACITY = Usage(0x66, UsageType.DATA_DYNAMIC_VALUE) 66 | FULL_CHARGE_CAPACITY = Usage(0x67, UsageType.DATA_DYNAMIC_VALUE) 67 | RUN_TIME_TO_EMPTY = Usage(0x68, UsageType.DATA_DYNAMIC_VALUE) 68 | AVERAGE_TIME_TO_EMPTY = Usage(0x69, UsageType.DATA_DYNAMIC_VALUE) 69 | AVERAGE_TIME_TO_FULL = Usage(0x6A, UsageType.DATA_DYNAMIC_VALUE) 70 | CYCLE_COUNT = Usage(0x6B, UsageType.DATA_DYNAMIC_VALUE) 71 | 72 | BATT_PACK_MODEL_LEVEL = Usage(0x80, UsageType.DATA_STATIC_VALUE) 73 | INTERNAL_CHARGE_CONTROLLER = Usage(0x81, UsageType.DATA_STATIC_FLAG) 74 | PRIMARY_BATTERY_SUPPORT = Usage(0x82, UsageType.DATA_STATIC_FLAG) 75 | DESIGN_CAPACITY = Usage(0x83, UsageType.DATA_STATIC_VALUE) 76 | SPECIFICATION_INFO = Usage(0x84, UsageType.DATA_STATIC_VALUE) 77 | MANUFACTURER_DATE = Usage(0x85, UsageType.DATA_STATIC_VALUE) 78 | SERIAL_NUMBER = Usage(0x86, UsageType.DATA_STATIC_VALUE) 79 | I_MANUFACTURER_NAME = Usage(0x87, UsageType.DATA_STATIC_VALUE) 80 | I_DEVICE_NAME = Usage(0x88, UsageType.DATA_STATIC_VALUE) 81 | I_DEVICE_CHEMISTERY = Usage(0x89, UsageType.DATA_STATIC_VALUE) 82 | MANUFACTURER_DATA = Usage(0x8A, UsageType.DATA_STATIC_VALUE) 83 | RECHARGABLE = Usage(0x8B, UsageType.DATA_STATIC_VALUE) 84 | WARNING_CAPACITY_LIMIT = Usage(0x8C, UsageType.DATA_STATIC_VALUE) 85 | CAPACITY_GRANULARITY_1 = Usage(0x8D, UsageType.DATA_STATIC_VALUE) 86 | CAPACITY_GRANULARITY_2 = Usage(0x8E, UsageType.DATA_STATIC_VALUE) 87 | I_OEM_INFORMATION = Usage(0x8F, UsageType.DATA_STATIC_VALUE) 88 | 89 | INHIBIT_CHARGE = Usage(0xC0, UsageType.DATA_DYNAMIC_FLAG) 90 | ENABLE_POLLING = Usage(0xC1, UsageType.DATA_DYNAMIC_FLAG) 91 | RESET_TO_ZERO = Usage(0xC2, UsageType.DATA_DYNAMIC_FLAG) 92 | 93 | AC_PRESENT = Usage(0xD0, UsageType.DATA_DYNAMIC_FLAG) 94 | BATTERY_PRESENT = Usage(0xD1, UsageType.DATA_DYNAMIC_FLAG) 95 | POWER_FAIL = Usage(0xD2, UsageType.DATA_DYNAMIC_FLAG) 96 | ALARM_INHIBITED = Usage(0xD3, UsageType.DATA_DYNAMIC_FLAG) 97 | THERMISTOR_UNDER_RANGE = Usage(0xD4, UsageType.DATA_DYNAMIC_FLAG) 98 | THERMISTOR_HOT = Usage(0xD5, UsageType.DATA_DYNAMIC_FLAG) 99 | THERMISTOR_COLD = Usage(0xD6, UsageType.DATA_DYNAMIC_FLAG) 100 | THERMISTOR_OVER_RANGE = Usage(0xD7, UsageType.DATA_DYNAMIC_FLAG) 101 | VOLTAGE_OUT_OF_RANGE = Usage(0xD8, UsageType.DATA_DYNAMIC_FLAG) 102 | CURRENT_OUT_OF_RANGE = Usage(0xD9, UsageType.DATA_DYNAMIC_FLAG) 103 | CURRENT_NOT_REGULATED = Usage(0xDA, UsageType.DATA_DYNAMIC_FLAG) 104 | VOLTAGE_NOT_REGULATED = Usage(0xDB, UsageType.DATA_DYNAMIC_FLAG) 105 | MASTER_MODE = Usage(0xDC, UsageType.DATA_DYNAMIC_FLAG) 106 | 107 | CHARGER_SELECTOR_SUPPORT = Usage(0xF0, UsageType.DATA_STATIC_FLAG) 108 | CHARGER_SPEC = Usage(0xF1, UsageType.DATA_STATIC_VALUE) 109 | LEVEL_2 = Usage(0xF2, UsageType.DATA_STATIC_FLAG) 110 | LEVEL_3 = Usage(0xF3, UsageType.DATA_STATIC_FLAG) -------------------------------------------------------------------------------- /hidparser/DeviceBuilder.py: -------------------------------------------------------------------------------- 1 | import copy as _copy 2 | from hidparser.enums import CollectionType, ReportFlags, EnumMask, ReportType 3 | from hidparser.UsagePage import UsagePage, Usage, UsageType, UsageRange 4 | from hidparser.Device import Device, Report, ReportGroup, Collection 5 | from hidparser.helper import ValueRange 6 | 7 | 8 | class DeviceBuilder: 9 | def __init__(self): 10 | self._state_stack = [] 11 | 12 | # Global Items 13 | self.usage_page = None 14 | self.unit = None 15 | self.unit_exponent = 1 16 | 17 | self.report_id = 0 18 | self.report_size = 0 19 | self.report_count = 0 20 | self.logical_range = ValueRange() 21 | self.physical_range = self.logical_range 22 | 23 | # Local Items 24 | self._usages = [] 25 | self._last_usage = None 26 | 27 | self.designators = range(0) 28 | self.strings = range(0) 29 | 30 | self._collection = Collection(allowed_usage_types=UsageType.COLLECTION_APPLICATION) 31 | self._current_collection = self._collection 32 | 33 | def add_report(self, report_type: ReportType, flags): 34 | usages = [] 35 | if not flags & ReportFlags.CONSTANT or len(self._usages): 36 | if len(self._usages) is 0: 37 | usages = [self._last_usage]*self.report_count 38 | else: 39 | while len(usages) < self.report_count: 40 | usage = self._usages.pop(0) 41 | self._last_usage = usage 42 | usages.extend(usage.get_range() if isinstance(usage, UsageRange) else [usage]) 43 | 44 | designators = range(0) 45 | if len(self.designators) > 0: 46 | designator_diff = len(self.designators) - self.report_count 47 | assert designator_diff >= 0, "Too few designators for report" 48 | designators = self.designators[0:-designator_diff] 49 | self.designators = self.designators[designator_diff + 1:] 50 | 51 | strings = range(0) 52 | if len(self.strings) > 0: 53 | strings_diff = len(self.strings) - self.report_count 54 | assert strings_diff >= 0, "Too few strings for report" 55 | strings = self.strings[0:-strings_diff] 56 | self.strings = self.strings[strings_diff + 1:] 57 | 58 | self._current_collection.append(Report( 59 | report_id=self.report_id, 60 | report_type=report_type, 61 | usages=usages, 62 | designators=designators, 63 | strings=strings, 64 | size=self.report_size, 65 | count=self.report_count, 66 | logical_range=_copy.copy(self.logical_range), 67 | physical_range=_copy.copy(self.physical_range), 68 | unit=self.unit, 69 | exponent=self.unit_exponent, 70 | flags=flags 71 | )) 72 | 73 | def set_report_id(self, report_id: int): 74 | self.report_id = report_id 75 | 76 | def set_designator_range(self, minimum=None, maximum=None): 77 | if minimum is None: 78 | minimum = self.designators.start 79 | if maximum is None: 80 | maximum = self.designators.stop - 1 # Subtract one, so the output range generator is inclusive from start to stop 81 | self.designators = range(minimum, maximum + 1) 82 | 83 | def set_string_range(self, minimum=None, maximum=None): 84 | if minimum is None: 85 | minimum = self.strings.start 86 | if maximum is None: 87 | maximum = self.strings.stop - 1 # Subtract one, so the output range generator is inclusive from start to stop 88 | self.strings = range(minimum, maximum + 1) 89 | 90 | def set_usage_range(self, minimum=None, maximum=None): 91 | usage = self._usages[len(self._usages)-1] if len(self._usages) else None 92 | if usage is None or not isinstance(usage, UsageRange): 93 | usage = UsageRange(self.usage_page) 94 | self._usages.append(usage) 95 | 96 | if minimum is not None: 97 | usage.minimum = minimum 98 | if maximum is not None: 99 | usage.maximum = maximum 100 | 101 | def set_logical_range(self, minimum = None, maximum = None): 102 | if minimum is not None: 103 | self.logical_range.minimum = minimum 104 | 105 | if maximum is not None: 106 | self.logical_range.maximum = maximum 107 | 108 | def set_physical_range(self, minimum=None, maximum=None): 109 | if minimum is not None: 110 | self.physical_range.minimum = minimum 111 | 112 | if maximum is not None: 113 | self.physical_range.maximum = maximum 114 | 115 | def add_usage(self, usage): 116 | if isinstance(usage, Usage): 117 | self._usages.append(usage) 118 | elif isinstance(usage, UsagePage): 119 | self._usages.append(usage.value) 120 | else: 121 | usage_page = self.usage_page if (usage & ~0xFFFF) == 0 else UsagePage.find_usage_page((usage & ~0xFFFF) >> 16) 122 | if usage_page is None: 123 | raise ValueError("Invalid usage page") 124 | self._usages.append(usage_page.get_usage(usage & 0xFFFF)) 125 | 126 | def set_usage_page(self, usage_page: UsagePage.__class__): 127 | self.usage_page = usage_page 128 | self._usages.clear() 129 | 130 | def push_collection(self, collection: CollectionType): 131 | try: 132 | usage = self._usages.pop(0) 133 | except IndexError: 134 | usage = None 135 | collection_element = Collection( 136 | usage=usage, 137 | parent=self._current_collection, 138 | collection_type=collection 139 | ) 140 | self._current_collection.append(collection_element) 141 | self._current_collection = collection_element 142 | 143 | self._usages.clear() 144 | return self 145 | 146 | def pop_collection(self): 147 | if self._current_collection.parent is None: 148 | raise RuntimeError("Can not pop collection state") 149 | self._current_collection = self._current_collection.parent 150 | 151 | self._usages.clear() 152 | return self 153 | 154 | def push(self): 155 | """ 156 | Pushes the state of all the global items onto the stack 157 | """ 158 | global_items = ( 159 | "usage_page", "unit","unit_exponent", "report_id", 160 | "report_size", "report_count", "logical_range", "physical_range" 161 | ) 162 | state = _copy.deepcopy({k:v for k,v in self.__dict__.items() if k in global_items}) 163 | self._state_stack.append(state) 164 | return self 165 | 166 | def pop(self): 167 | """ 168 | Pops and restores the global item states from the stack 169 | """ 170 | if len(self._state_stack) > 0: 171 | state = self._state_stack.pop() 172 | self.__dict__.update(state) 173 | return self 174 | 175 | def build(self): 176 | return Device(self._collection.items) 177 | -------------------------------------------------------------------------------- /hidparser/Device.py: -------------------------------------------------------------------------------- 1 | from hidparser.enums import CollectionType, ReportType, ReportFlags, UnitSystem 2 | from hidparser.UsagePage import UsagePage, Usage, UsageType 3 | from hidparser.helper import ValueRange 4 | 5 | from copy import copy as _copy 6 | from functools import partial as _partial 7 | from bitstring import BitArray as _BitArray, Bits as _Bits 8 | 9 | 10 | class Unit: 11 | """ 12 | Unit for Report values. 13 | 14 | Attributes: 15 | system (UnitSystem): The system used when interpreting the 16 | """ 17 | NOT_SPECIFIED = 0x00 18 | LUX = 0x010000E1 19 | KELVIN = 0x00010001 20 | FAHRENHEIT = 0x00010003 21 | PASCAL = 0xE1F1 22 | NEWTON = 0xE111 23 | METERS_PER_SECOND = 0xF011 24 | METERS_PER_SEC_SQRD = 0xE011 25 | FARAD = 0x00204FE1 26 | AMPERE = 0x00100001 27 | WATT = 0xD121 28 | HENRY = 0x00E0E121 29 | OHM = 0x00E0D121 30 | VOLT = 0x00F0D121 31 | HERTZ = 0xF001 32 | DEGREES = 0x14 33 | DEGREES_PER_SECOND = 0xF014 34 | DEGREES_PER_SEC_SQRD = 0xE014 35 | RADIANS = 0x12 36 | RADIANS_PER_SECOND = 0xF012 37 | RADIANS_PER_SEC_SQRD = 0xE012 38 | SECOND = 0x1001 39 | GAUSS = 0x00F0E101 40 | GRAM = 0x0101 41 | CENTIMETER = 0x11 42 | 43 | _map_nibble_exponent = { 44 | 0x0: 0, 0x1:1, 0x2: 2, 0x3: 3, 0x4: 4, 0x5: 5, 0x6: 6, 0x7: 7, 45 | 0x8: -8, 0x9: -7, 0xA: -6, 0xB: -5, 0xC: -4, 0xD: -3, 0xE: -2, 0xF: -1 46 | } 47 | 48 | def __init__(self): 49 | self.system = UnitSystem.NONE 50 | self.length = 0 51 | self.mass = 0 52 | self.time = 0 53 | self.temperature = 0 54 | self.current = 0 55 | self.luminosity = 0 56 | 57 | @classmethod 58 | def from_bytes(cls, data: bytes): 59 | unit = Unit() 60 | unit.system = UnitSystem(data[0]&0x0F) 61 | unit.length = cls._map_nibble_exponent[(data[0] & 0xF0) >> 4] 62 | if len(data) > 1: 63 | unit.mass = cls._map_nibble_exponent[data[1] & 0x0F] 64 | unit.time = cls._map_nibble_exponent[(data[1] & 0xF0) >> 4] 65 | if len(data) > 2: 66 | unit.temperature = cls._map_nibble_exponent[data[2] & 0x0F] 67 | unit.current = cls._map_nibble_exponent[(data[2] & 0xF0) >> 4] 68 | if len(data) > 3: 69 | unit.luminosity = cls._map_nibble_exponent[data[3] & 0x0F] 70 | 71 | 72 | class Report: 73 | def __init__( 74 | self, 75 | report_type: ReportType, 76 | report_id: int = 0, 77 | usages=[], 78 | designators=None, 79 | strings=None, 80 | size: int=0, 81 | count: int=0, 82 | logical_range=None, 83 | physical_range=None, 84 | unit=None, 85 | exponent=1, 86 | flags=None, 87 | parent=None 88 | ): 89 | self.report_id = report_id 90 | self.report_type = report_type 91 | self.size = size 92 | self.count = count 93 | self.designators = designators if designators is not None else range(0) 94 | self.strings = strings if strings is not None else range(0) 95 | if type(logical_range) in (list, tuple): 96 | logical_range = ValueRange(*logical_range) 97 | if type(physical_range) in (list, tuple): 98 | physical_range = ValueRange(*physical_range) 99 | self.logical_range = logical_range if logical_range is not None else ValueRange() # type: ValueRange 100 | self.physical_range = physical_range if physical_range is not None else _copy(self.logical_range) # type: ValueRange 101 | 102 | self.unit = unit if unit is not None else Unit() 103 | self.unit_exponent = exponent 104 | 105 | if type(usages) not in (list, tuple): 106 | usages = (usages,) 107 | self.usages = usages 108 | 109 | self.flags = flags 110 | 111 | self.parent = parent 112 | self._values = [0]*self.count if self.count>0 else [0] 113 | 114 | @property 115 | def bits(self): 116 | return self.size * self.count 117 | 118 | @property 119 | def value(self): 120 | if self.count>1: 121 | if self.logical_range == self.physical_range: 122 | return [int(value) for value in self._values] 123 | return self._values 124 | if self.logical_range == self.physical_range: 125 | return int(self._values[0]) 126 | return self._values[0] 127 | 128 | @value.setter 129 | def value(self, value): 130 | if self.count > 1: 131 | if type(value) is not list: 132 | raise ValueError("Can not set {} to {}".format(type(value), self.__class__.__name__)) 133 | if len(value) != self.count: 134 | raise ValueError("Value must be of length {}".format(self.count)) 135 | self._values = value 136 | else: 137 | if not self.physical_range.in_range(value): 138 | raise ArithmeticError("{} is not within physical range".format(value)) 139 | self._values[0] = value 140 | 141 | def __getitem__(self, key): 142 | if self.logical_range == self.physical_range: 143 | return int(self._values[key]) 144 | return self._values[key] 145 | 146 | def __setitem__(self, key, value): 147 | self._values[key] = value 148 | 149 | # TODO print out more meaningful information about this Report 150 | def __str__(self, index=0): 151 | result = "{}Report (size: {}, count: {})\n{}".format(" " * index, self.size, self.count," " * (index + 1)) 152 | if self.flags & ReportFlags.CONSTANT: 153 | return result + "(Constant)" 154 | return result + ", ".join([usage._name_ for usage in self.usages]) 155 | 156 | def pack(self): 157 | values = _BitArray(self.count*self.size) 158 | for i in range(self.count): 159 | offset = i * self.size 160 | try: 161 | values[offset:offset + self.size] = int(self.physical_range.scale_to(self.logical_range, self._values[i])) 162 | except ArithmeticError: 163 | # If the value is outside of the physical range, and NULLs are allowed, then do not modify the value 164 | if not self.flags & ReportFlags.NULL_STATE: 165 | raise 166 | return values 167 | 168 | def unpack(self, data): 169 | if not isinstance(data, _Bits): 170 | data = _Bits(data) 171 | for i in range(self.count): 172 | offset = i*self.size 173 | try: 174 | b = data[offset:offset + self.size] 175 | self._values[i] = self.logical_range.scale_to(self.physical_range, b.int if self.logical_range.minimum<0 else b.uint) 176 | except ArithmeticError: 177 | # If the value is outside of the logical range, and NULLs are allowed, then do not modify the value 178 | if not self.flags & ReportFlags.NULL_STATE: 179 | raise 180 | 181 | 182 | class Collection: 183 | def __init__(self, items=None, usage=None, allowed_usage_types=None, collection_type: CollectionType=None, parent: "Collection"=None): 184 | if allowed_usage_types is None: 185 | allowed_usage_types = UsageType.collection_usage_types() 186 | if isinstance(allowed_usage_types, UsageType): 187 | allowed_usage_types = (allowed_usage_types,) 188 | elif type(allowed_usage_types) not in (list, tuple): 189 | raise ValueError("usage types must be a UsageType or a list or tuple of UsageType") 190 | 191 | self.collection_type = collection_type 192 | self._usage_types = allowed_usage_types 193 | self._usage = usage 194 | self.items = [] # type: _List[_Union[Collection, Report]] 195 | self._attrs = {} 196 | 197 | # _parent either refers to the collection it's nested in, or the collection it's derrived from 198 | # i.e. collections in ReportGroup.input are derrived from the collections in the Device object 199 | self.parent = parent 200 | 201 | if items is not None: 202 | if type(items) not in (list, tuple): 203 | items = [items] 204 | for item in items: 205 | self.append(item) 206 | 207 | @property 208 | def bits(self): 209 | # TODO Cache the total bit size, and invalidate when a child Collection or Report is added somewhere in the tree 210 | return sum([item.bits for item in self.items]) 211 | 212 | def get_bit_size(self, report_type: ReportType): 213 | size = 0 214 | for item in self.items: 215 | if isinstance(item, Report): 216 | if item.report_type == report_type: 217 | size += item.bits 218 | else: 219 | size += item.get_bit_size(report_type) 220 | return size 221 | 222 | def get_size(self, report_type: ReportType): 223 | bits = self.get_bit_size(report_type) 224 | return int((bits - 1) / 8) + 1 225 | 226 | def deserialize(self, data, report_type: ReportType): 227 | offset = 0 228 | if not isinstance(data, _Bits): 229 | data = _Bits(data) 230 | for item in self.items: 231 | if isinstance(item, Report): 232 | if item.report_type is not report_type: 233 | continue 234 | item.unpack(data[offset:offset + item.bits]) 235 | else: 236 | item.deserialize(data[offset:offset + item.bits], report_type) 237 | offset += item.bits 238 | 239 | def serialize(self, report_type: ReportType) -> bytes: 240 | data = _BitArray() 241 | for item in self.items: 242 | if isinstance(item, Report): 243 | if item.report_type is not report_type: 244 | continue 245 | data.append(item.pack()) 246 | else: 247 | data.append(item.serialize(report_type)) 248 | return data 249 | 250 | def append(self, item): 251 | if isinstance(item, Collection): 252 | self.items.append(item) 253 | if item._usage is not None: 254 | self._add_to_attr(item._usage._name_.lower(), item) 255 | elif isinstance(item, UsagePage): 256 | if not [usage_type for usage_type in item.usage_types if usage_type in self._usage_types]: 257 | raise ValueError() 258 | collection = Collection(usage=item) 259 | self.items.append(collection) 260 | self._add_to_attr(item._usage._name_.lower(), collection) 261 | elif isinstance(item, Report): 262 | if len(item.usages)>0: 263 | for usage in item.usages: 264 | self._add_to_attr(usage._name_.lower(), property( 265 | fget=_partial(item.__getitem__, item.usages.index(usage)), 266 | fset=_partial(item.__setitem__, item.usages.index(usage)) 267 | )) 268 | self.items.append(item) 269 | else: 270 | raise ValueError("usage type is not UsagePage or Report") 271 | 272 | def _add_to_attr(self, key, item): 273 | if key in self._attrs.keys(): 274 | if type(self._attrs[key]) is not list: 275 | self._attrs[key] = [self._attrs[key]] 276 | self._attrs[key].append(item) 277 | else: 278 | self._attrs[key] = item 279 | 280 | def extend(self, items): 281 | for item in items: 282 | self.append(item) 283 | 284 | def __getitem__(self, item) -> "Collection": 285 | return self.items[item] 286 | 287 | def __getattr__(self, item) -> "Collection": 288 | try: 289 | value = self._attrs[item] 290 | if isinstance(value, property): 291 | return value.fget() 292 | return value 293 | except KeyError: 294 | raise AttributeError() 295 | 296 | def __iter__(self): 297 | return iter(self.items) 298 | 299 | def __cmp__(self, other): 300 | if isinstance(other, UsagePage): 301 | return self._usage is other 302 | return super(Collection, self).__cmp__(other) 303 | 304 | # TODO print out more meaningful information about this Collection 305 | def __str__(self, index=0): 306 | result = "{}Collection {}{}".format(" " * index * 2, self.collection_type._name_, (" ("+self._usage._name_+")") if self._usage is not None else "") 307 | for item in self.items: 308 | result += "\n"+item.__str__(index+1) 309 | return result 310 | 311 | 312 | class ReportGroup: 313 | def __init__(self): 314 | self._inputs = Collection(allowed_usage_types=(UsageType.COLLECTION_APPLICATION,)) 315 | self._input_size = None 316 | self._outputs = Collection(allowed_usage_types=(UsageType.COLLECTION_APPLICATION,)) 317 | self._output_size = None 318 | self._features = Collection(allowed_usage_types=(UsageType.COLLECTION_APPLICATION,)) 319 | self._feature_size = None 320 | 321 | @property 322 | def inputs(self) -> Collection: 323 | return self._inputs 324 | 325 | @property 326 | def input_size(self) -> int: 327 | if self._input_size is None: 328 | self._input_size = self._inputs.get_size(ReportType.INPUT) 329 | return self._input_size 330 | 331 | @property 332 | def outputs(self) -> Collection: 333 | return self._outputs 334 | 335 | @property 336 | def output_size(self) -> int: 337 | if self._output_size is None: 338 | self._output_size = self._inputs.get_size(ReportType.OUTPUT) 339 | return self._output_size 340 | 341 | @property 342 | def features(self) -> Collection: 343 | return self._features 344 | 345 | @property 346 | def feature_size(self) -> int: 347 | if self._feature_size is None: 348 | self._feature_size = self._inputs.get_size(ReportType.FEATURE) 349 | return self._feature_size 350 | 351 | 352 | class Device: 353 | def deserialize(self, data: bytes, report_type: ReportType=None): 354 | """ 355 | Deserialises the raw HID data received from the device. 356 | If ReportIDs are used, then the first byte must be the ReportID 357 | :param data: 358 | :param report_type: 359 | :return: None 360 | """ 361 | report = 0 362 | if len(self._reports) == 0: 363 | raise ValueError("No reports have been created for {}".format(self.__class__.__name__)) 364 | 365 | if report_type is None: 366 | report_type = ReportType.INPUT 367 | if 0 not in self._reports.keys(): 368 | report = data[0] 369 | data = data[1:] 370 | 371 | if report_type is ReportType.INPUT: 372 | if self._reports[report].input_size != len(data): 373 | raise ValueError("data({}) does not match input_size({})".format(len(data),self._reports[report].input_size)) 374 | self._reports[report].inputs.deserialize(data, report_type) 375 | if report_type is ReportType.OUTPUT: 376 | if self._reports[report].output_size != len(data): 377 | raise ValueError("data({}) does not match output_size({})".format(len(data)),self._reports[report].output_size) 378 | self._reports[report].outputs.deserialize(data, report_type) 379 | if report_type is ReportType.FEATURE: 380 | if self._reports[report].feature_size != len(data): 381 | raise ValueError("data({}) does not match feature_size({})".format(len(data),self._reports[report].feature_size)) 382 | self._reports[report].features.deserialize(data, report_type) 383 | 384 | def serialize(self, report: int = 0, report_type: ReportType=None) -> bytes: 385 | """ 386 | Serialises the reports into a bytes object. If ReportIDs are used, then report must be specified 387 | :param report: The ReportID to serialise. Default: 0 388 | :param report_type: The report type to serialise. Default: ReportType.OUTPUT 389 | :return: bytes 390 | """ 391 | if report_type is None: 392 | report_type = ReportType.OUTPUT 393 | if len(self._reports) == 0: 394 | raise ValueError("No reports have been created for {}".format(self.__class__.__name__)) 395 | 396 | data = None 397 | if report_type is ReportType.INPUT: 398 | data = self._reports[report].inputs.serialize(report_type) 399 | if report_type is ReportType.OUTPUT: 400 | data = self._reports[report].outputs.serialize(report_type) 401 | if report_type is ReportType.FEATURE: 402 | data = self._reports[report].features.serialize(report_type) 403 | 404 | # Prepend the ReportID if not 0 405 | if report > 0: 406 | data.prepend(bytes([report])) 407 | return data.bytes() 408 | 409 | def __init__(self, collection=None): 410 | self._reports = {} # type: _Dict[int, ReportGroup] 411 | self._collection = Collection(items=collection, allowed_usage_types=UsageType.COLLECTION_APPLICATION) 412 | # Create ReportGroups from the Report IDs found in the master Collection 413 | self._populate_report_types(self._collection) 414 | 415 | @property 416 | def reports(self): 417 | """ 418 | Returns a dictionary that maps report ids to collections 419 | :return: 420 | """ 421 | return self._reports 422 | 423 | @property 424 | def all(self): 425 | """ 426 | Returns the root collection, not bound to any report group 427 | :return: 428 | """ 429 | return self._collection 430 | 431 | def _populate_report_types(self, collection: Collection, path=None): 432 | if path is None: 433 | path = [] 434 | 435 | for item in collection.items: 436 | if isinstance(item, Collection): 437 | path.append(item) 438 | self._populate_report_types(item, path.copy()) 439 | path.pop() 440 | continue 441 | 442 | # assume the item is a Report 443 | 444 | # Create a ReportGroup on a new Report ID 445 | if item.report_id not in self._reports.keys(): 446 | self._reports[item.report_id] = ReportGroup() 447 | 448 | if item.report_type is ReportType.INPUT: 449 | self._collection_add_report( 450 | self._reports[item.report_id].inputs, 451 | path.copy(), 452 | item 453 | ) 454 | elif item.report_type is ReportType.OUTPUT: 455 | self._collection_add_report( 456 | self._reports[item.report_id].outputs, 457 | path.copy(), 458 | item 459 | ) 460 | elif item.report_type is ReportType.FEATURE: 461 | self._collection_add_report( 462 | self._reports[item.report_id].features, 463 | path.copy(), 464 | item 465 | ) 466 | 467 | def _collection_add_report(self, collection: Collection, path, report: Report): 468 | while len(path)>0: 469 | target = path.pop(0) 470 | try: 471 | collection = [item for item in collection.items if item.parent == target][0] 472 | continue 473 | except IndexError: 474 | break 475 | while len(path) >= 0 and collection.parent != target: 476 | # Create a derrived Collection 477 | new_collection = Collection( 478 | usage=target._usage, 479 | allowed_usage_types=target._usage_types, 480 | collection_type=target.collection_type, 481 | parent=target 482 | ) 483 | collection.append(new_collection) 484 | collection = new_collection 485 | if len(path)>0: 486 | target = path.pop(0) 487 | 488 | collection.append(report) 489 | 490 | def __str__(self, index = 0): 491 | result = "Device:" 492 | for report_id in self._reports.keys(): 493 | result += "\n Report 0x{:02X}:".format(report_id) 494 | if len(self._reports[report_id].inputs.items): 495 | result += "\n Inputs" 496 | for collection in self._reports[report_id].inputs: 497 | result += "\n" + collection.__str__(4) 498 | if len(self._reports[report_id].outputs.items): 499 | result += "\n Outputs" 500 | for collection in self._reports[report_id].outputs: 501 | result += "\n" + collection.__str__(4) 502 | if len(self._reports[report_id].features.items): 503 | result += "\n Features" 504 | for collection in self._reports[report_id].features: 505 | result += "\n" + collection.__str__(4) 506 | return result 507 | -------------------------------------------------------------------------------- /hidparser/UsagePages/Sensor.py: -------------------------------------------------------------------------------- 1 | from hidparser.UsagePage import Usage, UsageType, UsagePage 2 | 3 | 4 | #TODO Support modifiers as defined in HUTRR39b 5 | # data type usage switches -- we use them as modifiers for sensor properties & data fields 6 | # to create thresholds, for example. 7 | # NOTE: the usage tables actually define these as two bytes, but in order 8 | # to get the define macros to work so these are ‘or-ed’ these are defined 9 | # here as only one byte. 10 | # HID_USAGE_SENSOR_DATA_MOD_NONE = 0x00 # US 11 | # HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_ABS = 0x10 # US 12 | # HID_USAGE_SENSOR_DATA_MOD_MAX = 0x20 # US 13 | # HID_USAGE_SENSOR_DATA_MOD_MIN = 0x30 # US 14 | # HID_USAGE_SENSOR_DATA_MOD_ACCURACY = 0x40 # US 15 | # HID_USAGE_SENSOR_DATA_MOD_RESOLUTION = 0x50 # US 16 | # HID_USAGE_SENSOR_DATA_MOD_THRESHOLD_HIGH = 0x60 # US 17 | # 18 | # HID_USAGE_SENSOR_DATA_MOD_THRESHOLD_LOW = 0x70 # US 19 | # HID_USAGE_SENSOR_DATA_MOD_CALIBRATION_OFFSET = 0x80 # US 20 | # HID_USAGE_SENSOR_DATA_MOD_CALIBRATION_MULTIPLIER = 0x90 # US 21 | # HID_USAGE_SENSOR_DATA_MOD_REPORT_INTERVAL = 0xA0 # US 22 | # HID_USAGE_SENSOR_DATA_MOD_FREQUENCY_MAX = 0xB0 # US 23 | # HID_USAGE_SENSOR_DATA_MOD_PERIOD_MAX = 0xC0 # US 24 | # HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_RANGE_PCT = 0xD0 # US 25 | # HID_USAGE_SENSOR_DATA_MOD_CHANGE_SENSITIVITY_REL_PCT = 0xE0 # US 26 | # HID_USAGE_SENSOR_DATA_MOD_VENDOR_RESERVED = 0xF0 # US 27 | 28 | class Sensor(UsagePage): 29 | @classmethod 30 | def _get_usage_page_index(cls): 31 | return 0x20 32 | 33 | # sensor category usages 34 | SENSOR = Usage(0x01, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 35 | # sensor category biometric 36 | BIOMETRIC = Usage(0x10, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 37 | BIOMETRIC_PRESENCE = Usage(0x11, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 38 | BIOMETRIC_PROXIMITY = Usage(0x12, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 39 | BIOMETRIC_TOUCH = Usage(0x13, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 40 | # sensor category electrical 41 | ELECTRICAL = Usage(0x20, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 42 | ELECTRICAL_CAPACITANCE = Usage(0x21, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 43 | ELECTRICAL_CURRENT = Usage(0x22, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 44 | ELECTRICAL_POWER = Usage(0x23, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 45 | ELECTRICAL_INDUCTANCE = Usage(0x24, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 46 | ELECTRICAL_RESISTANCE = Usage(0x25, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 47 | ELECTRICAL_VOLTAGE = Usage(0x26, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 48 | ELECTRICAL_POTENTIOMETER = Usage(0x27, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 49 | ELECTRICAL_FREQUENCY = Usage(0x28, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 50 | ELECTRICAL_PERIOD = Usage(0x29, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 51 | # sensor category environmental 52 | ENVIRONMENTAL = Usage(0x30, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 53 | ENVIRONMENTAL_ATMOSPHERIC_PRESSURE = Usage(0x31, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 54 | ENVIRONMENTAL_HUMIDITY = Usage(0x32, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 55 | ENVIRONMENTAL_TEMPERATURE = Usage(0x33, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 56 | ENVIRONMENTAL_WIND_DIRECTION = Usage(0x34, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 57 | ENVIRONMENTAL_WIND_SPEED = Usage(0x35, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 58 | # sensor category light 59 | LIGHT = Usage(0x40, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 60 | LIGHT_AMBIENTLIGHT = Usage(0x41, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 61 | LIGHT_CONSUMER_INFRARED = Usage(0x42, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 62 | # sensor category location 63 | LOCATION = Usage(0x50, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 64 | LOCATION_BROADCAST = Usage(0x51, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 65 | LOCATION_DEAD_RECKONING = Usage(0x52, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 66 | LOCATION_GPS = Usage(0x53, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 67 | LOCATION_LOOKUP = Usage(0x54, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 68 | LOCATION_OTHER = Usage(0x55, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 69 | LOCATION_STATIC = Usage(0x56, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 70 | LOCATION_TRIANGULATION = Usage(0x57, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 71 | # sensor category mechanical 72 | MECHANICAL = Usage(0x60, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 73 | MECHANICAL_BOOLEAN_SWITCH = Usage(0x61, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 74 | MECHANICAL_BOOLEAN_SWITCH_ARRAY = Usage(0x62, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 75 | MECHANICAL_MULTIVALUE_SWITCH = Usage(0x63, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 76 | MECHANICAL_FORCE = Usage(0x64, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 77 | MECHANICAL_PRESSURE = Usage(0x65, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 78 | MECHANICAL_STRAIN = Usage(0x66, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 79 | 80 | MECHANICAL_SCALE_WEIGHT = Usage(0x67, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 81 | MECHANICAL_VIBRATOR = Usage(0x68, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 82 | MECHANICAL_HALL_EFFECT_SWITCH = Usage(0x69, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 83 | # sensor category motion 84 | MOTION = Usage(0x70, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 85 | MOTION_ACCELEROMETER_1D = Usage(0x71, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 86 | MOTION_ACCELEROMETER_2D = Usage(0x72, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 87 | MOTION_ACCELEROMETER_3D = Usage(0x73, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 88 | MOTION_GYROMETER_1D = Usage(0x74, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 89 | MOTION_GYROMETER_2D = Usage(0x75, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 90 | MOTION_GYROMETER_3D = Usage(0x76, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 91 | MOTION_MOTION_DETECTOR = Usage(0x77, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 92 | MOTION_SPEEDOMETER = Usage(0x78, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 93 | MOTION_ACCELEROMETER = Usage(0x79, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 94 | MOTION_GYROMETER = Usage(0x7A, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 95 | # sensor category orientation 96 | ORIENTATION = Usage(0x80, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 97 | ORIENTATION_COMPASS_1D = Usage(0x81, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 98 | ORIENTATION_COMPASS_2D = Usage(0x82, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 99 | ORIENTATION_COMPASS_3D = Usage(0x83, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 100 | ORIENTATION_INCLINOMETER_1D = Usage(0x84, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 101 | ORIENTATION_INCLINOMETER_2D = Usage(0x85, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 102 | ORIENTATION_INCLINOMETER_3D = Usage(0x86, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 103 | ORIENTATION_DISTANCE_1D = Usage(0x87, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 104 | ORIENTATION_DISTANCE_2D = Usage(0x88, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 105 | ORIENTATION_DISTANCE_3D = Usage(0x89, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 106 | ORIENTATION_DEVICE_ORIENTATION = Usage(0x8A, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 107 | ORIENTATION_COMPASS = Usage(0x8B, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 108 | ORIENTATION_INCLINOMETER = Usage(0x8C, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 109 | ORIENTATION_DISTANCE = Usage(0x8D, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 110 | # sensor category scanner 111 | SCANNER = Usage(0x90, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 112 | SCANNER_BARCODE = Usage(0x91, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 113 | SCANNER_RFID = Usage(0x92, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 114 | SCANNER_NFC = Usage(0x93, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 115 | # sensor category time 116 | TIME = Usage(0xA0, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 117 | TIME_ALARM = Usage(0xA1, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 118 | TIME_RTC = Usage(0xA2, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 119 | # sensor category other 120 | OTHER = Usage(0xE0, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 121 | OTHER_CUSTOM = Usage(0xE1, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 122 | OTHER_GENERIC = Usage(0xE2, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 123 | OTHER_GENERIC_ENUMERATOR = Usage(0xE3, [UsageType.COLLECTION_APPLICATION, UsageType.COLLECTION_PHYSICAL]) 124 | 125 | # state usages 126 | STATE = Usage(0x0201, UsageType.COLLECTION_NAMED_ARRAY) 127 | # state selectors 128 | STATE_UNKNOWN = Usage(0x0800, UsageType.DATA_SELECTOR) 129 | STATE_READY = Usage(0x0801, UsageType.DATA_SELECTOR) 130 | STATE_NOT_AVAILABLE = Usage(0x0802, UsageType.DATA_SELECTOR) 131 | STATE_NO_DATA = Usage(0x0803, UsageType.DATA_SELECTOR) 132 | STATE_INITIALIZING = Usage(0x0804, UsageType.DATA_SELECTOR) 133 | STATE_ACCESS_DENIED = Usage(0x0805, UsageType.DATA_SELECTOR) 134 | STATE_ERROR = Usage(0x0806, UsageType.DATA_SELECTOR) 135 | # event usages 136 | EVENT = Usage(0x0202, UsageType.COLLECTION_NAMED_ARRAY) 137 | # event selectors 138 | EVENT_UNKNOWN = Usage(0x0810, UsageType.DATA_SELECTOR) 139 | EVENT_STATE_CHANGED = Usage(0x0811, UsageType.DATA_SELECTOR) 140 | EVENT_PROPERTY_CHANGED = Usage(0x0812, UsageType.DATA_SELECTOR) 141 | EVENT_DATA_UPDATED = Usage(0x0813, UsageType.DATA_SELECTOR) 142 | EVENT_POLL_RESPONSE = Usage(0x0814, UsageType.DATA_SELECTOR) 143 | EVENT_CHANGE_SENSITIVITY = Usage(0x0815, UsageType.DATA_SELECTOR) 144 | EVENT_MAX_REACHED = Usage(0x0816, UsageType.DATA_SELECTOR) 145 | EVENT_MIN_REACHED = Usage(0x0817, UsageType.DATA_SELECTOR) 146 | EVENT_HIGH_THRESHOLD_CROSS_UPWARD = Usage(0x0818, UsageType.DATA_SELECTOR) 147 | EVENT_HIGH_THESHOLD_CROSS_ABOVE = EVENT_HIGH_THRESHOLD_CROSS_UPWARD 148 | EVENT_HIGH_THRESHOLD_CROSS_DOWNWARD = Usage(0x0819, UsageType.DATA_SELECTOR) 149 | EVENT_HIGH_THRESHOLD_CROSS_BELOW = EVENT_HIGH_THRESHOLD_CROSS_DOWNWARD 150 | EVENT_LOW_THRESHOLD_CROSS_UPWARD = Usage(0x081A, UsageType.DATA_SELECTOR) 151 | EVENT_LOW_THRESHOLD_CROSS_ABOVE = EVENT_LOW_THRESHOLD_CROSS_UPWARD 152 | EVENT_LOW_THRESHOLD_CROSS_DOWNWARD = Usage(0x081B, UsageType.DATA_SELECTOR) 153 | EVENT_LOW_THRESHOLD_CROSS_BELOW = EVENT_LOW_THRESHOLD_CROSS_DOWNWARD 154 | EVENT_ZERO_THRESHOLD_CROSS_UPWARD = Usage(0x081C, UsageType.DATA_SELECTOR) 155 | EVENT_ZERO_THRESHOLD_CROSS_ABOVE = EVENT_ZERO_THRESHOLD_CROSS_UPWARD 156 | EVENT_ZERO_THRESHOLD_CROSS_DOWNWARD = Usage(0x081D, UsageType.DATA_SELECTOR) 157 | EVENT_ZERO_THRESHOLD_CROSS_BELOW = EVENT_ZERO_THRESHOLD_CROSS_DOWNWARD 158 | EVENT_PERIOD_EXCEEDED = Usage(0x081E, UsageType.DATA_SELECTOR) 159 | EVENT_FREQUENCY_EXCEEDED = Usage(0x081F, UsageType.DATA_SELECTOR) 160 | EVENT_COMPLEX_TRIGGER = Usage(0x0820, UsageType.DATA_SELECTOR) 161 | # property usages (get/set feature report) 162 | PROPERTY = Usage(0x0300, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 163 | PROPERTY_FRIENDLY_NAME = Usage(0x0301, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 164 | PROPERTY_PERSISTENT_UNIQUE_ID = Usage(0x0302, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 165 | PROPERTY_SENSOR_STATUS = Usage(0x0303, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 166 | PROPERTY_MINIMUM_REPORT_INTERVAL = Usage(0x0304, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 167 | PROPERTY_SENSOR_MANUFACTURER = Usage(0x0305, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 168 | PROPERTY_SENSOR_MODEL = Usage(0x0306, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 169 | PROPERTY_SENSOR_SERIAL_NUMBER = Usage(0x0307, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 170 | PROPERTY_SENSOR_DESCRIPTION = Usage(0x0308, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 171 | PROPERTY_SENSOR_CONNECTION_TYPE = Usage(0x0309, UsageType.COLLECTION_NAMED_ARRAY) 172 | # begin connection type selectors 173 | PROPERTY_CONNECTION_TYPE_PC_INTEGRATED = Usage(0x0830, UsageType.DATA_SELECTOR) 174 | PROPERTY_CONNECTION_TYPE_PC_ATTACHED = Usage(0x0831, UsageType.DATA_SELECTOR) 175 | PROPERTY_CONNECTION_TYPE_PC_EXTERNAL = Usage(0x0832, UsageType.DATA_SELECTOR) 176 | # end connection type selectors 177 | PROPERTY_SENSOR_DEVICE_PATH = Usage(0x030A, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 178 | PROPERTY_HARDWARE_REVISION = Usage(0x030B, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 179 | PROPERTY_FIRMWARE_VERSION = Usage(0x030C, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 180 | PROPERTY_RELEASE_DATE = Usage(0x030D, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 181 | PROPERTY_REPORT_INTERVAL = Usage(0x030E, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 182 | PROPERTY_CHANGE_SENSITIVITY_ABS = Usage(0x030F, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 183 | PROPERTY_CHANGE_SENSITIVITY_RANGE_PCT = Usage(0x0310, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 184 | PROPERTY_CHANGE_SENSITIVITY_REL_PCT = Usage(0x0311, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 185 | PROPERTY_ACCURACY = Usage(0x0312, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 186 | PROPERTY_RESOLUTION = Usage(0x0313, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 187 | PROPERTY_RANGE_MAXIMUM = Usage(0x0314, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 188 | PROPERTY_RANGE_MINIMUM = Usage(0x0315, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 189 | PROPERTY_REPORTING_STATE = Usage(0x0316, UsageType.COLLECTION_NAMED_ARRAY) 190 | # begin reporting state selectors 191 | PROPERTY_REPORTING_STATE_NO_EVENTS = Usage(0x0840, UsageType.DATA_SELECTOR) 192 | HID_USAGE_REPORTING_STATE_ON_NONE = PROPERTY_REPORTING_STATE_NO_EVENTS 193 | PROPERTY_REPORTING_STATE_ALL_EVENTS = Usage(0x0841, UsageType.DATA_SELECTOR) 194 | HID_USAGE_REPORTING_STATE_ON_ALL = PROPERTY_REPORTING_STATE_ALL_EVENTS 195 | PROPERTY_REPORTING_STATE_THRESHOLD_EVENTS = Usage(0x0842, UsageType.DATA_SELECTOR) 196 | HID_USAGE_REPORTING_STATE_ON_THRESHOLD = PROPERTY_REPORTING_STATE_THRESHOLD_EVENTS 197 | PROPERTY_REPORTING_STATE_NO_EVENTS_WAKE = Usage(0x0843, UsageType.DATA_SELECTOR) 198 | PROPERTY_REPORTING_STATE_ALL_EVENTS_WAKE = Usage(0x0844, UsageType.DATA_SELECTOR) 199 | PROPERTY_REPORTING_STATE_THRESHOLD_EVENTS_WAKE = Usage(0x0845, UsageType.DATA_SELECTOR) 200 | # end reporting state selectors 201 | PROPERTY_SAMPLING_RATE = Usage(0x0317, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 202 | PROPERTY_RESPONSE_CURVE = Usage(0x0318, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 203 | PROPERTY_POWER_STATE = Usage(0x0319, UsageType.COLLECTION_NAMED_ARRAY) 204 | # begin power state selectors 205 | PROPERTY_POWER_STATE_UNDEFINED = Usage(0x0850, UsageType.DATA_SELECTOR) 206 | PROPERTY_POWER_STATE_D0_FULL_POWER = Usage(0x0851, UsageType.DATA_SELECTOR) 207 | PROPERTY_POWER_STATE_D1_LOW_POWER = Usage(0x0852, UsageType.DATA_SELECTOR) 208 | 209 | PROPERTY_POWER_STATE_D2_STANDBY_WITH_WAKE = Usage(0x0853, UsageType.DATA_SELECTOR) 210 | PROPERTY_POWER_STATE_D3_SLEEP_WITH_WAKE = Usage(0x0854, UsageType.DATA_SELECTOR) 211 | PROPERTY_POWER_STATE_D4_POWER_OFF = Usage(0x0855, UsageType.DATA_SELECTOR) 212 | # end power state selectors 213 | # data type location 214 | # data field usages (input report 215 | DATA_LOCATION = Usage(0x0400, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 216 | DATA_LOCATION_DESIRED_ACCURACY = Usage(0x0401, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 217 | DATA_LOCATION_ALTITUDE_ANTENNA_SEALEVEL = Usage(0x0402, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 218 | DATA_LOCATION_DIFFERENTIAL_REFERENCE_STATION_ID = Usage(0x0403, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 219 | DATA_LOCATION_ALTITIDE_ELIPSOID_ERROR = Usage(0x0404, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 220 | DATA_LOCATION_ALTITIDE_ELIPSOID = Usage(0x0405, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 221 | DATA_LOCATION_ALTITUDE_SEALEVEL_ERROR = Usage(0x0406, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 222 | DATA_LOCATION_ALTITUDE_SEALEVEL = Usage(0x0407, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 223 | DATA_LOCATION_DGPS_DATA_AGE = Usage(0x0408, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 224 | DATA_LOCATION_ERROR_RADIUS = Usage(0x0409, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 225 | DATA_LOCATION_FIX_QUALITY = Usage(0x040A, UsageType.COLLECTION_NAMED_ARRAY) 226 | # begin fix quality selectors 227 | DATA_FIX_QUALITY_NO_FIX = Usage(0x0870, UsageType.DATA_SELECTOR) 228 | DATA_FIX_QUALITY_GPS = Usage(0x0871, UsageType.DATA_SELECTOR) 229 | DATA_FIX_QUALITY_DGPS = Usage(0x0872, UsageType.DATA_SELECTOR) 230 | # end fix quality selectors 231 | DATA_LOCATION_FIX_TYPE = Usage(0x040B, UsageType.COLLECTION_NAMED_ARRAY) 232 | # begin fix type selectors 233 | DATA_FIX_TYPE_NO_FIX = Usage(0x0880, UsageType.DATA_SELECTOR) 234 | DATA_FIX_TYPE_GPS_SPS_MODE_FIX_VALID = Usage(0x0881, UsageType.DATA_SELECTOR) 235 | DATA_FIX_TYPE_DGPS_SPS_MODE_FIX_VALID = Usage(0x0882, UsageType.DATA_SELECTOR) 236 | DATA_FIX_TYPE_GPS_PPS_MODE_FIX_VALID = Usage(0x0883, UsageType.DATA_SELECTOR) 237 | DATA_FIX_TYPE_REAL_TIME_KINEMATIC = Usage(0x0884, UsageType.DATA_SELECTOR) 238 | DATA_FIX_TYPE_FLOAT_RTK = Usage(0x0885, UsageType.DATA_SELECTOR) 239 | DATA_FIX_TYPE_ESTIMATED_DEAD_RECKONING = Usage(0x0886, UsageType.DATA_SELECTOR) 240 | DATA_FIX_TYPE_MANUAL_INPUT_MODE = Usage(0x0887, UsageType.DATA_SELECTOR) 241 | DATA_FIX_TYPE_SIMULATOR_MODE = Usage(0x0888, UsageType.DATA_SELECTOR) 242 | # end fix type selectors 243 | DATA_LOCATION_GEOIDAL_SEPARATION = Usage(0x040C, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 244 | DATA_LOCATION_GPS_OPERATION_MODE = Usage(0x040D, UsageType.COLLECTION_NAMED_ARRAY) 245 | # begin gps operation mode selectors 246 | DATA_GPS_OP_MODE_MANUAL = Usage(0x0890, UsageType.DATA_SELECTOR) 247 | DATA_GPS_OP_MODE_AUTOMATIC = Usage(0x0891, UsageType.DATA_SELECTOR) 248 | # end gps operation mode selectors 249 | DATA_LOCATION_GPS_SELECTION_MODE = Usage(0x040E, UsageType.COLLECTION_NAMED_ARRAY) 250 | # begin gps selection mode selectors 251 | DATA_GPS_SEL_MODE_AUTONOMOUS = Usage(0x08A0, UsageType.DATA_SELECTOR) 252 | DATA_GPS_SEL_MODE_DGPS = Usage(0x08A1, UsageType.DATA_SELECTOR) 253 | DATA_GPS_SEL_MODE_ESTIMATED_DEAD_RECKONING = Usage(0x08A2, UsageType.DATA_SELECTOR) 254 | DATA_GPS_SEL_MODE_MANUAL_INPUT = Usage(0x08A3, UsageType.DATA_SELECTOR) 255 | DATA_GPS_SEL_MODE_SIMULATOR = Usage(0x08A4, UsageType.DATA_SELECTOR) 256 | DATA_GPS_SEL_MODE_DATA_NOT_VALID = Usage(0x08A5, UsageType.DATA_SELECTOR) 257 | # end gps selection mode selectors 258 | DATA_LOCATION_GPS_STATUS = Usage(0x040F, UsageType.COLLECTION_NAMED_ARRAY) 259 | # begin gps status selectors 260 | DATA_GPS_STATUS_DATA_VALID = Usage(0x08B0, UsageType.DATA_SELECTOR) 261 | DATA_GPS_STATUS_DATA_NOT_VALID = Usage(0x08B1, UsageType.DATA_SELECTOR) 262 | # end gps status selectors 263 | DATA_LOCATION_POSITION_DILUTION_OF_PRECISION = Usage(0x0410, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 264 | DATA_LOCATION_HORIZONTAL_DILUTION_OF_PRECISION = Usage(0x0411, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 265 | DATA_LOCATION_VERTICAL_DILUTION_OF_PRECISION = Usage(0x0412, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 266 | DATA_LOCATION_LATITUDE = Usage(0x0413, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 267 | DATA_LOCATION_LONGITUDE = Usage(0x0414, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 268 | DATA_LOCATION_TRUE_HEADING = Usage(0x0415, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 269 | DATA_LOCATION_MAGNETIC_HEADING = Usage(0x0416, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 270 | DATA_LOCATION_MAGNETIC_VARIATION = Usage(0x0417, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 271 | DATA_LOCATION_SPEED = Usage(0x0418, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 272 | DATA_LOCATION_SATELLITES_IN_VIEW = Usage(0x0419, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 273 | DATA_LOCATION_SATELLITES_IN_VIEW_AZIMUTH = Usage(0x041A, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 274 | DATA_LOCATION_SATELLITES_IN_VIEW_ELEVATION = Usage(0x041B, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 275 | DATA_LOCATION_SATELLITES_IN_VIEW_ID = Usage(0x041C, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 276 | DATA_LOCATION_SATELLITES_IN_VIEW_PRNs = Usage(0x041D, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 277 | DATA_LOCATION_SATELLITES_IN_VIEW_STN_RATIO = Usage(0x041E, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 278 | DATA_LOCATION_SATELLITES_USED_COUNT = Usage(0x041F, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 279 | DATA_LOCATION_SATELLITES_USED_PRNs = Usage(0x0420, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 280 | DATA_LOCATION_NMEA_SENTENCE = Usage(0x0421, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 281 | DATA_LOCATION_ADDRESS_LINE_1 = Usage(0x0422, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 282 | DATA_LOCATION_ADDRESS_LINE_2 = Usage(0x0423, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 283 | DATA_LOCATION_CITY = Usage(0x0424, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 284 | DATA_LOCATION_STATE_OR_PROVINCE = Usage(0x0425, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 285 | DATA_LOCATION_COUNTRY_OR_REGION = Usage(0x0426, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 286 | DATA_LOCATION_POSTAL_CODE = Usage(0x0427, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 287 | # property usages (get/set feature report) 288 | PROPERTY_LOCATION = Usage(0x042A, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 289 | PROPERTY_LOCATION_DESIRED_ACCURACY = Usage(0x042B, UsageType.COLLECTION_NAMED_ARRAY) 290 | # begin location desired accuracy selectors 291 | DESIRED_ACCURACY_DEFAULT = Usage(0x0860, UsageType.DATA_SELECTOR) 292 | DESIRED_ACCURACY_HIGH = Usage(0x0861, UsageType.DATA_SELECTOR) 293 | DESIRED_ACCURACY_MEDIUM = Usage(0x0862, UsageType.DATA_SELECTOR) 294 | DESIRED_ACCURACY_LOW = Usage(0x0863, UsageType.DATA_SELECTOR) 295 | # end location desired accuracy selectors 296 | # data type environmental 297 | # data field usages (input report) 298 | DATA_ENVIRONMENTAL = Usage(0x0430, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 299 | DATA_ENVIRONMENTAL_ATMOSPHERIC_PRESSURE = Usage(0x0431, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 300 | DATA_ENVIRONMENTAL_REFERENCE_PRESSURE = Usage(0x0432, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 301 | DATA_ENVIRONMENTAL_RELATIVE_HUMIDITY = Usage(0x0433, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 302 | DATA_ENVIRONMENTAL_TEMPERATURE = Usage(0x0434, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 303 | DATA_ENVIRONMENTAL_WIND_DIRECTION = Usage(0x0435, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 304 | DATA_ENVIRONMENTAL_WIND_SPEED = Usage(0x0436, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 305 | # property usages (get/set feature report) 306 | PROPERTY_ENVIRONMENTAL = Usage(0x0440, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 307 | PROPERTY_ENVIRONMENTAL_REFERENCE_PRESSURE = Usage(0x0441, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 308 | # data type motion 309 | # data field usages (input report) 310 | DATA_MOTION = Usage(0x0450, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 311 | DATA_MOTION_STATE = Usage(0x0451, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 312 | DATA_MOTION_ACCELERATION = Usage(0x0452, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 313 | DATA_MOTION_ACCELERATION_X_AXIS = Usage(0x0453, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 314 | DATA_MOTION_ACCELERATION_Y_AXIS = Usage(0x0454, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 315 | DATA_MOTION_ACCELERATION_Z_AXIS = Usage(0x0455, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 316 | DATA_MOTION_ANGULAR_VELOCITY = Usage(0x0456, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 317 | DATA_MOTION_ANGULAR_VELOCITY_X_AXIS = Usage(0x0457, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 318 | DATA_MOTION_ANGULAR_VELOCITY_Y_AXIS = Usage(0x0458, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 319 | DATA_MOTION_ANGULAR_VELOCITY_Z_AXIS = Usage(0x0459, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 320 | DATA_MOTION_ANGULAR_POSITION = Usage(0x045A, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 321 | DATA_MOTION_ANGULAR_POSITION_X_AXIS = Usage(0x045B, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 322 | DATA_MOTION_ANGULAR_POSITION_Y_AXIS = Usage(0x045C, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 323 | DATA_MOTION_ANGULAR_POSITION_Z_AXIS = Usage(0x045D, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 324 | DATA_MOTION_SPEED = Usage(0x045E, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 325 | DATA_MOTION_INTENSITY = Usage(0x045F, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 326 | # data type orientation 327 | # data field usages (input report) 328 | DATA_ORIENTATION = Usage(0x0470, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 329 | DATA_ORIENTATION_MAGNETIC_HEADING = Usage(0x0471, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 330 | DATA_ORIENTATION_MAGNETIC_HEADING_X = Usage(0x0472, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 331 | DATA_ORIENTATION_MAGNETIC_HEADING_Y = Usage(0x0473, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 332 | DATA_ORIENTATION_MAGNETIC_HEADING_Z = Usage(0x0474, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 333 | DATA_ORIENTATION_COMPENSATED_MAGNETIC_NORTH = Usage(0x0475, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 334 | DATA_ORIENTATION_COMPENSATED_TRUE_NORTH = Usage(0x0476, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 335 | DATA_ORIENTATION_MAGNETIC_NORTH = Usage(0x0477, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 336 | DATA_ORIENTATION_TRUE_NORTH = Usage(0x0478, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 337 | DATA_ORIENTATION_DISTANCE = Usage(0x0479, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 338 | DATA_ORIENTATION_DISTANCE_X = Usage(0x047A, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 339 | DATA_ORIENTATION_DISTANCE_Y = Usage(0x047B, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 340 | DATA_ORIENTATION_DISTANCE_Z = Usage(0x047C, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 341 | DATA_ORIENTATION_DISTANCE_OUT_OF_RANGE = Usage(0x047D, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 342 | DATA_ORIENTATION_TILT = Usage(0x047E, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 343 | DATA_ORIENTATION_TILT_X = Usage(0x047F, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 344 | DATA_ORIENTATION_TILT_Y = Usage(0x0480, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 345 | DATA_ORIENTATION_TILT_Z = Usage(0x0481, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 346 | DATA_ORIENTATION_ROTATION_MATRIX = Usage(0x0482, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 347 | DATA_ORIENTATION_QUATERNION = Usage(0x0483, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 348 | DATA_ORIENTATION_MAGNETIC_FLUX = Usage(0x0484, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 349 | DATA_ORIENTATION_MAGNETIC_FLUX_X_AXIS = Usage(0x0485, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 350 | DATA_ORIENTATION_MAGNETIC_FLUX_Y_AXIS = Usage(0x0486, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 351 | DATA_ORIENTATION_MAGNETIC_FLUX_Z_AXIS = Usage(0x0487, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 352 | # data type mechanical 353 | # data field usages (input report) 354 | DATA_MECHANICAL = Usage(0x0490, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 355 | DATA_MECHANICAL_BOOLEAN_SWITCH_STATE = Usage(0x0491, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 356 | DATA_MECHANICAL_BOOLEAN_SWITCH_ARRAY_STATES = Usage(0x0492, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 357 | DATA_MECHANICAL_MULTIVALUE_SWITCH_VALUE = Usage(0x0493, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 358 | DATA_MECHANICAL_FORCE = Usage(0x0494, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 359 | DATA_MECHANICAL_ABSOLUTE_PRESSURE = Usage(0x0495, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 360 | DATA_MECHANICAL_GAUGE_PRESSURE = Usage(0x0496, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 361 | DATA_MECHANICAL_STRAIN = Usage(0x0497, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 362 | DATA_MECHANICAL_WEIGHT = Usage(0x0498, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 363 | # property usages (get/set feature report) 364 | PROPERTY_MECHANICAL = Usage(0x04A0, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 365 | PROPERTY_MECHANICAL_VIBRATION_STATE = Usage(0x04A1, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 366 | DATA_MECHANICAL_VIBRATION_SPEED_FORWARD = Usage(0x04A2, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 367 | DATA_MECHANICAL_VIBRATION_SPEED_BACKWARD = Usage(0x04A3, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 368 | # data type biometric 369 | # data field usages (input report) 370 | DATA_BIOMETRIC = Usage(0x04B0, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 371 | DATA_BIOMETRIC_HUMAN_PRESENCE = Usage(0x04B1, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 372 | DATA_BIOMETRIC_HUMAN_PROXIMITY_RANGE = Usage(0x04B2, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 373 | DATA_BIOMETRIC_HUMAN_PROXIMITY_OUT_OF_RANGE = Usage(0x04B3, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 374 | DATA_BIOMETRIC_HUMAN_TOUCH_STATE = Usage(0x04B4, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 375 | # data type light sensor 376 | # data field usages (input report) 377 | DATA_LIGHT = Usage(0x04D0, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 378 | DATA_LIGHT_ILLUMINANCE = Usage(0x04D1, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 379 | DATA_LIGHT_COLOR_TEMPERATURE = Usage(0x04D2, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 380 | DATA_LIGHT_CHROMATICITY = Usage(0x04D3, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 381 | DATA_LIGHT_CHROMATICITY_X = Usage(0x04D4, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 382 | DATA_LIGHT_CHROMATICITY_Y = Usage(0x04D5, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 383 | DATA_LIGHT_CONSUMER_IR_SENTENCE_RECEIVE = Usage(0x04D6, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 384 | # property usages (get/set feature report) 385 | PROPERTY_LIGHT = Usage(0x04E0, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 386 | PROPERTY_LIGHT_CONSUMER_IR_SENTENCE_SEND = Usage(0x04E1, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 387 | # data type scanner 388 | # data field usages (input report) 389 | DATA_SCANNER = Usage(0x04F0, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 390 | DATA_SCANNER_RFID_TAG = Usage(0x04F1, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 391 | DATA_SCANNER_NFC_SENTENCE_RECEIVE = Usage(0x04F2, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 392 | # property usages (get/set feature report) 393 | PROPERTY_SCANNER = Usage(0x04F8, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 394 | PROPERTY_SCANNER_NFC_SENTENCE_SEND = Usage(0x04F9, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 395 | # data type electrical 396 | # data field usages (input report) 397 | DATA_ELECTRICAL = Usage(0x0500, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 398 | DATA_ELECTRICAL_CAPACITANCE = Usage(0x0501, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 399 | DATA_ELECTRICAL_CURRENT = Usage(0x0502, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 400 | DATA_ELECTRICAL_POWER = Usage(0x0503, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 401 | DATA_ELECTRICAL_INDUCTANCE = Usage(0x0504, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 402 | DATA_ELECTRICAL_RESISTANCE = Usage(0x0505, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 403 | DATA_ELECTRICAL_VOLTAGE = Usage(0x0506, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 404 | DATA_ELECTRICAL_FREQUENCY = Usage(0x0507, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 405 | DATA_ELECTRICAL_PERIOD = Usage(0x0508, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 406 | DATA_ELECTRICAL_PERCENT_OF_RANGE = Usage(0x0509, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 407 | # data type time 408 | # data field usages (input report) 409 | DATA_TIME = Usage(0x0520, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 410 | DATA_TIME_YEAR = Usage(0x0521, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 411 | DATA_TIME_MONTH = Usage(0x0522, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 412 | DATA_TIME_DAY = Usage(0x0523, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 413 | DATA_TIME_DAY_OF_WEEK = Usage(0x0524, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 414 | DATA_TIME_HOUR = Usage(0x0525, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 415 | DATA_TIME_MINUTE = Usage(0x0526, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 416 | DATA_TIME_SECOND = Usage(0x0527, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 417 | DATA_TIME_MILLISECOND = Usage(0x0528, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 418 | DATA_TIME_TIMESTAMP = Usage(0x0529, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 419 | DATA_TIME_JULIAN_DAY_OF_YEAR = Usage(0x052A, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 420 | # property usages (get/set feature report) 421 | PROPERTY_TIME = Usage(0x0530, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 422 | PROPERTY_TIME_TIME_ZONE_OFFSET_FROM_UTC = Usage(0x0531, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 423 | PROPERTY_TIME_TIME_ZONE_NAME = Usage(0x0532, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 424 | PROPERTY_TIME_DAYLIGHT_SAVINGS_TIME_OBSERVED = Usage(0x0533, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 425 | PROPERTY_TIME_TIME_TRIM_ADJUSTMENT = Usage(0x0534, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 426 | PROPERTY_TIME_ARM_ALARM = Usage(0x0535, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 427 | # data type custom 428 | # data field usages (input report) 429 | DATA_CUSTOM = Usage(0x0540, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 430 | DATA_CUSTOM_USAGE = Usage(0x0541, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 431 | DATA_CUSTOM_BOOLEAN_ARRAY = Usage(0x0542, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 432 | DATA_CUSTOM_VALUE = Usage(0x0543, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 433 | DATA_CUSTOM_VALUE_1 = Usage(0x0544, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 434 | DATA_CUSTOM_VALUE_2 = Usage(0x0545, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 435 | DATA_CUSTOM_VALUE_3 = Usage(0x0546, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 436 | DATA_CUSTOM_VALUE_4 = Usage(0x0547, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 437 | DATA_CUSTOM_VALUE_5 = Usage(0x0548, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 438 | DATA_CUSTOM_VALUE_6 = Usage(0x0549, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 439 | # data type generic 440 | # data field usages (input report) 441 | DATA_GENERIC = Usage(0x0560, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 442 | DATA_GENERIC_GUID_OR_PROPERTYKEY = Usage(0x0561, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 443 | DATA_GENERIC_CATEGORY_GUID = Usage(0x0562, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 444 | DATA_GENERIC_TYPE_GUID = Usage(0x0563, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 445 | DATA_GENERIC_EVENT_PROPERTYKEY = Usage(0x0564, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 446 | DATA_GENERIC_PROPERTY_PROPERTYKEY = Usage(0x0565, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 447 | DATA_GENERIC_DATAFIELD_PROPERTYKEY = Usage(0x0566, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 448 | DATA_GENERIC_EVENT = Usage(0x0567, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 449 | DATA_GENERIC_PROPERTY = Usage(0x0568, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 450 | DATA_GENERIC_DATAFIELD = Usage(0x0569, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 451 | DATA_ENUMERATOR_TABLE_ROW_INDEX = Usage(0x056A, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 452 | DATA_ENUMERATOR_TABLE_ROW_COUNT = Usage(0x056B, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 453 | DATA_GENERIC_GUID_OR_PROPERTYKEY_KIND = Usage(0x056C, UsageType.COLLECTION_NAMED_ARRAY) 454 | # begin GorPK kind selectors 455 | GORPK_KIND_CATEGORY = Usage(0x08D0, UsageType.DATA_SELECTOR) 456 | GORPK_KIND_TYPE = Usage(0x08D1, UsageType.DATA_SELECTOR) 457 | GORPK_KIND_EVENT = Usage(0x08D2, UsageType.DATA_SELECTOR) 458 | GORPK_KIND_PROPERTY = Usage(0x08D3, UsageType.DATA_SELECTOR) 459 | GORPK_KIND_DATAFIELD = Usage(0x08D4, UsageType.DATA_SELECTOR) 460 | # end GorPK kind selectors 461 | DATA_GENERIC_GUID = Usage(0x056D, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 462 | DATA_GENERIC_PROPERTYKEY = Usage(0x056E, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 463 | DATA_GENERIC_TOP_LEVEL_COLLECTION_ID = Usage(0x056F, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 464 | DATA_GENERIC_REPORT_ID = Usage(0x0570, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 465 | DATA_GENERIC_REPORT_ITEM_POSITION_INDEX = Usage(0x0571, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 466 | DATA_GENERIC_FIRMWARE_VARTYPE = Usage(0x0572, UsageType.COLLECTION_NAMED_ARRAY) 467 | # begin firmware vartype selectors 468 | FIRMWARE_VARTYPE_VT_NULL = Usage(0x0900, UsageType.DATA_SELECTOR) 469 | FIRMWARE_VARTYPE_VT_BOOL = Usage(0x0901, UsageType.DATA_SELECTOR) 470 | FIRMWARE_VARTYPE_VT_UI1 = Usage(0x0902, UsageType.DATA_SELECTOR) 471 | FIRMWARE_VARTYPE_VT_I1 = Usage(0x0903, UsageType.DATA_SELECTOR) 472 | FIRMWARE_VARTYPE_VT_UI2 = Usage(0x0904, UsageType.DATA_SELECTOR) 473 | FIRMWARE_VARTYPE_VT_I2 = Usage(0x0905, UsageType.DATA_SELECTOR) 474 | FIRMWARE_VARTYPE_VT_UI4 = Usage(0x0906, UsageType.DATA_SELECTOR) 475 | FIRMWARE_VARTYPE_VT_I4 = Usage(0x0907, UsageType.DATA_SELECTOR) 476 | FIRMWARE_VARTYPE_VT_UI8 = Usage(0x0908, UsageType.DATA_SELECTOR) 477 | FIRMWARE_VARTYPE_VT_I8 = Usage(0x0909, UsageType.DATA_SELECTOR) 478 | FIRMWARE_VARTYPE_VT_R4 = Usage(0x090A, UsageType.DATA_SELECTOR) 479 | FIRMWARE_VARTYPE_VT_R8 = Usage(0x090B, UsageType.DATA_SELECTOR) 480 | FIRMWARE_VARTYPE_VT_WSTR = Usage(0x090C, UsageType.DATA_SELECTOR) 481 | FIRMWARE_VARTYPE_VT_STR = Usage(0x090D, UsageType.DATA_SELECTOR) 482 | 483 | FIRMWARE_VARTYPE_VT_CLSID = Usage(0x090E, UsageType.DATA_SELECTOR) 484 | FIRMWARE_VARTYPE_VT_VECTOR_VT_UI1 = Usage(0x090F, UsageType.DATA_SELECTOR) 485 | FIRMWARE_VARTYPE_VT_F16E0 = Usage(0x0910, UsageType.DATA_SELECTOR) 486 | FIRMWARE_VARTYPE_VT_F16E1 = Usage(0x0911, UsageType.DATA_SELECTOR) 487 | FIRMWARE_VARTYPE_VT_F16E2 = Usage(0x0912, UsageType.DATA_SELECTOR) 488 | FIRMWARE_VARTYPE_VT_F16E3 = Usage(0x0913, UsageType.DATA_SELECTOR) 489 | FIRMWARE_VARTYPE_VT_F16E4 = Usage(0x0914, UsageType.DATA_SELECTOR) 490 | FIRMWARE_VARTYPE_VT_F16E5 = Usage(0x0915, UsageType.DATA_SELECTOR) 491 | FIRMWARE_VARTYPE_VT_F16E6 = Usage(0x0916, UsageType.DATA_SELECTOR) 492 | FIRMWARE_VARTYPE_VT_F16E7 = Usage(0x0917, UsageType.DATA_SELECTOR) 493 | FIRMWARE_VARTYPE_VT_F16E8 = Usage(0x0918, UsageType.DATA_SELECTOR) 494 | FIRMWARE_VARTYPE_VT_F16E9 = Usage(0x0919, UsageType.DATA_SELECTOR) 495 | FIRMWARE_VARTYPE_VT_F16EA = Usage(0x091A, UsageType.DATA_SELECTOR) 496 | FIRMWARE_VARTYPE_VT_F16EB = Usage(0x091B, UsageType.DATA_SELECTOR) 497 | FIRMWARE_VARTYPE_VT_F16EC = Usage(0x091C, UsageType.DATA_SELECTOR) 498 | FIRMWARE_VARTYPE_VT_F16ED = Usage(0x091D, UsageType.DATA_SELECTOR) 499 | FIRMWARE_VARTYPE_VT_F16EE = Usage(0x091E, UsageType.DATA_SELECTOR) 500 | FIRMWARE_VARTYPE_VT_F16EF = Usage(0x091F, UsageType.DATA_SELECTOR) 501 | FIRMWARE_VARTYPE_VT_F32E0 = Usage(0x0920, UsageType.DATA_SELECTOR) 502 | FIRMWARE_VARTYPE_VT_F32E1 = Usage(0x0921, UsageType.DATA_SELECTOR) 503 | FIRMWARE_VARTYPE_VT_F32E2 = Usage(0x0922, UsageType.DATA_SELECTOR) 504 | FIRMWARE_VARTYPE_VT_F32E3 = Usage(0x0923, UsageType.DATA_SELECTOR) 505 | FIRMWARE_VARTYPE_VT_F32E4 = Usage(0x0924, UsageType.DATA_SELECTOR) 506 | FIRMWARE_VARTYPE_VT_F32E5 = Usage(0x0925, UsageType.DATA_SELECTOR) 507 | FIRMWARE_VARTYPE_VT_F32E6 = Usage(0x0926, UsageType.DATA_SELECTOR) 508 | FIRMWARE_VARTYPE_VT_F32E7 = Usage(0x0927, UsageType.DATA_SELECTOR) 509 | FIRMWARE_VARTYPE_VT_F32E8 = Usage(0x0928, UsageType.DATA_SELECTOR) 510 | FIRMWARE_VARTYPE_VT_F32E9 = Usage(0x0929, UsageType.DATA_SELECTOR) 511 | FIRMWARE_VARTYPE_VT_F32EA = Usage(0x092A, UsageType.DATA_SELECTOR) 512 | FIRMWARE_VARTYPE_VT_F32EB = Usage(0x092B, UsageType.DATA_SELECTOR) 513 | FIRMWARE_VARTYPE_VT_F32EC = Usage(0x092C, UsageType.DATA_SELECTOR) 514 | FIRMWARE_VARTYPE_VT_F32ED = Usage(0x092D, UsageType.DATA_SELECTOR) 515 | FIRMWARE_VARTYPE_VT_F32EE = Usage(0x092E, UsageType.DATA_SELECTOR) 516 | FIRMWARE_VARTYPE_VT_F32EF = Usage(0x092F, UsageType.DATA_SELECTOR) 517 | # end firmware vartype selectors 518 | DATA_GENERIC_UNIT_OF_MEASURE = Usage(0x0573, UsageType.COLLECTION_NAMED_ARRAY) 519 | # begin unit of measure selectors 520 | GENERIC_UNIT_NOT_SPECIFIED = Usage(0x0940, UsageType.DATA_SELECTOR) 521 | GENERIC_UNIT_LUX = Usage(0x0941, UsageType.DATA_SELECTOR) 522 | GENERIC_UNIT_DEGREES_KELVIN = Usage(0x0942, UsageType.DATA_SELECTOR) 523 | GENERIC_UNIT_DEGREES_CELSIUS = Usage(0x0943, UsageType.DATA_SELECTOR) 524 | GENERIC_UNIT_PASCAL = Usage(0x0944, UsageType.DATA_SELECTOR) 525 | GENERIC_UNIT_NEWTON = Usage(0x0945, UsageType.DATA_SELECTOR) 526 | GENERIC_UNIT_METERS_PER_SECOND = Usage(0x0946, UsageType.DATA_SELECTOR) 527 | GENERIC_UNIT_KILOGRAM = Usage(0x0947, UsageType.DATA_SELECTOR) 528 | GENERIC_UNIT_METER = Usage(0x0948, UsageType.DATA_SELECTOR) 529 | GENERIC_UNIT_METERS_PER_SEC_SQRD = Usage(0x0949, UsageType.DATA_SELECTOR) 530 | GENERIC_UNIT_FARAD = Usage(0x094A, UsageType.DATA_SELECTOR) 531 | GENERIC_UNIT_AMPERE = Usage(0x094B, UsageType.DATA_SELECTOR) 532 | GENERIC_UNIT_WATT = Usage(0x094C, UsageType.DATA_SELECTOR) 533 | GENERIC_UNIT_HENRY = Usage(0x094D, UsageType.DATA_SELECTOR) 534 | GENERIC_UNIT_OHM = Usage(0x094E, UsageType.DATA_SELECTOR) 535 | GENERIC_UNIT_VOLT = Usage(0x094F, UsageType.DATA_SELECTOR) 536 | GENERIC_UNIT_HERTZ = Usage(0x0950, UsageType.DATA_SELECTOR) 537 | GENERIC_UNIT_BAR = Usage(0x0951, UsageType.DATA_SELECTOR) 538 | GENERIC_UNIT_DEGREES_ANTI_CLOCKWISE = Usage(0x0952, UsageType.DATA_SELECTOR) 539 | GENERIC_UNIT_DEGREES_CLOCKWISE = Usage(0x0953, UsageType.DATA_SELECTOR) 540 | GENERIC_UNIT_DEGREES = Usage(0x0954, UsageType.DATA_SELECTOR) 541 | GENERIC_UNIT_DEGREES_PER_SECOND = Usage(0x0955, UsageType.DATA_SELECTOR) 542 | GENERIC_UNIT_DEGREES_PER_SEC_SQRD = Usage(0x0956, UsageType.DATA_SELECTOR) 543 | GENERIC_UNIT_KNOT = Usage(0x0957, UsageType.DATA_SELECTOR) 544 | GENERIC_UNIT_PERCENT = Usage(0x0958, UsageType.DATA_SELECTOR) 545 | GENERIC_UNIT_SECOND = Usage(0x0959, UsageType.DATA_SELECTOR) 546 | GENERIC_UNIT_MILLISECOND = Usage(0x095A, UsageType.DATA_SELECTOR) 547 | GENERIC_UNIT_G = Usage(0x095B, UsageType.DATA_SELECTOR) 548 | GENERIC_UNIT_BYTES = Usage(0x095C, UsageType.DATA_SELECTOR) 549 | GENERIC_UNIT_MILLIGAUSS = Usage(0x095D, UsageType.DATA_SELECTOR) 550 | GENERIC_UNIT_BITS = Usage(0x095E, UsageType.DATA_SELECTOR) 551 | # end unit of measure selectors 552 | DATA_GENERIC_UNIT_EXPONENT = Usage(0x0574, UsageType.COLLECTION_NAMED_ARRAY) 553 | # begin unit exponent selectors 554 | GENERIC_EXPONENT_0 = Usage(0x0970, UsageType.DATA_SELECTOR) 555 | GENERIC_EXPONENT_1 = Usage(0x0971, UsageType.DATA_SELECTOR) 556 | GENERIC_EXPONENT_2 = Usage(0x0972, UsageType.DATA_SELECTOR) 557 | GENERIC_EXPONENT_3 = Usage(0x0973, UsageType.DATA_SELECTOR) 558 | GENERIC_EXPONENT_4 = Usage(0x0974, UsageType.DATA_SELECTOR) 559 | GENERIC_EXPONENT_5 = Usage(0x0975, UsageType.DATA_SELECTOR) 560 | GENERIC_EXPONENT_6 = Usage(0x0976, UsageType.DATA_SELECTOR) 561 | GENERIC_EXPONENT_7 = Usage(0x0977, UsageType.DATA_SELECTOR) 562 | GENERIC_EXPONENT_8 = Usage(0x0978, UsageType.DATA_SELECTOR) 563 | GENERIC_EXPONENT_9 = Usage(0x0979, UsageType.DATA_SELECTOR) 564 | GENERIC_EXPONENT_A = Usage(0x097A, UsageType.DATA_SELECTOR) 565 | GENERIC_EXPONENT_B = Usage(0x097B, UsageType.DATA_SELECTOR) 566 | GENERIC_EXPONENT_C = Usage(0x097C, UsageType.DATA_SELECTOR) 567 | GENERIC_EXPONENT_D = Usage(0x097D, UsageType.DATA_SELECTOR) 568 | GENERIC_EXPONENT_E = Usage(0x097E, UsageType.DATA_SELECTOR) 569 | GENERIC_EXPONENT_F = Usage(0x097F, UsageType.DATA_SELECTOR) 570 | # end unit exponent selectors 571 | DATA_GENERIC_REPORT_SIZE = Usage(0x0575, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 572 | DATA_GENERIC_REPORT_COUNT = Usage(0x0576, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 573 | # property usages (get/set feature report) 574 | PROPERTY_GENERIC = Usage(0x0580, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 575 | PROPERTY_ENUMERATOR_TABLE_ROW_INDEX = Usage(0x0581, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 576 | PROPERTY_ENUMERATOR_TABLE_ROW_COUNT = Usage(0x0582, [UsageType.DATA_STATIC_VALUE, UsageType.DATA_DYNAMIC_VALUE]) 577 | --------------------------------------------------------------------------------