You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Device Request]: Add support for MCHOSE V9 Pro Wireless Headset (2.4 GHz) #24
I am not a native speaker. I searched for all the data using AI. The text below was entirely written by AI, using the necessary data that I managed to find with its help. I hope this information will be useful for future updates.
Device Details
Brand: MCHOSE
Model: V9 Pro (Wireless Gaming Headset)
Connection: 2.4 GHz USB Dongle
Vendor ID (VID):0x291D
Product ID (PID):0x385D
Protocol Information
The headset dongle exposes vendor-defined collections (UsagePage: 0xFF90, Usage: 0x0001). It autonomously pushes telemetry input reports (0x55, 63 bytes) over URB_INTERRUPT in.
Report Kind:HidReportKind.InputOutput
Usage Page:0xFF90
Usage:0x0001
Report Length:63
Report ID:0x55
Verified Packet Capture (Wireshark USBPcap)
When the headset battery is at 60%, the dongle delivers the following input report:
55 65 3c 02 00 00 00 00 00 00 00 00 00 00 00 00 ...
Byte 0 (0x55): Report ID
Byte 1 (0x65): Telemetry frame header
Byte 2 (0x3C): Battery percentage (0x3C = 60%)
Byte 3 (0x02): Device/Power status
Suggested Protocol Implementation
usingSystem.Collections.Generic;usingSystem.Threading;usingSystem.Threading.Tasks;usingDeviceBatteryInfo.Core;usingDeviceBatteryInfo.Sources;usingDeviceBatteryInfo.Sources.Hid;namespaceDeviceBatteryInfo.Sources.Mchose;internalsealedclassMchoseProtocol():HidProtocol("MCHOSE",vendorId:0x291D,reportLength:63,reportKind:HidReportKind.InputOutput,usagePage:0xFF90,usage:0x0001){publicoverrideIReadOnlyList<HidDeviceInfo>Devices{get;}=[new("V9 Pro",0x385D),];publicoverrideasyncTask<BatteryReading>ReadAsync(HidChannelchannel,HidDeviceInfodevice,CancellationTokencancellationToken){varresponse=awaitchannel.ExchangeAsync(request:[],isComplete: r =>r.Length>=4&&r[0]==0x55&&r[1]==0x65,cancellationToken);returnBatteryReading.FromPercent(response[2]);}}
### Contribution-[x]I own this device and am willing to help test a backend forit.-[] I'd like toimplementthis myself and am opening this issue totrack it.
Thanks for the capture and for offering to test! The report you found (0x55 on FF90:0001, level in byte 2) looks like a good start. Before I can add the headset, I need a few more captures, because one reading at 60% could still be a coincidence.
Using Wireshark/USBPcap the same way you did, could you please send the full 0x55 report (all bytes, not only the first ones) in these situations:
At two or three other battery levels (for example around 80% and around 30%). Please also write the level the MCHOSE software shows at that moment.
While the headset is charging.
When it is fully charged and still on the cable.
After you switch the headset off while the dongle stays plugged in. Does the dongle still send 0x55 reports, and if so, what do they contain?
Two more questions:
How often does the dongle send the 0x55 report? About every second, every few seconds, or only when something changes? The timestamps in Wireshark show this.
Does byte 1 always stay 0x65, or does it sometimes have other values?
With that I can tell which bytes mean level and charging, and how long the plugin has to listen.
Hi! Here are the captures, detailed observations, and answers to all of your questions:
1. Battery levels (Discharging)
The battery level in byte 2 directly reflects the percentage 1-to-1 without conversion factors:
50%: 55 65 32 02 00 00 00 00 ... (Byte 2 is 0x32 = 50%, Byte 3 is 0x02)
60%: 55 65 3c 02 00 00 00 00 ... (Byte 2 is 0x3C = 60%, Byte 3 is 0x02)
70%: 55 65 46 02 00 00 00 00 ... (Byte 2 is 0x46 = 70%, Byte 3 is 0x02)
80%: 55 65 50 02 00 00 00 00 ... (Byte 2 is 0x50 = 80%, Byte 3 is 0x02)
All values match the official MCHOSE desktop app readings.
2. While charging
When the USB-C charging cable is plugged in, byte 3 switches from 0x02 to 0x03 (Charging indicator in the official app displays a green lightning bolt).
Hardware quirk: The headset does not update byte 2 dynamically during charging. It freezes at the last recorded percentage (e.g. 0x32 / 50%) while byte 3 stays 0x03. Once the cable is unplugged, the controller immediately recalculates and broadcasts the new level (jumped to 0x50 / 80% with byte 3 returning to 0x02).
3. Fully charged on cable
Because the fuel gauge freezes byte 2 while the charging cable is connected, the level does not report 100% on-cable until disconnected. When unplugged at full charge, it follows the same direct hex mapping (0x64 = 100%).
4. Turning the headset off (dongle plugged in)
When the headset is powered off using its physical switch, the dongle sends a single disconnect frame: 55 65 00 1a 00 00 00 00 ...
Byte 2 drops to 0x00.
Byte 3 becomes 0x1A (offline / link disconnected state).
After this frame, incoming interrupt transfers stop completely until the headset is switched back on.
Answers to the two additional questions:
How often does the dongle send the 0x55 report?
While charging: The dongle autonomously sends reports in bursts of two packets exactly every ~10.2 seconds.
While discharging: It pushes reports on state changes (power on, reconnect) and periodically when queried by the host software.
Does byte 1 always stay 0x65, or does it sometimes have other values?
For battery and operational telemetry, byte 1 is always 0x65.
The only exception is an initialization handshake frame sent immediately when the headset connects: a single packet 55 11 02 00 ... arrives first, followed by regular 55 65 telemetry reports.
Protocol Summary:
Collection: UsagePage 0xFF90, Usage 0x0001
Report ID: 0x55 (63/64 bytes payload)
Byte 0: 0x55 (Report ID)
Byte 1: 0x65 (Telemetry command)
Byte 2: Battery level (direct integer percentage, 0x00 to 0x64)
Thank you, this is very helpful! Level in byte 2, state in byte 3, the 00 1A frame when the headset is off, and the 55 11 frame on connect all make sense now.
There is one problem left. You wrote that while the headset runs on battery, the dongle only sends a report when something changes, or when the MCHOSE app asks for it. The plugin cannot rely on the MCHOSE app running, so it has to ask the dongle itself, the same way the app does.
Could you capture that request? In Wireshark:
Start the capture, then open (or restart) the MCHOSE app while the headset is on battery.
Look for an OUT transfer (host → dongle) on the same interface, right before a 55 65 report comes back. It is probably a URB_INTERRUPT out or a SET_REPORT control transfer.
Please send all bytes of that request, and the 55 65 report that answers it.
If the app sends several different requests when it starts, please send all of them, so I can find the one that returns the battery.
One more small question: does the level always come in steps of 10% (50, 60, 70, 80), or have you also seen values like 57%?
The battery level strictly reports in 10% discrete steps (50%, 60%, 70%, 80%, 90% observed so far). No intermediate granular percentages (like 57%) have been seen.
The plugin sends 55 65 01 and reads the 55 65 answer.
On battery it shows the level from byte 2 (in 10% steps).
While charging it shows only "charging", without a percentage, because the level stays frozen at the old value until the cable is removed.
When the headset is off (00 1A), the plugin keeps the last value instead of showing 0%.
Could you please test the branch? You need the .NET 10 SDK and a clone of the branch. Then, from tests/DeviceBatteryInfo.Tests, run:
dotnet test --filter "Category=Hardware&Name=Reads_every_supported_hid_device_through_the_plugin" --logger "console;verbosity=detailed"
Please close the MCHOSE app first, so we know the plugin gets an answer on its own. The output should contain a line for MCHOSE V9 Pro. Please run it in these situations and send the output each time:
On battery. Please also write the level the MCHOSE app shows.
While charging.
With the headset switched off (dongle still plugged in).
If you also use Macro Deck, you can build the plugin from the branch and add the headset under "Other devices" → MCHOSE → V9 Pro. Then please tell me if the level and the charging state look right over a longer time.
Hi! Here is the full console output from testing the feature/mchose-v9-pro branch using the hardware test command:
1. ON BATTERY (Discharging)
MCHOSE app reading: 90%
Test verdict: PASSED
........
AULA F75 not connected
Sony DualSense not connected
MCHOSE V9 Pro 90% Discharging
Passed Reads_every_supported_hid_device_through_the_plugin [359 ms]
Note: The active query (55 65 01) works completely fine on its own without the official application running.
2. WHILE CHARGING (Type-C cable plugged in)
Test verdict: FAILED
........
AULA F75 not connected
Sony DualSense not connected
MCHOSE V9 Pro read failed: The headset reported level 90, state 0.
Failed Reads_every_supported_hid_device_through_the_plugin [321 ms]
Error Message:
No supported HID device answered.
Assert.That(read, Is.GreaterThan(0))
Expected: greater than 0
But was: 0
Stack Trace:
at DeviceBatteryInfo.Tests.HardwareTests.Reads_every_supported_hid_device_through_the_plugin() in tests/DeviceBatteryInfo.Tests/HardwareTests.cs:line 151
Note: When queried while charging, the headset responded with level 90, but state was parsed as 0, triggering "read failed: The headset reported level 90, state 0".
3. HEADSET SWITCHED OFF (Dongle remains plugged in)
Test verdict: FAILED (Expected timeout)
........
AULA F75 not connected
Sony DualSense not connected
MCHOSE V9 Pro read failed: HID report exchange on \\?\hid#vid_291d&pid_385d&mi_00&col05#9&11c406c9&0&0004#{4d1e55b2-f16f-11cf-88cb-001111000030} was not answered within 2000 ms.
Failed Reads_every_supported_hid_device_through_the_plugin [2 s]
Error Message:
No supported HID device answered.
Assert.That(read, Is.GreaterThan(0))
Expected: greater than 0
But was: 0
Stack Trace:
at DeviceBatteryInfo.Tests.HardwareTests.Reads_every_supported_hid_device_through_the_plugin() in tests/DeviceBatteryInfo.Tests/HardwareTests.cs:line 151
Note: Because the headset is powered off, it does not respond to the active request, which results in the expected 2000 ms timeout.
I posted the test results above, but also tried testing the built plugin directly inside Macro Deck 3 as you suggested.
Unfortunately, running our locally built .dll in Macro Deck 3 causes the configuration dialog to open completely blank (the form fields and device selection dropdown do not load). This appears to be caused by a runtime/.NET version mismatch when loading the locally built library, as Macro Deck 3 fails to instantiate the plugin's configuration UI.
Additions from a real person: Or maybe I just didn't understand how to properly communicate to the AI what needed to be done. I previously tried manually adding support for my audio headset to your plugin, but I ran into the same problem (the settings window disappeared, was blank, and broke the plugin's logic). So I decided to write to you directly and provide all the necessary information
Since the console test (dotnet test) directly accesses the hardware and already gave clear results:
On battery: Successfully queried and reported 90% Discharging via 55 65 01.
While charging: Failed with read failed: The headset reported level 90, state 0.
Switched off: Timed out as expected.
Could you please check the state 0 parsing issue during charging on the branch? Once you create an official test build / .macroDeckPlugin release package, I'll be happy to install it directly in Macro Deck and test it over a longer period!
Thank you for testing! On battery it works without the MCHOSE app, and switched off it keeps the last value, which is what it should do.
Charging: the headset answers the plugin's request with state 0x00 while charging, not 0x03 like in the reports the dongle sends on its own. Before I map that, I'd like to see the raw answer over time. I added a test on the branch that prints the raw bytes. I rewrote the branch, so please update it with git fetch and git reset --hard origin/feature/mchose-v9-pro (a plain git pull will fail). Then run from tests/DeviceBatteryInfo.Tests:
dotnet test --filter "Category=Hardware&Name=Reads_the_raw_mchose_battery_report" --logger "console;verbosity=detailed"
Please run it:
Once on battery.
Right after plugging in the cable, then every 5 minutes for about half an hour while it charges (does the level change?).
Once when the MCHOSE app says it is fully charged, with the cable still in.
Right after unplugging the cable again.
Please write the level the MCHOSE app shows next to each run.
Macro Deck: a blank configuration window usually means the plugin was not installed the way Macro Deck expects, not that something is wrong with the .NET version. How exactly did you put the plugin into Macro Deck? Did you copy the .dll somewhere, or install a package?
The easiest way to try the branch in Macro Deck is the Makefile in the repository. You need GNU make and Git Bash on your PATH (on Windows). Then, with Macro Deck 3 running, from the repository folder:
make cli
make run
make cli installs the matching macrodeck-plugin tool, and make run builds the plugin and connects it to your running Macro Deck. The first time, Macro Deck asks you to pair it. You don't need to copy any files. If the window is still blank that way, please send the Macro Deck log from that time.
Hi! Here are the full test results covering all the requested scenarios:
Summary of the Logging Timeline
Timestamp
Physical State
Headset LED
Raw HID Report
Parsed Result
21:48:55
On battery (before cable)
Normal
55 65 5A 02 00 00 00...
90% Discharging
21:53:25
Cable plugged in (0 min)
Red
55 65 5A 00 00 00 00...
level 90, state 0
22:01:17
Charging (~8 min)
Red
55 65 5A 00 00 00 00...
level 90, state 0
22:09:27
Charging (~16 min)
Red
55 65 5A 00 00 00 00...
level 90, state 0
22:14:46
Charging (~21 min)
Red
55 65 5A 00 00 00 00...
level 90, state 0
22:19:59
Charging (~26 min)
Red
55 65 5A 00 00 00 00...
level 90, state 0
23:06:49
Charging (~73 min)
Red
55 65 5A 00 00 00 00...
level 90, state 0
00:12:05
Fully charged on cable
Blue
55 65 5A 00 00 00 00...
level 90, state 0
00:14:12
Right after unplugging
Blue/Normal
55 65 5A 02 00 00 00...
90% Discharging
00:14:48
~30s after unplugging and clicking the refresh button in the app.
Normal
55 65 64 02 00 00 00...
100% Discharging
Key Observations
State 0x00 strictly means Charging:
While the cable is connected, the active query response consistently returns byte 3 as 0x00. Bytes 4 through 15 are always 0x00.
Battery Level is frozen while plugged in:
Even when the hardware finishes charging (LED turns from Red to Blue), byte 2 remains frozen at the pre-charging value (0x5A / 90%) and state stays 0x00. The official MCHOSE app also displays 90% until the cable is removed.
Transition to 100%:
A few seconds after disconnecting the Type-C cable, the headset switches back to state 0x02 (Discharging) and updates byte 2 to 0x64 (100%).
Detailed Console Logs
1. Fully charged with cable still plugged in (LED turned Blue, MCHOSE app showing 90%):
MCHOSE 00:12:05: 55 65 5A 00 00 00 00 00 00 00 00 00 00 00 00 00 = The headset reported level 90, state 0.
Passed Reads_the_raw_mchose_battery_report [247 ms]
I am very confused by the fact that 100% in the application and 0x64 in the console were displayed only after I clicked the Refresh button in the official application. It might have been worth waiting longer, but when I saw that after disconnecting the cable from the headset, the indication in the application disappeared that charging was currently underway, and only 90% remained, it puzzled me and I was confused and just updated the data manually. The AI calms me down and says that the headset itself does not send requests all the time, and by pressing the button I just sent the same request as in the test "55 65 01", but the first time the answer came "55 65 5A 02..."
If the manual update allegedly sent the same request, then why was the answer different last time? Honestly, I don't know. It may be a coincidence, but I won't be able to check again soon. The headset has a very capacious battery (2000 mAh) and it will take several days to discharge it to values at which global tests can be repeated)))
Thank you for the long test, this answers it! With the cable in, the headset always answers state 0x00 with the frozen level, both while charging and when it is full. So the plugin now shows 0x00 as "charging" without a percentage, the same as 0x03. It cannot tell charging and full apart, because the headset doesn't either.
About the 90% → 100% after unplugging: I don't think the refresh button changed anything. Right after the cable comes out, the headset still reports the old frozen level for a short time, and then updates it. Your refresh came after that update, so it showed 100%. The plugin asks the headset again on every poll, so it will pick up the new value by itself. No need to repeat the test for that.
The change is on the branch; a git pull gets it. If you still run into problems with Macro Deck, just tell me and we'll fix them.
If you don't find any other issues, just give me your go and I'll merge it.
Great, thank you for testing it so thoroughly, and for the screenshots! The pull request is #28.
Even once it is merged, it will probably still take a few days until it shows up in a release: I want to make one full release together with the other devices that are in progress right now.
Activity
PyFlat-JR commentedon Oct 15, 2026
Thanks for the capture and for offering to test! The report you found (
0x55onFF90:0001, level in byte 2) looks like a good start. Before I can add the headset, I need a few more captures, because one reading at 60% could still be a coincidence.Using Wireshark/USBPcap the same way you did, could you please send the full
0x55report (all bytes, not only the first ones) in these situations:0x55reports, and if so, what do they contain?Two more questions:
0x55report? About every second, every few seconds, or only when something changes? The timestamps in Wireshark show this.0x65, or does it sometimes have other values?With that I can tell which bytes mean level and charging, and how long the plugin has to listen.
Don-AriOn commentedon Oct 15, 2026
Hi! Here are the captures, detailed observations, and answers to all of your questions:
1. Battery levels (Discharging)
The battery level in byte 2 directly reflects the percentage 1-to-1 without conversion factors:
55 65 32 02 00 00 00 00 ...(Byte 2 is0x32= 50%, Byte 3 is0x02)55 65 3c 02 00 00 00 00 ...(Byte 2 is0x3C= 60%, Byte 3 is0x02)55 65 46 02 00 00 00 00 ...(Byte 2 is0x46= 70%, Byte 3 is0x02)55 65 50 02 00 00 00 00 ...(Byte 2 is0x50= 80%, Byte 3 is0x02)All values match the official MCHOSE desktop app readings.
2. While charging
0x02to0x03(Charging indicator in the official app displays a green lightning bolt).55 65 32 03 00 00 00 00 ...0x32/ 50%) while byte 3 stays0x03. Once the cable is unplugged, the controller immediately recalculates and broadcasts the new level (jumped to0x50/ 80% with byte 3 returning to0x02).3. Fully charged on cable
Because the fuel gauge freezes byte 2 while the charging cable is connected, the level does not report 100% on-cable until disconnected. When unplugged at full charge, it follows the same direct hex mapping (
0x64= 100%).4. Turning the headset off (dongle plugged in)
When the headset is powered off using its physical switch, the dongle sends a single disconnect frame:
55 65 00 1a 00 00 00 00 ...0x00.0x1A(offline / link disconnected state).Answers to the two additional questions:
How often does the dongle send the 0x55 report?
Does byte 1 always stay 0x65, or does it sometimes have other values?
0x65.55 11 02 00 ...arrives first, followed by regular55 65telemetry reports.Protocol Summary:
UsagePage 0xFF90, Usage 0x00010x55(63/64 bytes payload)0x55(Report ID)0x65(Telemetry command)0x00to0x64)0x02= Discharging / Active0x03= Charging0x1A= Offline / DisconnectedPyFlat-JR commentedon Oct 15, 2026
Thank you, this is very helpful! Level in byte 2, state in byte 3, the
00 1Aframe when the headset is off, and the55 11frame on connect all make sense now.There is one problem left. You wrote that while the headset runs on battery, the dongle only sends a report when something changes, or when the MCHOSE app asks for it. The plugin cannot rely on the MCHOSE app running, so it has to ask the dongle itself, the same way the app does.
Could you capture that request? In Wireshark:
55 65report comes back. It is probably aURB_INTERRUPT outor aSET_REPORTcontrol transfer.55 65report that answers it.If the app sends several different requests when it starts, please send all of them, so I can find the one that returns the battery.
One more small question: does the level always come in steps of 10% (50, 60, 70, 80), or have you also seen values like 57%?
Don-AriOn commentedon Oct 15, 2026
Hi! Here are the captures of the request/response exchange when the host queries the headset on battery:
1. The Query Request (host -> dongle)
Sent via
URB_INTERRUPT outon endpoint 0x03 (Frame 39795):55 65 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x55(Report ID)0x650x01(Query command)2. The Response (dongle -> host)
Received via
URB_INTERRUPT inon endpoint 0x82 within ~25ms (Frame 39813):55 65 5a 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x5A(90% battery level)0x02(discharging)3. Level steps:
The battery level strictly reports in 10% discrete steps (50%, 60%, 70%, 80%, 90% observed so far). No intermediate granular percentages (like 57%) have been seen.
PyFlat-JR commentedon Oct 15, 2026
Thank you, that was the missing piece!
I've added the headset on the branch
feature/mchose-v9-pro. It works like this:55 65 01and reads the55 65answer.00 1A), the plugin keeps the last value instead of showing 0%.Could you please test the branch? You need the .NET 10 SDK and a clone of the branch. Then, from
tests/DeviceBatteryInfo.Tests, run:Please close the MCHOSE app first, so we know the plugin gets an answer on its own. The output should contain a line for
MCHOSE V9 Pro. Please run it in these situations and send the output each time:If you also use Macro Deck, you can build the plugin from the branch and add the headset under "Other devices" → MCHOSE → V9 Pro. Then please tell me if the level and the charging state look right over a longer time.
Don-AriOn commentedon Oct 15, 2026
Hi! Here is the full console output from testing the
feature/mchose-v9-probranch using the hardware test command:1. ON BATTERY (Discharging)
2. WHILE CHARGING (Type-C cable plugged in)
3. HEADSET SWITCHED OFF (Dongle remains plugged in)
Don-AriOn commentedon Oct 15, 2026
I posted the test results above, but also tried testing the built plugin directly inside Macro Deck 3 as you suggested.
Unfortunately, running our locally built
.dllin Macro Deck 3 causes the configuration dialog to open completely blank (the form fields and device selection dropdown do not load). This appears to be caused by a runtime/.NET version mismatch when loading the locally built library, as Macro Deck 3 fails to instantiate the plugin's configuration UI.Since the console test (
dotnet test) directly accesses the hardware and already gave clear results:90% Dischargingvia55 65 01.read failed: The headset reported level 90, state 0.Could you please check the
state 0parsing issue during charging on the branch? Once you create an official test build /.macroDeckPluginrelease package, I'll be happy to install it directly in Macro Deck and test it over a longer period!PyFlat-JR commentedon Oct 15, 2026
Thank you for testing! On battery it works without the MCHOSE app, and switched off it keeps the last value, which is what it should do.
Charging: the headset answers the plugin's request with state
0x00while charging, not0x03like in the reports the dongle sends on its own. Before I map that, I'd like to see the raw answer over time. I added a test on the branch that prints the raw bytes. I rewrote the branch, so please update it withgit fetchandgit reset --hard origin/feature/mchose-v9-pro(a plaingit pullwill fail). Then run fromtests/DeviceBatteryInfo.Tests:Please run it:
Please write the level the MCHOSE app shows next to each run.
Macro Deck: a blank configuration window usually means the plugin was not installed the way Macro Deck expects, not that something is wrong with the .NET version. How exactly did you put the plugin into Macro Deck? Did you copy the
.dllsomewhere, or install a package?The easiest way to try the branch in Macro Deck is the Makefile in the repository. You need GNU make and Git Bash on your
PATH(on Windows). Then, with Macro Deck 3 running, from the repository folder:make cliinstalls the matchingmacrodeck-plugintool, andmake runbuilds the plugin and connects it to your running Macro Deck. The first time, Macro Deck asks you to pair it. You don't need to copy any files. If the window is still blank that way, please send the Macro Deck log from that time.Don-AriOn commentedon Oct 15, 2026
Hi! Here are the full test results covering all the requested scenarios:
Summary of the Logging Timeline
55 65 5A 02 00 00 00...55 65 5A 00 00 00 00...55 65 5A 00 00 00 00...55 65 5A 00 00 00 00...55 65 5A 00 00 00 00...55 65 5A 00 00 00 00...55 65 5A 00 00 00 00...55 65 5A 00 00 00 00...55 65 5A 02 00 00 00...55 65 64 02 00 00 00...Key Observations
0x00strictly means Charging:While the cable is connected, the active query response consistently returns byte 3 as
0x00. Bytes 4 through 15 are always0x00.Even when the hardware finishes charging (LED turns from Red to Blue), byte 2 remains frozen at the pre-charging value (
0x5A/ 90%) and state stays0x00. The official MCHOSE app also displays 90% until the cable is removed.A few seconds after disconnecting the Type-C cable, the headset switches back to state
0x02(Discharging) and updates byte 2 to0x64(100%).Detailed Console Logs
1. Fully charged with cable still plugged in (LED turned Blue, MCHOSE app showing 90%):
MCHOSE 00:12:05: 55 65 5A 00 00 00 00 00 00 00 00 00 00 00 00 00 = The headset reported level 90, state 0.
Passed Reads_the_raw_mchose_battery_report [247 ms]
2. Right after unplugging the cable (immediate query):
MCHOSE 00:14:12: 55 65 5A 02 00 00 00 00 00 00 00 00 00 00 00 00 = 90% Discharging
Passed Reads_the_raw_mchose_battery_report [243 ms]
3. ~30 seconds after unplugging (battery gauge refreshed to true value):
MCHOSE 00:14:48: 55 65 64 02 00 00 00 00 00 00 00 00 00 00 00 00 = 100% Discharging
Passed Reads_the_raw_mchose_battery_report [178 ms]
Author's observations
I am very confused by the fact that 100% in the application and 0x64 in the console were displayed only after I clicked the Refresh button in the official application. It might have been worth waiting longer, but when I saw that after disconnecting the cable from the headset, the indication in the application disappeared that charging was currently underway, and only 90% remained, it puzzled me and I was confused and just updated the data manually. The AI calms me down and says that the headset itself does not send requests all the time, and by pressing the button I just sent the same request as in the test "55 65 01", but the first time the answer came "55 65 5A 02..."
If the manual update allegedly sent the same request, then why was the answer different last time? Honestly, I don't know. It may be a coincidence, but I won't be able to check again soon. The headset has a very capacious battery (2000 mAh) and it will take several days to discharge it to values at which global tests can be repeated)))
PyFlat-JR commentedon Oct 15, 2026
Thank you for the long test, this answers it! With the cable in, the headset always answers state
0x00with the frozen level, both while charging and when it is full. So the plugin now shows0x00as "charging" without a percentage, the same as0x03. It cannot tell charging and full apart, because the headset doesn't either.About the 90% → 100% after unplugging: I don't think the refresh button changed anything. Right after the cable comes out, the headset still reports the old frozen level for a short time, and then updates it. Your refresh came after that update, so it showed 100%. The plugin asks the headset again on every poll, so it will pick up the new value by itself. No need to repeat the test for that.
The change is on the branch; a
git pullgets it. If you still run into problems with Macro Deck, just tell me and we'll fix them.If you don't find any other issues, just give me your go and I'll merge it.
Don-AriOn commentedon Oct 15, 2026
I built and packed the plugin locally, installed it into Macro Deck 3, and tested the widget in all real scenarios. It works like a charm!
Here is what I tested:
charging, and shows--instead of misleading frozen percentage values.no signalstate, and immediately recovers back to normal once the headset is turned back on.Everything behaves as expected. You have my full go to merge the branch into
mainand release it!Thank you again for the quick implementation and guidance throughout the process!
PyFlat-JR commentedon Oct 15, 2026
Great, thank you for testing it so thoroughly, and for the screenshots! The pull request is #28.
Even once it is merged, it will probably still take a few days until it shows up in a release: I want to make one full release together with the other devices that are in progress right now.