MarByteBeep / StageLinq

NodeJS library implementation to access information through the Denon StageLinq protocol
GNU General Public License v3.0
26 stars 2 forks source link

Add support for connecting to multiple devices #7

Open MarByteBeep opened 2 years ago

MarByteBeep commented 2 years ago

We need support to connect to more than just a single device. Also connecting/responding to various services on each of these devices should properly function

MarByteBeep commented 2 years ago

@honusz can you fetch the https://github.com/MarByteBeep/StageLinq/tree/listen branch? And run the application with --listen or simply select launch from VS Code?

This is a simple test that will listen to all devices on the network and shows a message when they are added and shows a message when they are lost (disconnected). A typical output would be:

[10:41:19] [INFO] Found 'primego' Controller with index '0' at '192.168.178.34:44791' with following software: { name: 'JP11', version: '2.1.1' }
[10:41:32] [INFO] Controller with index '0' is lost
[10:41:55] [INFO] Found 'primego' Controller with index '1' at '192.168.178.34:44009' with following software: { name: 'JP11', version: '2.1.1' }

So this code should be able to detect multiple devices and will also detect if they are lost.

Before I continue, I'd love to hear from you how this code works with multiple Denon devices, like what you have. Note, this is just WIP code, a proof of concept. Nothing more :)

Feel free to add them to the network, disconnect them, plug them in again etc: the code should handle all that, without crashing. Although, that's the idea :)

honusz commented 2 years ago

It worked, picked up all my denon devices.

[10:11:53] [INFO] Found 'sc6000' Controller with ID '0' at '192.168.2.143:39467' with following software: { name: 'JP13', version: '2.1.1' }
[10:11:53] [INFO] Found 'sc6000' Controller with ID '1' at '192.168.2.144:38779' with following software: { name: 'JP13', version: '2.1.1' }
[10:11:54] [INFO] Found 'DN-X1800Prime' Controller with ID '2' at '192.168.2.248:50010' with following software: { name: 'JM08', version: '1.00' }
[10:14:10] [INFO] Found 'SoundSwitchPC' Controller with ID '3' at '192.168.2.11:53881' with following software: { name: 'SSS0', version: '1.00' }

Including SoundSwitch, which I launched while the it was running.

It did crash when I closed SoundSwitch:

assert.js:406
    throw err;
    ^

AssertionError [ERR_ASSERTION]: The expression evaluated to a falsy value:

  (0, assert_1.strict)(result.action === common_1.Action.Login)

    at Socket.<anonymous> (/Users/honusz/code/stagelinq2/dist/Listener.js:67:33)
    at Socket.emit (events.js:400:28)
    at UDP.onMessage [as onmessage] (dgram.js:941:8)
    at UDP.callbackTrampoline (internal/async_hooks.js:131:17) {
  generatedMessage: true,
  code: 'ERR_ASSERTION',
  actual: false,
  expected: true,
  operator: '=='
}
honusz commented 2 years ago

It seems the SC6000 doesn't broadcast a DISCOVERER_EXIT_ message, but SoundSwitch does, which is causing the assertion to fail.

Also, not sure if this will be an issue, but if the connection is briefly interrupted or dropped, it creates the device as a new id. This may cause issues depending on how multiple controllers are implemented, if the program instantiates a new Controller for each discovery.

Screen Shot 2022-01-10 at 11 34 34 AM
honusz commented 2 years ago

PR #8 fixes the above crash when receiving DISCOVERER_EXIT_ messages. Simple fix, so if you prefer doing it in a different way, feel free to ignore =)

kkirjala commented 2 years ago

@honusz can you fetch the https://github.com/MarByteBeep/StageLinq/tree/listen branch? And run the application with --listen or simply select launch from VS Code?

This is a simple test that will listen to all devices on the network and shows a message when they are added and shows a message when they are lost (disconnected). A typical output would be:

I tested this using the --listen command line argument and while it was indeed able to detect both players (SC5000) it was no longer connecting to either one of them or displaying any state information (playState, externalMixerVolume, bpm etc.).

This is how the console output looked like:

stagelinq@0.0.1 start node dist/main.js --listen

[00:17:05] [INFO] Found 'DN-X1800Prime' Controller with ID '0' at '192.168.1.101:50010' with following software: { name: 'JM08', version: '1.00' } [00:17:05] [INFO] Found 'sc5000-1' Controller with ID '1' at '192.168.1.235:36663' with following software: { name: 'JP07', version: '2.1.1' } [00:17:05] [INFO] Found 'sc5000' Controller with ID '2' at '192.168.1.226:39605' with following software: { name: 'JP07', version: '2.1.1' } ^C[00:17:20] [INFO] ... exiting

(I was pressing the ctrl-C in the end as the app was just sitting idle there)

Without the --listen command line argument the app waits 'til it discovers one device and connects to it:

stagelinq@0.0.1 start node dist/main.js

[00:17:40] [INFO] Found 'sc5000' Controller at '192.168.1.226:39605' with following software: { name: 'JP07', version: '2.1.1' } [00:17:40] [LOG] TCP connection to '192.168.1.226:39605' local port: 59210 [00:17:41] [INFO] Discovered the following services: [00:17:41] [INFO] port: 39043 => StateMap [00:17:41] [INFO] port: 35607 => Broadcast [00:17:41] [INFO] port: 46229 => Syncing [00:17:41] [INFO] port: 41627 => TimeSynchronization [00:17:41] [INFO] port: 43313 => BeatInfo [00:17:41] [INFO] port: 36733 => FileTransfer [00:17:41] [LOG] TCP connection to '192.168.1.226:36733' local port: 59211 [00:17:41] [INFO] Connected to service 'FileTransfer' at port 36733 [00:17:42] [LOG] TCP connection to '192.168.1.226:39043' local port: 59212 [00:17:42] [INFO] Connected to service 'StateMap' at port 39043 [00:17:42] [LOG] /Engine/Deck1/Play => {"state":true,"type":1} [00:17:42] [LOG] /Engine/Deck1/PlayState => {"state":true,"type":1}

honusz commented 2 years ago

I tested this using the --listen command line argument and while it was indeed able to detect both players (SC5000) it was no longer connecting to either one of them or displaying any state information (playState, externalMixerVolume, bpm etc.).

Yep, that's the expected behaviour. It's just a test of the new listener methods, which the multi-player functionality will be built upon. Without the --listen arg, it should behave as main does.

MarByteBeep commented 2 years ago

@honusz sorry was away from development for a few days! Will check all these awesome findings of yours tomorrow and will let you know if I need more info!

honusz commented 2 years ago

Just to revisit this because I think we pretty close to being able to integrate this to main, I'd propose a small tweak to listener.foundDevices, so that disconnecting and reconnecting controllers keep the same ID number, which will probably simplify API consumption.

Instead of using an incrementing currentDeviceId, use an ID generated from the token (which I'm fairly sure is unique) so a device will always have the same ID. For a quick test, I created an ID just by summing result.token (as its an integer array):

this.foundDevices[key] = {
      time: this.elapsedTime,
      id: result.token.reduce((a, b) => a + b, 0), //id: result.token.reduce((a, b) => a + b, 0),
};

As well as a small tweak to how the timeOut is checked:

if (this.elapsedTime > DISCOVERY_TIMEOUT && this.foundDevices.length) {
     throw new Error('Failed to detect any controller');
}

Both devices connect no problem...

[14:05:09] [INFO] Found 'sc6000' Controller with ID '2063' at '192.168.2.143:41411' with following software: { name: 'JP13', version: '2.1.2' }
[14:05:09] [LOG] TCP connection to '192.168.2.143:41411' local port: 56225
[14:05:09] [INFO] Found 'sc6000' Controller with ID '1765' at '192.168.2.144:44343' with following software: { name: 'JP13', version: '2.1.2' }
[14:05:09] [LOG] TCP connection to '192.168.2.144:44343' local port: 56226

...and reconnect with the same ID:

[14:25:26] [INFO] Controller with ID '2063' is lost
[14:25:31] [INFO] Found 'sc6000' Controller with ID '2063' at '192.168.2.143:41411' with following software: { name: 'JP13', version: '2.1.2' }
[14:25:31] [LOG] TCP connection to '192.168.2.143:41411' local port: 56506

I'll admit summing the token is a bit of a hack-y way of generating a unique, deterministic ID 😂 It's short, but theres probably like a 5% chance of a collision with 4 devices.

chrisle commented 2 years ago

@honusz I very recently forked the project and added new values for /Client/Preferences/Player so we can now differentiate between two SC6000's. https://github.com/chrisle/StageLinq

[22:38:56] [LOG] New track loaded: {
  deck: '1B',
  player: 1,
  layer: 'B',
  address: '192.168.86.201',
  port: 43209,
  masterTempo: 143.2281494140625,
  masterStatus: true,
  play: false,
  playState: false,
  artist: 'Giuseppe Ottaviani',
  trackNetworkPath: 'net://8fba5ced-9381-4d5c-b334-5bf935b8e49e/SC6000 (Internal)/Engine Library/Music/Giuseppe Ottaviani/15 Years of Vandit The Best Of/09 linking people (original mix).mp3',
  songLoaded: true,
  title: 'Linking People (Original Mix)',
  hasTrackData: true,
  fileLocation: '/media/SC6000/Engine Library/Music/Giuseppe Ottaviani/15 Years of Vandit The Best Of/09 linking people (original mix).mp3',
  currentBpm: 143.2281494140625,
  externalMixerVolume: 0,
  jogColor: '#ff1cd2e5'
}
[22:39:00] [LOG] /Engine/Deck2/PlayState => {"state":true,"type":1}
[22:39:00] [LOG] /Engine/Deck2/Play => {"state":true,"type":1}
[22:39:00] [LOG] /Engine/Deck2/CurrentBPM => {"type":0,"value":143.2281494140625}
[22:39:00] [LOG] /Engine/Master/MasterTempo => {"type":0,"value":143.2281494140625}
[22:39:02] [LOG] Now Playing on [1B]: Linking People (Original Mix) - Giuseppe Ottaviani
jaygarcia commented 1 year ago

@chrisle , thank you so much for the updates to this library via your Fork. I tried your fork's main branch and am happy to share that it works with 4 connected SC6000m's!

[15:55:03] [DEBUG] Updating state A
[15:55:03] [LOG] Updating state [1A] {
  deck: '1A',
  player: 1,
  layer: 'A',
  address: '10.0.1.236',
  port: 44195,
  masterTempo: 131.1229705810547,
  masterStatus: false,
  deviceId: 'net://{DEVICE_ID}',
  play: false,
  playState: false,
  artist: 'TJR',
  trackNetworkPath: 'net://{DEVICE_ID}/SC6000M (Internal)/Engine Library/Music/TJR/Aciiieeed! No_ 29/02-dj_tjr_-_ear_worm.mp3',
  source: 'SC6000M (Internal)',
  trackPath: 'Music/TJR/Aciiieeed! No_ 29/02-dj_tjr_-_ear_worm.mp3',
  trackPathAbsolute: '/SC6000M (Internal)/Engine Library/Music/TJR/Aciiieeed! No_ 29/02-dj_tjr_-_ear_worm.mp3',
  songLoaded: true,
  title: 'Ear Worm (Original Mix)',
  hasTrackData: true,
  fileLocation: 'net://{DEVICE_ID}/SC6000M (Internal)/Engine Library/Music/TJR/Aciiieeed! No_ 29/02-dj_tjr_-_ear_worm.mp3',
  currentBpm: 66.85420989990234,
  externalMixerVolume: 0,
  jogColor: '#ffbc3cc3',
  dbSourceName: 'net://{DEVICE_ID}/SC6000M (Internal)'
}
...
[15:55:15] [DEBUG] data: {
  "layer": "A",
  "playState": false,
  "play": false
}
[15:55:15] [DEBUG] Updating state A
[15:55:15] [LOG] Updating state [2A] {
  deck: '2A',
  player: 2,
  layer: 'A',
  address: '10.0.1.232',
  port: 34565,
  masterTempo: 57.54404067993164,
  masterStatus: true,
  deviceId: 'net://{DEVICE_ID}',
  play: false,
  playState: false,
  artist: 'Kolour FM',
  trackNetworkPath: 'net://{DEVICE_ID}/SC6000M (Internal)/Engine Library/Music/Kolour FM/PREACH/Us_(Original_Mix).aiff',
  source: 'SC6000M (Internal)',
  trackPath: 'Music/Kolour FM/PREACH/Us_(Original_Mix).aiff',
  trackPathAbsolute: '/SC6000M (Internal)/Engine Library/Music/Kolour FM/PREACH/Us_(Original_Mix).aiff',
  songLoaded: true,
  title: 'Us (Original Mix)',
  hasTrackData: true,
  fileLocation: '/media/SC6000M/Engine Library/Music/Kolour FM/PREACH/Us_(Original_Mix).aiff',
  currentBpm: 57.54404067993164,
  externalMixerVolume: 0,
  jogColor: '#ff2b84ef',
  dbSourceName: 'net://{DEVICE_ID}/SC6000M (Internal)'
}
[15:55:37] [DEBUG] 10.0.1.234:41327 /Engine/Deck1/PlayState => {"state":false,"type":1}
[15:55:39] [DEBUG] data: {
  "layer": "A",
  "playState": false
}
[15:55:39] [DEBUG] Updating state A
[15:55:39] [LOG] Updating state [3A] {
  deck: '3A',
  player: 3,
  layer: 'A',
  address: '10.0.1.234',
  port: 41327,
  masterTempo: 57.54404067993164,
  masterStatus: false,
  deviceId: 'net://{DEVICE_ID}',
  play: false,
  playState: false,
  artist: 'Thanatos',
  trackNetworkPath: 'net://{DEVICE_ID}/SC6000M (Internal)/Engine Library/Music/Thanatos/Awakenings EP/02-thanatos-concorde_(original_mix).mp3',
  source: 'SC6000M (Internal)',
  trackPath: 'Music/Thanatos/Awakenings EP/02-thanatos-concorde_(original_mix).mp3',
  trackPathAbsolute: '/SC6000M (Internal)/Engine Library/Music/Thanatos/Awakenings EP/02-thanatos-concorde_(original_mix).mp3',
  songLoaded: true,
  title: 'Concorde (Original Mix)',
  hasTrackData: true,
  fileLocation: 'net://{DEVICE_ID}/SC6000M (Internal)/Engine Library/Music/Thanatos/Awakenings EP/02-thanatos-concorde_(original_mix).mp3',
  currentBpm: 65.53107452392578,
  externalMixerVolume: 0,
  jogColor: '#ff2b84ef',
  dbSourceName: 'net://{DEVICE_ID}/SC6000M (Internal)'
}

....
[15:56:35] [LOG] Updating state [4A] {
  deck: '4A',
  player: 4,
  layer: 'A',
  address: '10.0.1.235',
  port: 41131,
  masterTempo: 61.46255111694336,
  masterStatus: true,
  deviceId: 'net://{DEVICE_ID}',
  play: false,
  playState: false,
  artist: 'Spencer & Hill',
  trackNetworkPath: 'net://{DEVICE_ID}/SC6000M (Internal)/Engine Library/Music/Spencer & Hill/Miami Weapons 2010 (Part 2)/Spencer and Hill - 303 (Bastian Van Shield Remix).mp3',
  source: 'SC6000M (Internal)',
  trackPath: 'Music/Spencer & Hill/Miami Weapons 2010 (Part 2)/Spencer and Hill - 303 (Bastian Van Shield Remix).mp3',
  trackPathAbsolute: '/SC6000M (Internal)/Engine Library/Music/Spencer & Hill/Miami Weapons 2010 (Part 2)/Spencer and Hill - 303 (Bastian Van Shield Remix).mp3',
  songLoaded: true,
  title: '303 (Bastian Van Shield Remix)',
  hasTrackData: true,
  fileLocation: 'net://{DEVICE_ID}/SC6000M (Internal)/Engine Library/Music/Spencer & Hill/Miami Weapons 2010 (Part 2)/Spencer and Hill - 303 (Bastian Van Shield Remix).mp3',
  currentBpm: 61.46255111694336,
  externalMixerVolume: 0,
  jogColor: '#ffbc3cc3',
  dbSourceName: 'net://{DEVICE_ID}/SC6000M (Internal)'
}
dzelionis commented 1 year ago

Hey team, Just checking if you are all aware of Microsoft WSL2 ? as for the stuff we do....its really awesome....?? https://learn.microsoft.com/en-us/windows/wsl/tutorials/gui-apps