-
Notifications
You must be signed in to change notification settings - Fork 3
Introduce action_endpoint
#45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,6 +35,9 @@ def __init__(self, name: str, info: Tuple[int, int, int], | |
| for i, r in enumerate(rdescs): | ||
| self.endpoints.append(Endpoint(self, r, i)) | ||
|
|
||
| self._action_endpoint: Dict[ActionType, int] = {} | ||
| self._map_endpoint_actions() | ||
|
|
||
| self.report_rate = 100 | ||
| self.fw = Firmware(self) | ||
| self.hw: Dict[str, HWComponent] = {} | ||
|
|
@@ -75,6 +78,13 @@ def actuators(self, val: List[Actuator]) -> None: | |
|
|
||
| self._actuators = val | ||
|
|
||
| def _map_endpoint_actions(self) -> None: | ||
| for i, endpoint in enumerate(self.endpoints): | ||
| for features in endpoint.parsed_rdesc.input_reports.values(): | ||
| for feature in features: | ||
| if feature.usage_name in ['X', 'Y']: | ||
| self._action_endpoint[ActionType.XY] = i | ||
|
|
||
| def destroy(self) -> None: | ||
| for endpoint in self.endpoints: | ||
| endpoint.destroy() | ||
|
|
@@ -96,23 +106,32 @@ def transform_action(self, data: Dict[str, Any]) -> Dict[str, Any]: | |
|
|
||
| return hid_data | ||
|
|
||
| def send_hid_action(self, action: object) -> None: | ||
| def send_hid_action(self, endpoint_number: int, action: object) -> None: | ||
| ''' | ||
| Sends a HID action | ||
|
|
||
| We assume there's only one endpoint for each type of action (mouse, | ||
| keyboard, button, etc.) so we send the action to all endpoints. The | ||
| endpoint will only act on the action if it supports it. | ||
|
|
||
| :param action: HID action | ||
| :param endpoint_number: Number of the endpoint from where to send the action | ||
| :param action: HID action | ||
| ''' | ||
| for endpoint in self.endpoints: | ||
| endpoint.send(endpoint.create_report(action)) | ||
|
|
||
| def _simulate_action_xy(self, action: Dict[str, Any], packets: List[EventData], report_count: int) -> None: | ||
| # FIXME: Read max size from the report descriptor | ||
| axis_max = 127 | ||
| axis_min = -127 | ||
| endpoint = self.endpoints[endpoint_number] | ||
| endpoint.send(endpoint.create_report(action)) | ||
|
|
||
| def _simulate_action_xy(self, action: Dict[str, Any], endpoint_number: int, packets: List[EventData], | ||
| report_count: int) -> None: | ||
| endpoint = self.endpoints[endpoint_number] | ||
| min = max = {} | ||
| for features in endpoint.parsed_rdesc.input_reports.values(): | ||
| for feature in features: | ||
| if feature.usage_name in ['X', 'Y']: | ||
| axis = feature.usage_name.lower() | ||
| min[axis] = feature.logical_min | ||
| max[axis] = feature.logical_max | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should likely be provided by the actuator, which owns its own endpoint.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The actuator does not own an endpoint. The device owns the endpoints and just uses actuators to transform data before sending. I think we should keep actuators simple, they just represent a hardware property and transform data accordingly. They should know nothing about the HID protocol.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The actuator should still have its own min/max. This is a hardware capability, so there is nothing wrong to have this information in the actuator itself. How you fetch the information (from the endpoint/HID report descriptor) is orthogonal to the fact that you should store the information in the actuator (once and for all, when you create your device).
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But the min/max is only in place at the HID level. The actuator translates units in high-level actions (eg. 5mm = 5000 dots). I could move my mouse for 1 mile if I wanted to. Where the limits appear is in the high-level action to HID events translation. Given a certain number of reports and a certain logical max/min per report, a limit appears. I guess this could be simplified by saying that the min/max is HID property, not a sensor property. If we had a sensor that was only able to consecutively read a certain distance (eg. can only detect movement up to 30mm, after that you would need to stop the mouse and start the action again), then we would implement a maximum value in the actuator that represents that sensor. Does that make sense?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One example where setting the min/max in the actuator doesn't work would be a weird device that reports axis movement on one of two endpoints with different min/max (it would select the endpoint by either having some configuration or based on context).
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
well, a sensor is supposed to represent the physical chip. So at some point, your chip would have a hard limit in what values it can give you, being number of bits or the accuracy.
Well, here, you need 2 actuators with different min/max. Actuators are cheap. My main concern here is that you are basically parsing the report descriptor for every call to If you really don't want to have a HID actuator, you can always add the associated endpoint and some HID data in an object, and store this object in an opaque field
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I was not aware the report descriptor was parsed when accessing these fields. Risking getting a bit off-topic here, is there any reason for this? Can the report descriptors change over time without us being notified? If not, maybe hid-tools should be caching it?
I would prefer that approach, yes. Storing the min/max in the actuator would also mean that we need to change the way we are processing the actuators right now. We would query them on each HID event instead of each high-level action. We can generate thousands of HID events, so I don't know if we are maybe better off with just parsing the report descriptor.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We are not technically parsing the report descriptor, the code is looping through all of the endpoints, then all of the reports, then all of the fields, and then accessing the value. So from a high level point of view your code is "parsing" the report descriptor, because it tries to make sense of it. If you store the value in, let's say the actuator, the access time is 1, instead of n, because you don't need to check all the fields in the report descriptor.
Sigh. You are really making things confusing for yourself. If you want to have a HID_actuator, then all you need to have is one per report. Each of them would be called once per high-level action, but each would tell you which endpoint it would emit its events too. Then you filter out which you want. The only difference with right now, is that you will have maybe more actuators, in the case you have similar reports. But you shouldn't have a difference in code. Also, a proper way is to say that you have your current actuator, but you subclass it as a HID one. |
||
|
|
||
| assert 'x' in min and 'x' in max and 'y' in min and 'y' in max, 'Endpoint does not support XY movement' | ||
|
|
||
| # We assume a linear motion | ||
| dot_buffer = {} | ||
|
|
@@ -139,6 +158,7 @@ def _simulate_action_xy(self, action: Dict[str, Any], packets: List[EventData], | |
| dot_buffer = self.transform_action(action['data']) | ||
|
|
||
| for attr in ['x', 'y']: | ||
| axis_max = max[attr] | ||
| assert dot_buffer[attr] <= axis_max * report_count | ||
| step[attr] = dot_buffer[attr] / report_count | ||
|
|
||
|
|
@@ -149,6 +169,8 @@ def _simulate_action_xy(self, action: Dict[str, Any], packets: List[EventData], | |
| break | ||
|
|
||
| for attr in ['x', 'y']: | ||
| axis_min = min[attr] | ||
| axis_max = max[attr] | ||
| dot_buffer[attr] -= step[attr] | ||
| diff = int(round(real_dot_buffer[attr] - dot_buffer[attr])) | ||
| ''' | ||
|
|
@@ -180,6 +202,7 @@ def simulate_action(self, action: Dict[str, Any], type: int = None) -> None: | |
| packets: List[EventData] = [] | ||
|
|
||
| report_count = int(round(ms2s(action['duration']) * self.report_rate)) | ||
| endpoint = self._action_endpoint[action['type']] | ||
|
|
||
| if not report_count: | ||
| report_count = 1 | ||
|
|
@@ -188,14 +211,15 @@ def simulate_action(self, action: Dict[str, Any], type: int = None) -> None: | |
| packets.append(EventData()) | ||
|
|
||
| if action['type'] == ActionType.XY: | ||
| self._simulate_action_xy(action, packets, report_count) | ||
| self._simulate_action_xy(action, endpoint, packets, report_count) | ||
| elif action['type'] == ActionType.BUTTON: | ||
| self._simulate_action_xy(action, packets, report_count) | ||
| pass | ||
|
|
||
| s = sched.scheduler(time.time, time.sleep) | ||
| next_time = 0.0 | ||
| for packet in packets: | ||
| s.enter(next_time, 1, self.send_hid_action, | ||
| kwargs={'action': packet}) | ||
| s.enter(next_time, 1, self.send_hid_action, kwargs={ | ||
| 'endpoint_number': endpoint, | ||
| 'action': packet}) | ||
| next_time += 1 / self.report_rate | ||
| s.run() | ||
Uh oh!
There was an error while loading. Please reload this page.