307 lines
9.6 KiB
Markdown
307 lines
9.6 KiB
Markdown
# Adding New Controllers to ComfyUI_ControlFreak
|
|
|
|
This guide explains how to implement a new controller type for ComfyUI_ControlFreak. Controllers allow physical input devices to interact with ComfyUI nodes and commands.
|
|
|
|
## Controller Implementation Pattern
|
|
|
|
Each controller follows a consistent pattern that includes initialization, connection management, input handling, and event emission through the event bus.
|
|
|
|
## Implementation Steps
|
|
|
|
### 1. Create a New Controller File
|
|
|
|
Create a new JavaScript file in this directory with a descriptive name, such as `touchscreen.js` or `voice.js`.
|
|
|
|
```javascript
|
|
/**
|
|
* Your Controller Implementation Description
|
|
*/
|
|
|
|
import { eventBus } from "../core/eventBus.js";
|
|
|
|
export class YourControllerClass {
|
|
constructor() {
|
|
this.initialized = false;
|
|
this.connectedDevices = new Map();
|
|
// Additional controller-specific properties
|
|
|
|
// Set up event bus subscriptions
|
|
this._setupEventListeners();
|
|
}
|
|
|
|
// ... rest of implementation
|
|
}
|
|
```
|
|
|
|
### 2. Implement Required Methods
|
|
|
|
Your controller class should implement these key methods:
|
|
|
|
- **_setupEventListeners()**: Subscribe to events for connect/disconnect requests
|
|
- **initialize()**: Set up the controller API, request permissions if needed
|
|
- **connect(deviceId)**: Connect to a specific device
|
|
- **disconnect(deviceId)**: Disconnect from a specific device
|
|
- **getDevices()**: Return a list of available devices
|
|
- **onMessage() / onUpdate()**: Register callbacks for input events
|
|
- **onStateChange()**: Register callbacks for device connection changes
|
|
|
|
### 3. Event Handling
|
|
|
|
Controllers must emit standard events through the event bus:
|
|
|
|
- **controller:input**: When an input is detected (used by mapping engine)
|
|
- **[controller-type]:message** / **[controller-type]:update**: Device-specific events
|
|
- **[controller-type]:deviceConnected** / **[controller-type]:deviceDisconnected**: When devices connect/disconnect
|
|
- **[controller-type]:stateChanged**: When the overall controller state changes
|
|
- **[controller-type]:error**: When errors occur
|
|
|
|
### 4. Example Input Format
|
|
|
|
For consistency, controller input events should follow this format:
|
|
|
|
```javascript
|
|
const controlInput = {
|
|
type: 'your_controller_type', // Type identifier
|
|
deviceId: deviceId, // Unique device identifier
|
|
controlId: controlId, // Specific input identifier within device
|
|
rawValue: value, // Raw input value (typically 0-1 or 0-127)
|
|
name: `Your Controller Name`, // Human-readable name
|
|
// Additional controller-specific properties
|
|
};
|
|
|
|
// Emit to event bus
|
|
eventBus.emit('controller:input', controlInput);
|
|
```
|
|
|
|
### 5. Register Your Controller
|
|
|
|
Finally, register your controller in `js/index.js`:
|
|
|
|
```javascript
|
|
// 1. Import your controller class
|
|
import { YourControllerClass } from './controllers/your_controller.js';
|
|
|
|
// 2. Create instance in initializeControlFreak()
|
|
const yourController = new YourControllerClass();
|
|
|
|
// 3. Register with context provider
|
|
contextProvider.register('yourController', yourController);
|
|
|
|
// 4. Initialize your controller
|
|
await yourController.initialize();
|
|
|
|
// 5. Set up event listeners in setupControllerEventListeners()
|
|
yourController.onUpdate(update => {
|
|
const controlInput = {
|
|
// Format the update as controlInput
|
|
};
|
|
eventBus.emit('controller:input', controlInput);
|
|
});
|
|
```
|
|
|
|
## Complete Controller Template
|
|
|
|
Here's a starter template for a new controller implementation:
|
|
|
|
```javascript
|
|
/**
|
|
* [Your Controller] Implementation
|
|
*/
|
|
|
|
import { eventBus } from "../core/eventBus.js";
|
|
|
|
export class YourControllerClass {
|
|
constructor() {
|
|
this.initialized = false;
|
|
this.connectedDevices = new Map(); // Track connected devices
|
|
this.messageCallbacks = [];
|
|
this.stateChangeCallbacks = [];
|
|
}
|
|
|
|
_setupEventListeners() {
|
|
// Listen for requests to connect/disconnect devices
|
|
eventBus.on('yourcontroller:connect', async (deviceId) => {
|
|
await this.connect(deviceId);
|
|
});
|
|
|
|
eventBus.on('yourcontroller:disconnect', async (deviceId) => {
|
|
await this.disconnect(deviceId);
|
|
});
|
|
}
|
|
|
|
async initialize() {
|
|
try {
|
|
// Check if API is available
|
|
if (!window.YourControllerAPI) {
|
|
console.error("YourController: API not supported in this browser.");
|
|
eventBus.emit('yourcontroller:error', {
|
|
message: "API not supported in this browser"
|
|
});
|
|
return false;
|
|
}
|
|
|
|
// Initialize API and set up event listeners
|
|
// ...
|
|
|
|
this.initialized = true;
|
|
|
|
// Emit initialization event
|
|
eventBus.emit('yourcontroller:initialized', {
|
|
devices: this.getDevices()
|
|
});
|
|
|
|
return true;
|
|
} catch (error) {
|
|
console.error("YourController: Error initializing:", error);
|
|
this.initialized = false;
|
|
|
|
// Emit error event
|
|
eventBus.emit('yourcontroller:error', {
|
|
message: "Error initializing controller",
|
|
error
|
|
});
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Connect to a device
|
|
* @param {string} deviceId - The device ID to connect to
|
|
* @returns {Promise<string>} - The connected device ID
|
|
*/
|
|
async connect(deviceId) {
|
|
try {
|
|
if (!this.initialized) {
|
|
await this.initialize();
|
|
}
|
|
|
|
// Connect to device
|
|
// ...
|
|
|
|
// Track the connected device
|
|
this.connectedDevices.set(deviceId, deviceName);
|
|
|
|
// Emit connected event
|
|
eventBus.emit('yourcontroller:deviceConnected', {
|
|
deviceId,
|
|
deviceName,
|
|
deviceType: 'yourcontroller'
|
|
});
|
|
|
|
return deviceId;
|
|
} catch (error) {
|
|
console.error(`YourController: Error connecting to device ${deviceId}:`, error);
|
|
eventBus.emit('yourcontroller:error', {
|
|
message: `Error connecting to device ${deviceId}`,
|
|
error
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Disconnect from a device
|
|
* @param {string} deviceId - The device ID to disconnect from
|
|
* @returns {Promise<boolean>} - Whether disconnection was successful
|
|
*/
|
|
async disconnect(deviceId) {
|
|
try {
|
|
// Disconnect from device
|
|
// ...
|
|
|
|
// Remove from connected devices
|
|
const deviceName = this.connectedDevices.get(deviceId);
|
|
this.connectedDevices.delete(deviceId);
|
|
|
|
// Emit disconnected event
|
|
eventBus.emit('yourcontroller:deviceDisconnected', {
|
|
deviceId,
|
|
deviceName,
|
|
deviceType: 'yourcontroller'
|
|
});
|
|
|
|
return true;
|
|
} catch (error) {
|
|
console.error(`YourController: Error disconnecting from device ${deviceId}:`, error);
|
|
eventBus.emit('yourcontroller:error', {
|
|
message: `Error disconnecting from device ${deviceId}`,
|
|
error
|
|
});
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get list of available devices
|
|
* @returns {Array} - Array of device objects
|
|
*/
|
|
getDevices() {
|
|
// Return array of available devices
|
|
return Array.from(this.connectedDevices.entries()).map(([id, name]) => ({
|
|
id,
|
|
name,
|
|
type: 'yourcontroller',
|
|
connected: true
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Register callback for input messages
|
|
* @param {Function} callback - Function to be called with message data
|
|
*/
|
|
onMessage(callback) {
|
|
this.messageCallbacks.push(callback);
|
|
}
|
|
|
|
/**
|
|
* Register callback for state changes
|
|
* @param {Function} callback - Function to be called with new device list
|
|
*/
|
|
onStateChange(callback) {
|
|
this.stateChangeCallbacks.push(callback);
|
|
}
|
|
|
|
/**
|
|
* Trigger message callbacks
|
|
* @param {Object} message - Message data to pass to callbacks
|
|
*/
|
|
_triggerMessageCallbacks(message) {
|
|
for (const callback of this.messageCallbacks) {
|
|
try {
|
|
callback(message);
|
|
} catch (error) {
|
|
console.error("YourController: Error in message callback:", error);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Trigger state change callbacks
|
|
* @param {Array} devices - Updated device list to pass to callbacks
|
|
*/
|
|
_triggerStateChangeCallbacks(devices) {
|
|
for (const callback of this.stateChangeCallbacks) {
|
|
try {
|
|
callback(devices);
|
|
} catch (error) {
|
|
console.error("YourController: Error in state change callback:", error);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
1. **Error Handling**: Always wrap API calls in try/catch blocks and emit errors through the event bus
|
|
2. **Logging**: Use descriptive console logs for debugging with consistent format
|
|
3. **Standard Events**: Follow the established event naming conventions
|
|
4. **Value Normalization**: Convert input values to a 0-1 range when possible
|
|
5. **Resource Management**: Clean up event listeners and resources in disconnect/cleanup methods
|
|
|
|
## Existing Controller Examples
|
|
|
|
For reference, examine these existing controllers:
|
|
- `midi.js`: WebMIDI API integration for MIDI controllers
|
|
- `gamepad.js`: Gamepad API integration for game controllers |