mrene / node-minidsp

A control interface to the MiniDSP2x4HD via USB/HID
29 stars 8 forks source link

New protocol information and refactoring #5

Open markubiak opened 7 years ago

markubiak commented 7 years ago

Over the last few days I've been working on getting the MiniDSP 2x4 HD to talk to my Raspberry Pi and have it display the current volume, mute, input, etc. on a 14-segment alphanumeric display. It is working fine, but with significant and structural modifications to the code that I would never commit. Here is the information I found out. The _getMasterStatus() command in device.js sends [0x05, 0xFF, 0xDA, 0x02]. I realized that any command starting with [0x05, 0xFF] results in the read of essentially a block of memory from 0x00 to 0xFF, with each element having 16 bits of data associated with it. This data is read by giving the starting address and number of elements to return. The current volume is stored at 0xDA and the mute status is stored at 0xDB. So the above command requests the volume and mute status. By fuzzing this block, I figured out that the current selected input is actually stored at 0xD9, right before the volume. Changing the command to [0x05, 0xFF, 0xD9, 0x03] now sends back, in order, the current input index, volume, and mute status. Where this gets weird is what the MiniDSP sends back upon a change. If the volume changes, the board sends back the original [0x05, 0xFF, 0xDA, VOL, MUTE]. However, if input changes, it seems as if the board sends back [0x05, 0xFF, 0xA9, INPUT]. I can't quite figure out why, but I have also never tried to reverse engineer anything like this before so some advice would be wonderful. Once we figure out what's going on a bit better, I'd be happy to write or rewrite portions of device.js to reflect the extra information available. The next step would be exposing device.js. Since your package.json points to a nonexistent index.js, I cannot manually call any javascript functions without directly instantiating device.js. I'd rather write some code so that these classes are properly exported and can be accessed from a simple require('minidsp'); I have a very ugly index.js that does this in an extremely hacky manner. If you couldn't already tell I'm not a huge JS guy so this is all a bit bizzare to me. Anyways, let me know how I can help out.

mrene commented 7 years ago

I hadn't thought of it being a generic read register command - that's interesting. I'll take a look once I get home and have a device nearby.

Good catch on the index file, I'll fix that one. For accessing Device from a different package, node supports referencing internal package files directly - you can do const Device = require('minidsp/src/device'); etc

markubiak commented 7 years ago

Thanks Matthieu, that last line actually works great. I also have a small node script used to fuzz the 0x05 0xFF memory block, if that would be of interest to you.

mrene commented 7 years ago

Sure that'd be useful

markubiak commented 7 years ago

Here it is, very straightforward. Particular area of interest is the 0xA0- 0xAF row. minidsp-fuzz.txt

Just rename .txt to .js and run.

mrene commented 7 years ago

What do you mean by what the MiniDSP sends back upon a change - is it sending HID reports when a change happens over the IR remote?

It looks like 0x05 is the command itself and 0xFF is part of the address - I scanned 0x0000 to 0xFFFF but only a few blocks aren't filled with 0xFF.

  Offset  00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
0000FBA0  00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF  ............ÿÿÿÿ
0000FBB0  05 02 90 89 00 00 00 00 00 00 00 00 00 00 00 01  ..‰............
0000FFA0  FF 64 FF 03 20 03 2C 01 03 00 64 00 00 00 00 00  ÿdÿ. .,...d.....
0000FFB0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0000FFC0  00 00 FF FF F8 FF FF FF 04 83 1F AA 03 AA A1 4A  ..ÿÿøÿÿÿ.ƒ.ª.ª¡J
0000FFD0  02 DC 27 54 04 AA AA D8 00 02 00 00 FF FF FF FF  .Ü'T.ªªØ....ÿÿÿÿ
0000FFE0  01 01 00 00 00 00 FF FF FF FF FF FF FF FF FF FF  ......ÿÿÿÿÿÿÿÿÿÿ
0000FFF0  FF FF FF FF FF FF FF FF FF FF FF FF FF FF 05 4C  ÿÿÿÿÿÿÿÿÿÿÿÿÿÿ.L

This also means that the air app reads 0xE5, 0xE0 and 0xDA-0xDB after a config switch - I wonder what's in E0 and E0 now.

markubiak commented 7 years ago

I'm afraid you've lost me there. What do you mean xFF is is part of the address? Like, the full address would be 0xFFDA for the volume? I also don't understand the "Offset" in the provided graph... is that essentially the values of the 16-bit addresses?

As for the "sends back upon change", I first have to explain how I'm using your code. I am continuously looping the _getMasterStatus() command, IE upon callback _getMasterStatus() is called again. It's a bit prettier than that, but that's the core concept around how I'm reading the volume to put on a nice little I2C display.

That being said, what will occasionally happen is that callback from _getMasterStatus() is NOT in the form [0x05, 0xFF, 0xDA, VOL, MUTE] (or [0x05, 0xFF, 0xD9, INPUT ,VOL, MUTE] in my local changes), but rather some odd values IF and only if I have recently made a change using the IR remote. A volume change will send the callback as [0x05, 0xFF, 0xDA, VOL, MUTE], and changing input returns [0x05, 0xFF, 0xA9, INPUT]. This only happens because _getMasterStatus() is in a loop, so it is essentially getting called at the exact moment that the change is being made. My next goal is to figure out the HID protocol and node-hid a bit better so I can wait for the volume/mute/input change instead of saturating the USB connection (which is surprisingly stable btw).

mrene commented 7 years ago

The address for volume would be FFDA, yes. That means the command is formatted as 0x05 [U16 address] [U8 len] (Im assuming that because 05 00 00 20 still returns 32 bytes of data). The offset column is the address queried (it's the way hexdump displays it).

It's interesting that you're getting the wrong feedback when racing against the IR remote, the transport class basically gives you the next HID report, I had not seen any unsolicited replies like this before. I would guess that the commands to the ADSP went through the same channel and ended up back as an HID report. This could be fixed by comparing the first 4 bytes of the response before resolving the promise in sendCommand.

If you run DEBUG="minidsp:transport:usb" minidsp proxy and change volume/input with the remote, do you see messages being received from the device? If you do then we would save a few "addresses of interest" and fire some events when device.js gets notified of a change. Im thinking there's probably something hooking up an i2c bus within the minidsp to this channel.

markubiak commented 7 years ago

Oh my, this is good...

I don't know what that command is, exactly, and I intend to find out. But what I'm getting back is exactly what you expected. On volume change, I am getting back 0xFFDA: minidsp:transport:usb onData <Buffer 06 05 ff da 46 01> +0ms On input change, I am getting back 0xFFA9: minidsp:transport:usb onData <Buffer 05 05 ff a9 01> +11s The remote also has options to use the remote to change the configuration. I am going to test this and reply with the results of that test, and figure out how to use these callbacks to avoid an infinite loop...

EDIT: It seems that changing the configuration has no HID callbacks as far as I can tell.