### Python: Build Coyote V2 Waveform Control Packet (PWM_A34/B34) Source: https://context7.com/lemobits/dglab-docs/llms.txt Constructs a 3-byte packet for controlling waveform parameters (X, Y, Z) for continuous pulse output. This packet is sent every 100ms to maintain the pulse. Includes helper functions for converting frequency to X/Y values. ```python import struct import time import math def build_waveform_packet(x, y, z): """ Build 3-byte waveform packet for PWM_A34 or PWM_B34 Byte 2: [Reserved(4) | Z_high(4)] Byte 1: [Z_low(1) | Y_high(7)] Byte 0: [Y_low(3) | X(5)] x: pulse duration (0-31 ms) - continuous pulses y: gap duration (0-1023 ms) - silence after pulses z: pulse width (0-31) - actual width = z * 5us Frequency = 1000 / (x + y) Hz """ # Clamp values x = max(0, min(31, x)) y = max(0, min(1023, y)) z = max(0, min(31, z)) # Pack into 24 bits: [RRRR | ZZZZZ | YYYYYYYYYY | XXXXX] bits = (z << 15) | (y << 5) | x byte2 = (bits >> 16) & 0xFF byte1 = (bits >> 8) & 0xFF byte0 = bits & 0xFF return bytes([byte2, byte1, byte0]) def frequency_to_xy(freq_hz): """Recommended algorithm for natural feel""" x = int(math.sqrt(freq_hz / 1000.0) * 15) y = freq_hz - x return max(0, min(31, x)), max(0, min(1023, y)) # Example 1: Create 100 Hz waveform x, y = frequency_to_xy(100) # Returns approximately (5, 95) z = 20 # Strong pulse width packet_100hz = build_waveform_packet(x, y, z) # Example 2: Frequency ramp (engine acceleration effect) frequencies = [7, 8, 10, 14, 25, 50, 100] for freq in frequencies: x, y = frequency_to_xy(freq) packet = build_waveform_packet(x, y, 20) # await pwm_a34_char.write_value(packet) time.sleep(0.1) # Send every 100ms # Example 3: Constant 100Hz, varying pulse width x, y = 1, 9 # 100 Hz for z_value in [4, 8, 12, 16, 20]: packet = build_waveform_packet(x, y, z_value) # await pwm_a34_char.write_value(packet) time.sleep(0.1) # CRITICAL: Must send waveform packet every 100ms to maintain output # If sending stops, device output stops immediately ``` -------------------------------- ### JavaScript: Coyote V3 Bluetooth Global Configuration (BF) Source: https://context7.com/lemobits/dglab-docs/llms.txt Configures safety limits and frequency balance parameters for the Coyote V3 device that persist across power cycles. This command must be sent after every connection. It requires a connected GATT characteristic for writing. ```javascript function buildBFCommand(limitA, limitB, balanceParam1A, balanceParam1B, balanceParam2A, balanceParam2B) { const packet = new Uint8Array(7); packet[0] = 0xBF; packet[1] = limitA; packet[2] = limitB; packet[3] = balanceParam1A; packet[4] = balanceParam1B; packet[5] = balanceParam2A; packet[6] = balanceParam2B; return packet; } const safetyConfig = buildBFCommand( 100, 50, 128, 128, 128, 128 ); await writeChar.writeValue(safetyConfig); console.log('Safety limits configured'); ``` -------------------------------- ### Establish WebSocket Connection and Bind to DG-LAB App Source: https://context7.com/lemobits/dglab-docs/llms.txt Connects to a WebSocket server and binds the control terminal with the DG-LAB app. It handles incoming messages for binding status and app feedback, and generates a QR code for app connection. Dependencies include a WebSocket client and a UUID generation function. ```javascript const clientId = generateUUID(); const ws = new WebSocket(`wss://your-server.com/${clientId}`); ws.onopen = () => { console.log('Connected to WebSocket server'); }; ws.onmessage = (event) => { const data = JSON.parse(event.data); handleMessage(data); }; function handleMessage(data) { if (data.type === 'bind') { if (data.message === 'targetId') { // Server assigned us this ID displayQRCode(data.clientId); console.log(`Your ID: ${data.clientId}`); } else if (data.message === '200') { console.log('Successfully bound to app!'); console.log(`App ID: ${data.targetId}`); } } else if (data.type === 'msg') { // Handle app status updates if (data.message.startsWith('strength-')) { const parts = data.message.split('-')[1].split('+'); console.log(`App strength - A: ${parts[0]}/${parts[2]}, B: ${parts[1]}/${parts[3]}`); } else if (data.message.startsWith('feedback-')) { const buttonId = parseInt(data.message.split('-')[1]); console.log(`App button pressed: ${buttonId}`); } } } // Generate QR code for app to scan // Format: https://www.dungeon-lab.com/app-download.php#DGLAB-SOCKET#wss://your-server.com/CLIENT_ID function displayQRCode(clientId) { const qrContent = `https://www.dungeon-lab.com/app-download.php#DGLAB-SOCKET#wss://your-server.com/${clientId}`; // Use any QR code library to generate and display console.log(`QR Code content: ${qrContent}`); } function generateUUID() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { const r = Math.random() * 16 | 0; return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); }); } ``` -------------------------------- ### CSS: Style for Log Title Element Source: https://github.com/lemobits/dglab-docs/blob/main/docs/public/web-bluetooth/bluetooth.html Styles the title element for the logs section, using flexbox to center the content and adding some top margin for spacing. ```css .logs-title { display: flex; width: 100%; justify-content: center; margin-top: 26px; } ``` -------------------------------- ### Kotlin: Map Frequency (10-1000 Hz) to Protocol Byte Values Source: https://context7.com/lemobits/dglab-docs/llms.txt Maps human-readable frequencies (10-1000 Hz) to protocol byte values (10-240) using a compression algorithm. It also provides a reverse mapping function to convert byte values back to frequencies. ```kotlin fun mapFrequency(inputHz: Int): Int { val periodMs = 1000 / inputHz return when (periodMs) { in 10..100 -> periodMs // 1:1 mapping for low frequencies in 101..600 -> (periodMs - 100) / 5 + 100 // 1:5 compression for mid frequencies in 601..1000 -> (periodMs - 600) / 10 + 200 // 1:10 compression for high frequencies else -> 10 // Default fallback } } // Example conversions println(mapFrequency(100)) // 100 Hz -> 10ms period -> byte value 10 println(mapFrequency(50)) // 50 Hz -> 20ms period -> byte value 20 println(mapFrequency(10)) // 10 Hz -> 100ms period -> byte value 100 println(mapFrequency(6)) // ~6.6 Hz -> 150ms period -> byte value 110 println(mapFrequency(1)) // ~1.5 Hz -> 650ms period -> byte value 204 // Reverse mapping (byte value to frequency Hz) fun byteToFrequency(byteValue: Int): Int { val periodMs = when (byteValue) { in 10..100 -> byteValue in 101..200 -> (byteValue - 100) * 5 + 100 in 201..240 -> (byteValue - 200) * 10 + 600 else -> 10 } return 1000 / periodMs } ``` -------------------------------- ### Convert V2 Waveform Parameters to V3 Protocol Format (Kotlin) Source: https://context7.com/lemobits/dglab-docs/llms.txt This Kotlin function `convertV2toV3` takes V2 waveform parameters (X, Y, Z) and converts them into the V3 protocol's frequency and intensity format. It calculates the period, maps it to a V3 frequency byte, converts pulse width to intensity, and replicates these values for a 100ms window. It relies on a private helper function `mapPeriodToV3`. ```kotlin // Complete V2 to V3 conversion utility class WaveformConverter { // Convert V2 waveform to V3 format fun convertV2toV3(x: Int, y: Int, z: Int): V3Waveform { // Step 1: Calculate total period (ms) val periodMs = x + y // Step 2: Convert period to V3 frequency byte val frequencyByte = mapPeriodToV3(periodMs) // Step 3: Convert pulse width to V3 intensity val intensity = (z * 5).coerceIn(0, 100) // Step 4: Replicate 4 times for 100ms window return V3Waveform( frequencies = listOf(frequencyByte, frequencyByte, frequencyByte, frequencyByte), intensities = listOf(intensity, intensity, intensity, intensity) ) } // Map period (ms) to V3 protocol byte value (10-240) private fun mapPeriodToV3(periodMs: Int): Int { return when (periodMs) { in 10..100 -> periodMs // 1:1 mapping in 101..600 -> (periodMs - 100) / 5 + 100 // 1:5 compression in 601..1000 -> (periodMs - 600) / 10 + 200 // 1:10 compression else -> 10 // Fallback } } // Build complete V3 B0 command from V2 parameters fun buildV3CommandFromV2( xA: Int, yA: Int, zA: Int, xB: Int, yB: Int, zB: Int, strengthA: Int, strengthB: Int ): ByteArray { val waveA = convertV2toV3(xA, yA, zA) val waveB = convertV2toV3(xB, yB, zB) val packet = ByteArray(20) packet[0] = 0xB0.toByte() packet[1] = 0x00 // No strength change, seq=0 packet[2] = strengthA.toByte() packet[3] = strengthB.toByte() // Channel A: frequencies then intensities waveA.frequencies.forEachIndexed { i, freq -> packet[4 + i] = freq.toByte() } waveA.intensities.forEachIndexed { i, int -> packet[8 + i] = int.toByte() } // Channel B: frequencies then intensities waveB.frequencies.forEachIndexed { i, freq -> packet[12 + i] = freq.toByte() } waveB.intensities.forEachIndexed { i, int -> packet[16 + i] = int.toByte() } return packet } } data class V3Waveform( val frequencies: List, val intensities: List ) // Example usage fun main() { val converter = WaveformConverter() // V2 parameters: 100 Hz waveform (x=1, y=9, z=20) val v2X = 1 val v2Y = 9 val v2Z = 20 val v3Wave = converter.convertV2toV3(v2X, v2Y, v2Z) println("V2 [x=$v2X, y=$v2Y, z=$v2Z] → V3:") println(" Frequencies: ${v3Wave.frequencies}") println(" Intensities: ${v3Wave.intensities}") // Build complete command val command = converter.buildV3CommandFromV2( xA = 1, yA = 9, zA = 20, // Channel A: 100Hz xB = 5, yB = 95, zB = 15, // Channel B: 10Hz strengthA = 100, strengthB = 50 ) println("\nComplete V3 B0 command: ${command.joinToString("") { "%02X".format(it) }}") } ``` -------------------------------- ### CSS: Style for Log Container and Scrollbar Source: https://github.com/lemobits/dglab-docs/blob/main/docs/public/web-bluetooth/bluetooth.html Styles the main log container with a border and sets up a scrollable area. It also customizes the webkit scrollbar appearance, setting its width and thumb color. ```css .logs { height: 200px; overflow-y: scroll; border: 1px solid #ffe99d; margin: 10px 20px 0px 20px; } *::-webkit-scrollbar { width: 4px; /* 设置滚动条的宽度为1px */ } *::-webkit-scrollbar-thumb { background-color: #ffe99d; /* 设置滚动条按钮的颜色为蓝色 */ } ``` -------------------------------- ### WebSocket Connection and Binding Source: https://context7.com/lemobits/dglab-docs/llms.txt Establishes a WebSocket connection to the DG-LAB server and binds a control terminal with the DG-LAB app for remote control. This includes generating a client ID, handling connection events, and displaying a QR code for app pairing. ```APIDOC ## WebSocket Control Protocol - Connection and Binding ### Description Establish WebSocket connection and bind control terminal with DG-LAB app for remote control. ### Method WebSocket ### Endpoint WSS (`wss://your-server.com/CLIENT_ID`) ### Parameters #### Path Parameters - **clientId** (string) - Required - A unique identifier for the control terminal. #### Query Parameters None #### Request Body None (WebSocket connection) ### Request Example ```javascript // Third-party control terminal code const clientId = generateUUID(); const ws = new WebSocket(`wss://your-server.com/${clientId}`); ws.onopen = () => { console.log('Connected to WebSocket server'); }; ws.onmessage = (event) => { const data = JSON.parse(event.data); handleMessage(data); }; function handleMessage(data) { if (data.type === 'bind') { if (data.message === 'targetId') { // Server assigned us this ID displayQRCode(data.clientId); console.log(`Your ID: ${data.clientId}`); } else if (data.message === '200') { console.log('Successfully bound to app!'); console.log(`App ID: ${data.targetId}`); } } else if (data.type === 'msg') { // Handle app status updates if (data.message.startsWith('strength-')) { const parts = data.message.split('-')[1].split('+'); console.log(`App strength - A: ${parts[0]}/${parts[2]}, B: ${parts[1]}/${parts[3]}`); } else if (data.message.startsWith('feedback-')) { const buttonId = parseInt(data.message.split('-')[1]); console.log(`App button pressed: ${buttonId}`); } } } // Generate QR code for app to scan // Format: https://www.dungeon-lab.com/app-download.php#DGLAB-SOCKET#wss://your-server.com/CLIENT_ID function displayQRCode(clientId) { const qrContent = `https://www.dungeon-lab.com/app-download.php#DGLAB-SOCKET#wss://your-server.com/${clientId}`; // Use any QR code library to generate and display console.log(`QR Code content: ${qrContent}`); } function generateUUID() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { const r = Math.random() * 16 | 0; return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); }); } ``` ### Response #### Success Response (Connection Established) - WebSocket connection is established. #### Response Example ``` Connected to WebSocket server ``` #### Bind Response - **type** (string) - Message type, e.g., 'bind'. - **message** (string) - Status message, e.g., 'targetId' or '200'. - **clientId** (string) - The client ID assigned by the server (when message is 'targetId'). - **targetId** (string) - The ID of the bound app (when message is '200'). #### Response Example ```json { "type": "bind", "message": "targetId", "clientId": "some-client-id" } ``` ```json { "type": "bind", "message": "200", "targetId": "app-id-123" } ``` ``` -------------------------------- ### Calculate Strength Adjustment Logic (Pseudocode) Source: https://context7.com/lemobits/dglab-docs/llms.txt This pseudocode function calculates a strength adjustment value based on measured voltage, a target voltage, and a defined voltage range. It maps the measured voltage within a specified range to a strength value between 0 and 40. It requires the measured voltage, the target voltage (center of the range), and the total width of the voltage range as inputs. ```python def calculate_strength_adjustment(measured_voltage, target_voltage, voltage_range): """ Args: measured_voltage: Current voltage from CC1 ADC (0.0 - 2.1V) target_voltage: Center of target range (e.g., 0.35V) voltage_range: Total range width (e.g., 0.35V means ±0.175V) Returns: Strength adjustment value (0-40) """ min_v = target_voltage - voltage_range / 2 # 0.49V max_v = target_voltage + voltage_range / 2 # 0.84V if measured_voltage < min_v or measured_voltage > max_v: return 0 # Outside trigger range # Calculate distance from center center = (min_v + max_v) / 2 distance_from_center = abs(measured_voltage - center) max_distance = voltage_range / 2 # Closer to center = higher strength adjustment normalized = 1.0 - (distance_from_center / max_distance) strength = int(normalized * 40) # Scale to 0-40 return strength # Usage example voltage = 0.65 # Read from ADC adjustment = calculate_strength_adjustment(voltage, 0.35, 0.35) print(f"Voltage: {voltage}V → Strength adjustment: +{adjustment}") ``` -------------------------------- ### Python: Build Coyote V2 Strength Control Packet (PWM_AB2) Source: https://context7.com/lemobits/dglab-docs/llms.txt Constructs a 3-byte data packet for controlling the strength of two channels (A and B) independently. Each channel uses 11-bit resolution, and the packet is designed for the PWM_AB2 characteristic. ```python import struct import time # Connect to V2 device (UUID: 955Axxxx-0FE2-F5AA-A094-84B8D4F3E8AD) # Service: 0x180B, Characteristic: 0x1504 (PWM_AB2) def build_strength_packet(strength_a, strength_b): """ Build 3-byte strength control packet Byte 2: [Reserved(2) | A_high(6)] Byte 1: [A_low(5) | B_high(3)] Byte 0: [B_low(8)] strength_a, strength_b: 0-2047 (displayed value = strength / 7) """ # Clamp values strength_a = max(0, min(2047, strength_a)) strength_b = max(0, min(2047, strength_b)) # Pack into 24 bits: [00 | AAAAAAAAAAA | BBBBBBBBBBB] bits = (strength_a << 11) | strength_b # Convert to 3 bytes (big-endian) byte2 = (bits >> 16) & 0xFF byte1 = (bits >> 8) & 0xFF byte0 = bits & 0xFF return bytes([byte2, byte1, byte0]) # Example: Set channel A to 140 (displayed as 20), channel B to 70 (displayed as 10) packet = build_strength_packet(140, 70) print(f"Strength packet: {packet.hex()}") # Output: hex representation # Write to PWM_AB2 characteristic # await pwm_ab2_char.write_value(packet) # Example: Gradually increase strength for app_value in range(0, 50): # App displays 0-50 actual_value = app_value * 7 packet = build_strength_packet(actual_value, actual_value) # await pwm_ab2_char.write_value(packet) time.sleep(0.1) ``` -------------------------------- ### Coyote V3 Bluetooth Protocol - Frequency Mapping Source: https://context7.com/lemobits/dglab-docs/llms.txt Maps human-readable frequency (10-1000 Hz) to protocol byte values (10-240) using a compression algorithm. Includes functions for both frequency to byte mapping and reverse mapping. ```APIDOC ## Coyote V3 Bluetooth Protocol - Frequency Mapping ### Description Maps human-readable frequency (10-1000 Hz) to protocol byte values (10-240) using a compression algorithm. ### Functions - **mapFrequency(inputHz: Int): Int**: Converts a frequency in Hz to a byte value. - **byteToFrequency(byteValue: Int): Int**: Converts a byte value back to a frequency in Hz. ### Frequency to Byte Mapping Logic - **10-100 Hz**: `periodMs = 1000 / inputHz`, `byteValue = periodMs` (1:1 mapping) - **~1.67-10 Hz**: `periodMs = 1000 / inputHz`, `byteValue = (periodMs - 100) / 5 + 100` (1:5 compression) - **~1-1.67 Hz**: `periodMs = 1000 / inputHz`, `byteValue = (periodMs - 600) / 10 + 200` (1:10 compression) - **Default Fallback**: Returns `10` ### Byte to Frequency Mapping Logic - **10-100**: `periodMs = byteValue`, `frequencyHz = 1000 / periodMs` - **101-200**: `periodMs = (byteValue - 100) * 5 + 100`, `frequencyHz = 1000 / periodMs` - **201-240**: `periodMs = (byteValue - 200) * 10 + 600`, `frequencyHz = 1000 / periodMs` - **Default Fallback**: Returns `1000 / 10 = 100 Hz` ### Request Example (Kotlin) ```kotlin fun mapFrequency(inputHz: Int): Int { val periodMs = 1000 / inputHz return when (periodMs) { in 10..100 -> periodMs // 1:1 mapping for low frequencies in 101..600 -> (periodMs - 100) / 5 + 100 // 1:5 compression for mid frequencies in 601..1000 -> (periodMs - 600) / 10 + 200 // 1:10 compression for high frequencies else -> 10 // Default fallback } } println(mapFrequency(100)) // 100 Hz -> 10ms period -> byte value 100 println(mapFrequency(10)) // 10 Hz -> 100ms period -> byte value 100 println(mapFrequency(1)) // ~1.5 Hz -> 650ms period -> byte value 204 ``` ### Response Example (Kotlin) ```kotlin fun byteToFrequency(byteValue: Int): Int { val periodMs = when (byteValue) { in 10..100 -> byteValue in 101..200 -> (byteValue - 100) * 5 + 100 in 201..240 -> (byteValue - 200) * 10 + 600 else -> 10 } return 1000 / periodMs } println(byteToFrequency(100)) // Byte 100 -> 100ms period -> 10 Hz println(byteToFrequency(204)) // Byte 204 -> 650ms period -> ~1.5 Hz ``` ``` -------------------------------- ### Coyote V2 Bluetooth Protocol - Waveform Control (PWM_A34/B34) Source: https://context7.com/lemobits/dglab-docs/llms.txt Sends waveform parameters to maintain continuous pulse output. Parameters include pulse duration, gap duration, and pulse width. ```APIDOC ## Coyote V2 Bluetooth Protocol - Waveform Control (PWM_A34/B34) ### Description Sends waveform parameters every 100ms to maintain continuous pulse output using X/Y/Z values. This is used for PWM_A34 and PWM_B34 characteristics. ### Method POST (Write to Characteristic) ### Endpoint Connect to V2 device (UUID: 955Axxxx-0FE2-F5AA-A094-84B8D4F3E8AD) Characteristic: 0x1505 (PWM_A34) or 0x1506 (PWM_B34) ### Parameters #### Request Body A 3-byte packet is constructed as follows: - **Byte 2**: `[Reserved(4) | Z_high(4)]` - **Byte 1**: `[Z_low(1) | Y_high(7)]` - **Byte 0**: `[Y_low(3) | X(5)]` Where: - **X**: Pulse duration (0-31 ms) - controls the duration of individual pulses. - **Y**: Gap duration (0-1023 ms) - controls the silence duration after each pulse. - **Z**: Pulse width (0-31) - determines the actual pulse width, where `pulse_width = z * 5us`. ### Frequency Calculation The frequency is determined by the X and Y values: `Frequency = 1000 / (X + Y) Hz`. ### Request Example (Python) ```python import struct import math import time def build_waveform_packet(x, y, z): """ Build 3-byte waveform packet for PWM_A34 or PWM_B34 Byte 2: [Reserved(4) | Z_high(4)] Byte 1: [Z_low(1) | Y_high(7)] Byte 0: [Y_low(3) | X(5)] x: pulse duration (0-31 ms) y: gap duration (0-1023 ms) z: pulse width (0-31) - actual width = z * 5us """ # Clamp values x = max(0, min(31, x)) y = max(0, min(1023, y)) z = max(0, min(31, z)) # Pack into 24 bits: [RRRR | ZZZZZ | YYYYYYYYYY | XXXXX] bits = (z << 15) | (y << 5) | x byte2 = (bits >> 16) & 0xFF byte1 = (bits >> 8) & 0xFF byte0 = bits & 0xFF return bytes([byte2, byte1, byte0]) def frequency_to_xy(freq_hz): """Recommended algorithm for natural feel""" x = int(math.sqrt(freq_hz / 1000.0) * 15) y = freq_hz - x return max(0, min(31, x)), max(0, min(1023, y)) # Example 1: Create 100 Hz waveform x, y = frequency_to_xy(100) # Returns approximately (5, 95) z = 20 # Strong pulse width packet_100hz = build_waveform_packet(x, y, z) print(f"100 Hz packet: {packet_100hz.hex()}") # Example 2: Frequency ramp (engine acceleration effect) frequencies = [7, 8, 10, 14, 25, 50, 100] for freq in frequencies: x, y = frequency_to_xy(freq) packet = build_waveform_packet(x, y, 20) # await pwm_a34_char.write_value(packet) time.sleep(0.1) # Send every 100ms ``` ### Response Example This endpoint does not return a response body upon successful write. Success is indicated by the absence of an error. #### Error Handling - If the write operation fails (e.g., due to incorrect data format or connection issues), an error will be returned by the Bluetooth stack. ### CRITICAL USAGE NOTE **Must send waveform packet every 100ms to maintain output.** If sending stops, device output stops immediately. ``` -------------------------------- ### Send Control Commands via WebSocket to DG-LAB App Source: https://context7.com/lemobits/dglab-docs/llms.txt Sends various control commands to the DG-LAB app, including strength adjustments, waveform data, queue clearing, heartbeats, and disconnect requests. It requires the `appId` obtained during the binding process and the WebSocket instance `ws`. Commands have specific message formats and length limitations. ```javascript let appId = null; // Set this when bind succeeds // 1. Increase channel A strength by 10 function increaseStrength(channel, amount) { const message = { type: 'msg', clientId: appId, targetId: clientId, message: `strength-${channel}+1+${amount}` }; ws.send(JSON.stringify(message)); } increaseStrength(1, 10); // Increase A by 10 // 2. Set channel B to absolute value 50 function setAbsoluteStrength(channel, value) { const message = { type: 'msg', clientId: appId, targetId: clientId, message: `strength-${channel}+2+${value}` }; ws.send(JSON.JSON.stringify(message)); } setAbsoluteStrength(2, 50); // Set B to 50 // 3. Send waveform data (V3 protocol format) function sendWaveform(channel, hexDataArray) { // hexDataArray: array of 8-byte hex strings (each represents 100ms) // Maximum 100 elements per send (10 seconds) if (hexDataArray.length > 100) { console.error('Waveform array too large, max 100 elements'); return; } const message = { type: 'msg', clientId: appId, targetId: clientId, message: `pulse-${channel}:${JSON.stringify(hexDataArray)}` }; // Check message length (max 1950 chars) const msgStr = JSON.stringify(message); if (msgStr.length > 1950) { console.error('Message too long:', msgStr.length); return; } ws.send(msgStr); } // Example: Send 1 second of 50Hz waveform to channel A // Each hex string = frequency(2) + intensity(2) repeated 4 times const waveformData = new Array(10).fill('14006400140064001400640014006400'); sendWaveform('A', waveformData); // 4. Clear waveform queue function clearQueue(channel) { const channelNum = channel === 'A' ? 1 : 2; const message = { type: 'msg', clientId: appId, targetId: clientId, message: `clear-${channelNum}` }; ws.send(JSON.stringify(message)); } clearQueue('A'); // Clear channel A queue // 5. Send heartbeat to keep connection alive setInterval(() => { const heartbeat = { type: 'heartbeat', clientId: clientId, targetId: appId || '', message: 'ping' }; ws.send(JSON.stringify(heartbeat)); }, 30000); // Every 30 seconds // 6. Disconnect gracefully function disconnect() { const breakMsg = { type: 'break', clientId: clientId, targetId: appId, message: 'disconnect' }; ws.send(JSON.stringify(breakMsg)); ws.close(); } ``` -------------------------------- ### JavaScript: Coyote V3 Bluetooth Core Control Command (B0) Source: https://context7.com/lemobits/dglab-docs/llms.txt Sends the B0 command to control waveform output and strength for both channels simultaneously. Requires Bluetooth Web API access and specific GATT services/characteristics. Outputs strength feedback via notifications. ```javascript const device = await navigator.bluetooth.requestDevice({ filters: [{ name: '47L121000' }], optionalServices: ['0000180c-0000-1000-8000-00805f9b34fb'] }); const server = await device.gatt.connect(); const service = await server.getPrimaryService('0000180c-0000-1000-8000-00805f9b34fb'); const writeChar = await service.getCharacteristic('0000150a-0000-1000-8000-00805f9b34fb'); const notifyChar = await service.getCharacteristic('0000150b-0000-1000-8000-00805f9b34fb'); await notifyChar.startNotifications(); notifyChar.addEventListener('characteristicvaluechanged', (event) => { const data = new Uint8Array(event.target.value.buffer); if (data[0] === 0xB1) { console.log(`Strength feedback - Seq: ${data[1]}, A: ${data[2]}, B: ${data[3]}`); } }); function buildB0Command(seq, modeA, modeB, strengthA, strengthB, waveformA, waveformB) { const packet = new Uint8Array(20); packet[0] = 0xB0; packet[1] = (seq << 4) | (modeA << 2) | modeB; packet[2] = strengthA; packet[3] = strengthB; waveformA.frequencies.forEach((freq, i) => packet[4 + i] = freq); waveformA.intensities.forEach((int, i) => packet[8 + i] = int); waveformB.frequencies.forEach((freq, i) => packet[12 + i] = freq); waveformB.intensities.forEach((int, i) => packet[16 + i] = int); return packet; } const waveA = { frequencies: [10, 20, 30, 40], intensities: [30, 40, 50, 60] }; const waveB = { frequencies: [0, 0, 0, 0], intensities: [101, 101, 101, 101] }; const command = buildB0Command( 1, 0b11, 0b00, 25, 0, waveA, waveB ); setInterval(() => { writeChar.writeValue(command); }, 100); ``` -------------------------------- ### Coyote V2 Bluetooth Protocol - Strength Control (PWM_AB2) Source: https://context7.com/lemobits/dglab-docs/llms.txt Controls the strength for two channels (A and B) using a 3-byte data packet. Each channel has an 11-bit resolution. ```APIDOC ## Coyote V2 Bluetooth Protocol - Strength Control (PWM_AB2) ### Description Controls strength for both channels (A and B) using a 3-byte data packet with 11-bit resolution per channel. ### Method POST (Write to Characteristic) ### Endpoint Connect to V2 device (UUID: 955Axxxx-0FE2-F5AA-A094-84B8D4F3E8AD) Characteristic: 0x1504 (PWM_AB2) ### Parameters #### Request Body A 3-byte packet is constructed as follows: - **Byte 2**: `[Reserved(2) | A_high(6)]` - **Byte 1**: `[A_low(5) | B_high(3)]` - **Byte 0**: `[B_low(8)]` Where `strength_a` and `strength_b` are values from 0 to 2047. The displayed value is `strength / 7`. ### Request Example (Python) ```python import struct def build_strength_packet(strength_a, strength_b): """ Build 3-byte strength control packet Byte 2: [Reserved(2) | A_high(6)] Byte 1: [A_low(5) | B_high(3)] Byte 0: [B_low(8)] strength_a, strength_b: 0-2047 (displayed value = strength / 7) """ # Clamp values strength_a = max(0, min(2047, strength_a)) strength_b = max(0, min(2047, strength_b)) # Pack into 24 bits: [00 | AAAAAAAAAAA | BBBBBBBBBBB] bits = (strength_a << 11) | strength_b # Convert to 3 bytes (big-endian) byte2 = (bits >> 16) & 0xFF byte1 = (bits >> 8) & 0xFF byte0 = bits & 0xFF return bytes([byte2, byte1, byte0]) # Example: Set channel A to 140 (displayed as 20), channel B to 70 (displayed as 10) packet = build_strength_packet(140, 70) print(f"Strength packet: {packet.hex()}") # Output: hex representation # Write to PWM_AB2 characteristic # await pwm_ab2_char.write_value(packet) ``` ### Response Example This endpoint does not return a response body upon successful write. Success is indicated by the absence of an error. #### Error Handling - If the write operation fails (e.g., due to incorrect data format or connection issues), an error will be returned by the Bluetooth stack. ### Usage Notes - The device requires a continuous connection and characteristic writes to maintain strength output. - Gradually increasing strength is recommended to avoid abrupt changes. ``` ``` -------------------------------- ### WebSocket Send Control Commands Source: https://context7.com/lemobits/dglab-docs/llms.txt Sends various control commands to the DG-LAB app through the established WebSocket connection. This includes adjusting signal strength, setting absolute values, sending waveform data, clearing queues, sending heartbeats, and disconnecting. ```APIDOC ## WebSocket Control Protocol - Send Control Commands ### Description Send strength adjustment and waveform commands to DG-LAB app through WebSocket server. ### Method WebSocket (send method) ### Endpoint Existing WebSocket connection ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body JSON object with the following structure: - **type** (string) - Message type (e.g., 'msg', 'heartbeat', 'break'). - **clientId** (string) - The client ID of the control terminal. - **targetId** (string) - The ID of the DG-LAB app to control. - **message** (string or object) - The specific command or data. ### Request Examples 1. **Increase Strength:** ```javascript function increaseStrength(channel, amount) { const message = { type: 'msg', clientId: appId, // Assume appId is set after binding targetId: clientId, // Assume clientId is from initial connection message: `strength-${channel}+1+${amount}` }; ws.send(JSON.stringify(message)); } increaseStrength(1, 10); // Increase A by 10 ``` 2. **Set Absolute Strength:** ```javascript function setAbsoluteStrength(channel, value) { const message = { type: 'msg', clientId: appId, targetId: clientId, message: `strength-${channel}+2+${value}` }; ws.send(JSON.stringify(message)); } setAbsoluteStrength(2, 50); // Set B to 50 ``` 3. **Send Waveform Data:** ```javascript function sendWaveform(channel, hexDataArray) { // hexDataArray: array of 8-byte hex strings (each represents 100ms) // Maximum 100 elements per send (10 seconds) if (hexDataArray.length > 100) { console.error('Waveform array too large, max 100 elements'); return; } const message = { type: 'msg', clientId: appId, targetId: clientId, message: `pulse-${channel}:${JSON.stringify(hexDataArray)}` }; // Check message length (max 1950 chars) const msgStr = JSON.stringify(message); if (msgStr.length > 1950) { console.error('Message too long:', msgStr.length); return; } ws.send(msgStr); } // Example: Send 1 second of 50Hz waveform to channel A // Each hex string = frequency(2) + intensity(2) repeated 4 times const waveformData = new Array(10).fill('14006400140064001400640014006400'); sendWaveform('A', waveformData); ``` 4. **Clear Queue:** ```javascript function clearQueue(channel) { const channelNum = channel === 'A' ? 1 : 2; const message = { type: 'msg', clientId: appId, targetId: clientId, message: `clear-${channelNum}` }; ws.send(JSON.JSON.stringify(message)); } clearQueue('A'); // Clear channel A queue ``` 5. **Send Heartbeat:** ```javascript setInterval(() => { const heartbeat = { type: 'heartbeat', clientId: clientId, targetId: appId || '', message: 'ping' }; ws.send(JSON.stringify(heartbeat)); }, 30000); // Every 30 seconds ``` 6. **Disconnect:** ```javascript function disconnect() { const breakMsg = { type: 'break', clientId: clientId, targetId: appId, message: 'disconnect' }; ws.send(JSON.stringify(breakMsg)); ws.close(); } ``` ### Response #### Success Response (Command Acknowledgment/Execution) - Messages sent via WebSocket are typically not acknowledged with explicit success/error responses unless they are part of the 'bind' protocol or specific command feedback. Application status updates are received via `ws.onmessage`. #### Response Example (Status Update via onmessage) ```json { "type": "msg", "message": "strength-A:50/100,B:70/100" } ``` ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.