diff --git a/web/main-app.js b/web/main-app.js index 68024f7..7340aec 100644 --- a/web/main-app.js +++ b/web/main-app.js @@ -158,6 +158,32 @@ button:disabled { max-width: 500px; } + +#csispairbox { + display: flex; + position: fixed; + left: 0; + top: 0; + width: 100vw; + height: 100vh; + + background: rgba(255, 255, 255, 0.8); + backdrop-filter: blur(10px); + + z-index: 900; +} + +#csispairbox.hidden { + display: none; +} + +.csispaircontent { + margin: auto; + position: relative; + width: 90%; + max-width: 500px; +} + #splashbox { display: flex; position: fixed; @@ -240,6 +266,16 @@ button:disabled { + +
@@ -293,6 +329,9 @@ export class MainApp extends HTMLElement { this.bauFound = this.bauFound.bind(this); this.requestBC = this.requestBC.bind(this); this.bcReceived = this.bcReceived.bind(this); + this.sirkFound = this.sirkFound.bind(this); + this.pairCSIS = this.pairCSIS.bind(this); + this.closeCSISPair = this.closeCSISPair.bind(this); } initializeModels() { @@ -366,6 +405,12 @@ export class MainApp extends HTMLElement { const button = this.shadowRoot?.querySelector('#connect'); button?.addEventListener('click', WebUSBDeviceService.scan); + const pairCSISButton = this.shadowRoot?.querySelector('#paircsisbutton'); + pairCSISButton?.addEventListener('click', this.pairCSIS); + + const cancelCSISButton = this.shadowRoot?.querySelector('#cancelcsisbutton'); + cancelCSISButton?.addEventListener('click', this.closeCSISPair); + const splashbox = this.shadowRoot?.querySelector('#splashbox'); WebUSBDeviceService.addEventListener('connected', () => { splashbox?.classList.add('hidden') }); WebUSBDeviceService.addEventListener('disconnected', () => { splashbox?.classList.remove('hidden') }); @@ -374,7 +419,6 @@ export class MainApp extends HTMLElement { this.#stopScanButton = this.shadowRoot?.querySelector('#stop_scan'); this.#stopScanButton.addEventListener('click', this.sendStopScan); - this.#stopScanButton.addEventListener('click', this.sendStopScan); // this.#stopScanButton.disabled = true; this.#scanSinkButton = this.shadowRoot?.querySelector('#sink_scan'); @@ -398,6 +442,8 @@ export class MainApp extends HTMLElement { this.#model.addEventListener('scan-stopped', this.scanStopped); this.#model.addEventListener('sink-scan-started', this.sinkScanStarted); this.#model.addEventListener('source-scan-started', this.sourceScanStarted); + this.#model.addEventListener('sirk-found', this.sirkFound); + this.#model.addEventListener('csis-pairing-complete', this.closeCSISPair); const activityLog = this.shadowRoot?.querySelector('#activity'); if (this.#pageState.get('log') === 'y') { @@ -502,6 +548,51 @@ export class MainApp extends HTMLElement { qrscannerbox?.classList.add('hidden'); } + sirkFound(evt) { + console.log("SIRK found"); + + const sirk = evt.detail.sirk; + const set_size = evt.detail.set_size; + + if (window["pairedSetMembers"] > 0) { + return; + } + + window["sirk"] = sirk; + window["set_size"] = set_size; + + const csispairbox = this.shadowRoot?.querySelector('#csispairbox'); + + csispairbox?.classList.remove('hidden'); + } + + closeCSISPair() { + console.log("Close CSIS pairing query"); + + const csispairbox = this.shadowRoot?.querySelector('#csispairbox'); + + window["setPairingInProgress"] = 0; + csispairbox?.classList.add('hidden'); + } + + pairCSIS() { + console.log("Pairing coordinated set"); + + const csispairbox = this.shadowRoot?.querySelector('#csispairbox'); + + const sirk = window["sirk"]; + const set_size = window["set_size"]; + window["setPairingInProgress"] = 1; + window["pairedSetMembers"] = 1; + + // Pair all already-found members first + this.#model.handlePrefoundSetMembers(set_size, sirk); + + setTimeout(() => { + this.#model.findSetMembers(set_size, sirk); + }, 500); + } + bauFound(evt) { const {decoded, raw} = evt.detail; diff --git a/web/models/assistant-model.js b/web/models/assistant-model.js index 8de64e6..04abc17 100644 --- a/web/models/assistant-model.js +++ b/web/models/assistant-model.js @@ -376,6 +376,18 @@ class AssistantModel extends EventTarget { } else { if (message.subType === MessageSubType.SINK_CONNECTED) { sink.state = "connected"; + + const pairingInProgress = window["setPairingInProgress"]; + const pairedSetMembers = window["pairedSetMembers"]; + + if (pairingInProgress) { + window["pairedSetMembers"] = pairedSetMembers + 1; + + if (window["pairedSetMembers"] == window["set_size"]) { + this.dispatchEvent(new CustomEvent('csis-pairing-complete')); + } + } + this.dispatchEvent(new CustomEvent('sink-updated', {detail: { sink }})); } else { this.#sinks.splice(this.#sinks.indexOf(sink, 1)); @@ -472,11 +484,17 @@ class AssistantModel extends EventTarget { ])?.value; console.log(`Set size = ${set_size}, rank = ${rank}, sirk = ${sirk}`); + const sink = this.#sinks.find(i => compareTypedArray(i.addr.value.addr, addr.value.addr)); - setTimeout(() => { - // Delayed setVolume to prevent comm issue - this.findSetMembers(set_size, sirk); - }, 500); + if (sink) { + sink.csis = { + sirk, + rank, + set_size + }; + } + + this.dispatchEvent(new CustomEvent('sirk-found', {detail: {sirk, set_size}})); } handleSetMemberFound(message) { @@ -495,11 +513,14 @@ class AssistantModel extends EventTarget { return; } + // Send Connect : Note, if the connect fails, we should rely on FW to send new "found" event + this.connectSink({addr}); + console.log(`Set member = ${addr}`); } handleSinkConnectivityRes(message) { - console.log(`Handle potential Error`); + console.log(`Handle Sink Connectivity Response`); // TODO: Tie RES to actual call (could be another sink) const payloadArray = ltvToTvArray(message.payload); @@ -554,6 +575,20 @@ class AssistantModel extends EventTarget { } } + handleStartSetMemberScanRes(message) { + console.log("Handle Start Set Member Scan Res"); + + const payloadArray = ltvToTvArray(message.payload); + + const err = tvArrayFindItem(payloadArray, [ + BT_DataType.BT_DATA_ERROR_CODE + ])?.value; + + if (err !== 0) { + console.log("Error code", err); + } + } + handleRES(message) { console.log(`Response message with subType 0x${message.subType.toString(16)}`); @@ -586,6 +621,10 @@ class AssistantModel extends EventTarget { console.log('RESET response received'); this.dispatchEvent(new CustomEvent('scan-stopped')); break; + case MessageSubType.START_SET_MEMBER_SCAN: + console.log('START_SET_MEMBER_SCAN response received'); + this.handleStartSetMemberScanRes(message); + break; default: console.log(`Missing handler for RES subType 0x${message.subType.toString(16)}`); } @@ -667,7 +706,7 @@ class AssistantModel extends EventTarget { console.log('Set indentifier found'); this.handleSetIndentifierFound(message); break; - case MessageSubType.SINK_SET_MEMBER_FOUND: + case MessageSubType.SET_MEMBER_FOUND: console.log('Set member found'); this.handleSetMemberFound(message); default: @@ -1029,6 +1068,25 @@ class AssistantModel extends EventTarget { this.#service.sendCMD(message); } + handlePrefoundSetMembers(set_size, sirk) { + console.log("Connect to already found CSIS set members"); + + const connected_set_members = this.#sinks.filter(i => (i.csis && compareTypedArray(i.csis.sirk, sirk) && i.state === "connected")); + const unconnected_set_members = this.#sinks.filter(i => (i.csis && compareTypedArray(i.csis.sirk, sirk) && i.state ==! "connected")); + + window["pairedSetMembers"] = connected_set_members.length; + + // If we already discovered members that are not connected yet, connect to them now + unconnected_set_members.forEach( s => { this.connectSink(s); }); + + // In case all other members were already connected before, then we just need to send off event that we are done + const pairedSetMembers = window["pairedSetMembers"]; + + if (pairedSetMembers == set_size) { + this.dispatchEvent(new CustomEvent('csis-pairing-complete')); + } + } + findSetMembers(set_size, sirk) { console.log("Connect CSIS set members CMD");