Skip to content

Commit 239063e

Browse files
authored
Merge pull request #19 from ComfyChloe/Dev-Chloe
OSCGoesBrr, Hyperate fixes, New verison
2 parents a282c12 + d12374a commit 239063e

File tree

18 files changed

+3859
-153
lines changed

18 files changed

+3859
-153
lines changed

Containers/Hyperate.js

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,13 +127,16 @@ class HyperateAddon {
127127
}
128128
stop() {
129129
if (!this.enabled) {
130+
debug.info('HypeRate addon already stopped');
130131
return;
131132
}
133+
debug.info('HypeRate addon stopping...');
132134
this.enabled = false;
133135
this.reconnecting = false;
136+
this.lastError = null; // Clear error on manual stop
134137
this.disconnect();
135138
this.notifyStatusChange(); // Notify UI of state change immediately
136-
debug.info('HypeRate addon stopped');
139+
debug.info('HypeRate addon stopped successfully');
137140
}
138141
connect() {
139142
if (!this.secrets || !this.secrets.hyperate || !this.secrets.hyperate.apiKey) {
@@ -155,21 +158,25 @@ class HyperateAddon {
155158
});
156159
this.ws.on('close', (code, reason) => {
157160
debug.info('HypeRate connection closed');
161+
this.removeAllListeners();
158162
this.cleanup();
159163
if (this.enabled && !this.lastError) {
160164
this.lastError = 'Connection closed unexpectedly';
161165
}
162166
this.notifyStatusChange();
163-
if (this.enabled) {
167+
// Only reconnect if still enabled and not manually stopped
168+
if (this.enabled && !this.reconnecting) {
164169
this.scheduleReconnect();
165170
}
166171
});
167172
this.ws.on('error', (error) => {
168173
debug.logError(`HypeRate connection error: ${error.message}`);
169174
this.lastError = error.message || 'Connection error';
175+
this.removeAllListeners();
170176
this.cleanup();
171177
this.notifyStatusChange();
172-
if (this.enabled) {
178+
// Only reconnect if still enabled and not manually stopped
179+
if (this.enabled && !this.reconnecting) {
173180
this.scheduleReconnect();
174181
}
175182
});
@@ -188,20 +195,77 @@ class HyperateAddon {
188195
}
189196
}
190197
disconnect() {
198+
debug.info('HypeRate disconnecting...');
199+
// Leave all active channels before disconnecting
200+
if (this.ws && (this.ws.readyState === WebSocket.OPEN)) {
201+
const trackerIds = Array.from(this.trackers.keys());
202+
if (trackerIds.length > 0) {
203+
debug.info(`Leaving ${trackerIds.length} active channel(s)...`);
204+
trackerIds.forEach(deviceId => {
205+
try {
206+
const message = {
207+
topic: `hr:${deviceId}`,
208+
event: "phx_leave",
209+
payload: {},
210+
ref: 0
211+
};
212+
this.ws.send(JSON.stringify(message));
213+
debug.info(`Left channel: ${deviceId}`);
214+
} catch (error) {
215+
debug.warn(`Failed to leave channel ${deviceId}: ${error.message}`);
216+
}
217+
});
218+
}
219+
}
220+
221+
// Clear trackers map
222+
const trackerCount = this.trackers.size;
223+
this.trackers.clear();
224+
if (trackerCount > 0) {
225+
debug.info(`Cleared ${trackerCount} tracker(s) from memory`);
226+
}
227+
228+
// Clean up intervals and timeouts
191229
this.cleanup();
230+
231+
// Remove all event listeners and close WebSocket
192232
if (this.ws) {
193-
this.ws.close();
233+
this.removeAllListeners();
234+
const wsState = this.ws.readyState;
235+
if (wsState === WebSocket.OPEN || wsState === WebSocket.CONNECTING) {
236+
this.ws.close();
237+
debug.info('WebSocket connection closed');
238+
} else {
239+
debug.info(`WebSocket already closed (state: ${wsState})`);
240+
}
194241
this.ws = null;
195242
}
243+
debug.info('HypeRate disconnect completed');
244+
}
245+
removeAllListeners() {
246+
if (this.ws) {
247+
this.ws.removeAllListeners('open');
248+
this.ws.removeAllListeners('close');
249+
this.ws.removeAllListeners('error');
250+
this.ws.removeAllListeners('message');
251+
}
196252
}
197253
cleanup() {
254+
let cleaned = false;
198255
if (this.heartbeatInterval) {
199256
clearInterval(this.heartbeatInterval);
200257
this.heartbeatInterval = null;
258+
cleaned = true;
259+
debug.info('Cleared heartbeat interval');
201260
}
202261
if (this.reconnectTimeout) {
203262
clearTimeout(this.reconnectTimeout);
204263
this.reconnectTimeout = null;
264+
cleaned = true;
265+
debug.info('Cleared reconnect timeout');
266+
}
267+
if (cleaned) {
268+
debug.info('HypeRate cleanup completed');
205269
}
206270
}
207271
scheduleReconnect() {
@@ -276,6 +340,8 @@ class HyperateAddon {
276340
}
277341
leaveChannel(deviceId) {
278342
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
343+
// Still remove from trackers even if not connected
344+
this.trackers.delete(deviceId);
279345
return false;
280346
}
281347
const message = {

Containers/OSCLeash.js

Lines changed: 62 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class OSCLeashConfig {
5555
if (this.Logging) {
5656
debug.info(' Logging is enabled');
5757
}
58-
debug.info(` Using integrated OSC service (no separate ports)`);
58+
debug.info(` Using OSC-Query for receiving, legacy OSC for sending`);
5959
debug.info(` Leash name(s): ${this.Leashes.join(', ')}`);
6060
debug.info(` Strength Multiplier: ${this.StrengthMultiplier}`);
6161
debug.info(` Delays: ${this.ActiveDelay}ms & ${this.InactiveDelay}ms`);
@@ -124,11 +124,12 @@ class Leash {
124124
* OSC Package Controller - Handles OSC message routing
125125
*/
126126
class OSCPackageController {
127-
constructor(leashCollection, oscService, addonInstance = null) {
127+
constructor(leashCollection, oscQuery, oscService, addonInstance = null) {
128128
if (!leashCollection || leashCollection.length === 0) {
129129
throw new Error("Leash collection empty within Package manager.");
130130
}
131131
this.leashes = leashCollection;
132+
this.oscQuery = oscQuery;
132133
this.oscService = oscService;
133134
this.addonInstance = addonInstance;
134135
this.listeners = new Map();
@@ -182,9 +183,15 @@ class OSCPackageController {
182183
});
183184
}
184185
registerListener(address, callback) {
185-
if (this.oscService && this.oscService.registerOSCLeashListener) {
186-
this.oscService.registerOSCLeashListener(address, callback);
187-
this.listeners.set(address, callback);
186+
if (this.oscQuery) {
187+
// Create a wrapper callback that filters OSC-Query messages by address
188+
const wrappedCallback = (oscData) => {
189+
if (oscData.address === address) {
190+
callback(oscData.value);
191+
}
192+
};
193+
this.oscQuery.on('osc-message', wrappedCallback);
194+
this.listeners.set(address, wrappedCallback);
188195
}
189196
}
190197
updateGrabbed(currLeash, value) {
@@ -221,9 +228,9 @@ class OSCPackageController {
221228
}
222229
}
223230
removeAllListeners() {
224-
if (this.oscService && this.oscService.unregisterOSCLeashListener) {
231+
if (this.oscQuery) {
225232
for (const [address, callback] of this.listeners) {
226-
this.oscService.unregisterOSCLeashListener(address, callback);
233+
this.oscQuery.off('osc-message', callback);
227234
}
228235
}
229236
this.listeners.clear();
@@ -233,7 +240,8 @@ class OSCPackageController {
233240
* OSC Leash Program - Main processing logic
234241
*/
235242
class OSCLeashProgram {
236-
constructor(oscService, addonInstance = null) {
243+
constructor(oscQuery, oscService, addonInstance = null) {
244+
this.oscQuery = oscQuery;
237245
this.oscService = oscService;
238246
this.addonInstance = addonInstance;
239247
this.running = false;
@@ -274,8 +282,14 @@ class OSCLeashProgram {
274282
clearInterval(intervalId);
275283
this.activeIntervals.delete(leash.Name);
276284
// debug.info(`Stopped monitoring thread for ${leash.Name}`);
277-
// Send stop signals
278-
this.leashOutput(0.0, 0.0, 0, leash.settings);
285+
// Send stop signals (only if OSC is available)
286+
try {
287+
if (this.oscService && this.oscService.isListening) {
288+
this.leashOutput(0.0, 0.0, 0, leash.settings);
289+
}
290+
} catch (error) {
291+
debug.warn(`OSCLeash: Could not send stop signals: ${error.message}`);
292+
}
279293
// Reset leash state
280294
leash.Active = false;
281295
leash.resetMovement();
@@ -288,6 +302,12 @@ class OSCLeashProgram {
288302
this.stopLeashMonitoring(leash);
289303
return;
290304
}
305+
// Check if OSC service is still available and listening
306+
if (!this.oscService || !this.oscService.isListening) {
307+
debug.info(`Stopping movement processing - OSC service not available`);
308+
this.stopLeashMonitoring(leash);
309+
return;
310+
}
291311
// Double-check that we should actually be processing
292312
if (!leash.Grabbed) {
293313
debug.warn(`Process called but leash ${leash.Name} is not grabbed! Stopping monitoring.`);
@@ -370,10 +390,19 @@ class OSCLeashProgram {
370390
debug.warn('OSCLeash: OSC service not available');
371391
return;
372392
}
393+
// Check if OSC service is actually running before sending
394+
if (!this.oscService.isListening) {
395+
debug.warn('OSCLeash: OSC service not listening - skipping output');
396+
return;
397+
}
373398
// Send OSC messages to VRChat
374-
this.oscService.sendMessage('/input/Vertical', vert, 'f');
375-
this.oscService.sendMessage('/input/Horizontal', hori, 'f');
376-
this.oscService.sendMessage('/input/Run', runType, 'i');
399+
try {
400+
this.oscService.sendMessage('/input/Vertical', vert, 'f');
401+
this.oscService.sendMessage('/input/Horizontal', hori, 'f');
402+
this.oscService.sendMessage('/input/Run', runType, 'i');
403+
} catch (error) {
404+
debug.error(`OSCLeash: Error sending OSC messages: ${error.message}`);
405+
}
377406
}
378407
clamp(n) {
379408
return Math.max(-1.0, Math.min(n, 1.0));
@@ -388,6 +417,7 @@ class OSCLeashProgram {
388417
class OSCLeashAddon {
389418
constructor() {
390419
this.enabled = false;
420+
this.oscQuery = null;
391421
this.oscService = null;
392422
this.config = this.loadConfig();
393423
this.settings = new OSCLeashConfig(this.config);
@@ -452,9 +482,13 @@ class OSCLeashAddon {
452482
return this.enabled;
453483
}
454484

455-
start(oscService = null) {
485+
start(oscQuery = null, oscService = null) {
486+
if (!oscQuery) {
487+
debug.logError('Cannot start OSCLeash: No OSC-Query service provided');
488+
return false;
489+
}
456490
if (!oscService) {
457-
debug.logError('Cannot start OSCLeash: No OSC service provided');
491+
debug.logError('Cannot start OSCLeash: No OSC service provided for sending');
458492
return false;
459493
}
460494

@@ -463,6 +497,7 @@ class OSCLeashAddon {
463497
return true;
464498
}
465499

500+
this.oscQuery = oscQuery;
466501
this.oscService = oscService;
467502
this.enabled = true;
468503

@@ -479,10 +514,10 @@ class OSCLeashAddon {
479514
}
480515

481516
// Create program
482-
this.program = new OSCLeashProgram(this.oscService, this);
517+
this.program = new OSCLeashProgram(this.oscQuery, this.oscService, this);
483518

484519
// Create package controller
485-
this.packageController = new OSCPackageController(this.leashes, this.oscService, this);
520+
this.packageController = new OSCPackageController(this.leashes, this.oscQuery, this.oscService, this);
486521
this.packageController.onLeashActivate = (leash) => {
487522
this.program.leashRun(leash);
488523
};
@@ -494,7 +529,7 @@ class OSCLeashAddon {
494529
// Do NOT automatically set leash as active - wait for OSC grab detection
495530
// this.leashes[0].Active = true; // REMOVED - leashes should only be active when grabbed
496531

497-
debug.info('OSCLeash initialized - all leashes in idle state, waiting for grab detection...');
532+
debug.info('OSCLeash initialized - all leashes in idle state, waiting for grab detection via OSC-Query...');
498533
this.settings.printInfo();
499534
debug.info('OSCLeash addon started, awaiting input...');
500535
this.notifyStatusChange(); // Notify UI of status change
@@ -526,9 +561,13 @@ class OSCLeashAddon {
526561
leash.resetMovement();
527562
}
528563

529-
// Send stop signals
530-
if (this.program && this.oscService) {
531-
this.program.leashOutput(0.0, 0.0, 0, this.settings);
564+
// Send stop signals only if OSC is available
565+
try {
566+
if (this.program && this.oscService && this.oscService.isListening) {
567+
this.program.leashOutput(0.0, 0.0, 0, this.settings);
568+
}
569+
} catch (error) {
570+
debug.warn(`OSCLeash: Could not send stop signals during shutdown: ${error.message}`);
532571
}
533572

534573
// Remove listeners
@@ -560,8 +599,8 @@ class OSCLeashAddon {
560599
this.saveConfig();
561600

562601
// Restart if it was enabled
563-
if (wasEnabled && this.oscService) {
564-
this.start(this.oscService);
602+
if (wasEnabled && this.oscQuery && this.oscService) {
603+
this.start(this.oscQuery, this.oscService);
565604
}
566605

567606
debug.info('OSCLeash config updated');

0 commit comments

Comments
 (0)