Websocket Pattern Creation

Hello

I’m trying to create a script that sends code written in Pixelblaze’s language through a WebSocket to the Pixelblaze, ensuring that the code is correctly processed and the pattern becomes fully functional.

When I attempted to implement this myself, I was able to create a new pattern that appeared on the device, but it seems to be improperly formatted and therefore not operational. Can you provide guidance or help me correct this?

import WebSocket from 'ws';
import CRC32 from 'crc-32';  

// Replace with your Pixelblaze IP address
const pixelblazeUrl = 'ws://192.168.1.93:81';  // Change to your Pixelblaze IP
const ws = new WebSocket(pixelblazeUrl);

const chunkMaxSize = 8192;  // Max size per chunk for ESP32
let outstandingPackets = 0;
let saveInProgress = null;
let sendQueue = [];

// Custom Pixelblaze pattern code (this is the real code to be sent and compiled)
const customPatternCode = `
export function render(index) {
  hsv(index / pixelCount, 1, 1); // Simple rainbow pattern
}
`;

// Logging helper
function logStep(step) {
  console.log(`\n--- ${step} ---`);
}

// On successful connection
ws.on('open', () => {
  logStep('Connected to Pixelblaze');

  // Start by saving a new custom pattern
  saveNewPattern("AAA", customPatternCode);
});

// Prepare and compile the program, send the metadata, and then transmit the pattern data
function saveNewPattern(patternName, patternCode) {
  const programBits = assembleProgramBits(patternName, patternCode);

  const crc = CRC32.buf(new Uint8Array(programBits.binary));  // Calculate CRC for the pattern data

  const message = {
    setCode: {
      size: programBits.binary.byteLength, // size of the binary in bytes
      crc: crc >>> 0,  // Ensure unsigned CRC value
      name: programBits.name  // Pattern name
    }
  };

  logStep(`Saving new pattern: ${patternName}`);

  // Send the metadata first
  ws.send(JSON.stringify(message));
  
  // Then send the actual program data in chunks
  sendBlob(programBits.binary);

  // Wait for save operation to complete, then request the program list
  setTimeout(() => {
    logStep('Requesting list of programs');
    requestProgramList();
  }, 3000);  // Wait 3 seconds to ensure program is saved
}

// Assemble the program into a binary blob ready for transmission
function assembleProgramBits(patternName, patternCode) {
  const codeBuffer = new TextEncoder().encode(patternCode);
  
  // Create a binary representation of the program (could include additional metadata)
  const binary = new Uint8Array(codeBuffer);

  return {
    name: patternName,
    binary: binary  // Return the compiled program as a Uint8Array (binary format)
  };
}

// Send the binary data in chunks to handle large payloads
function sendBlob(binaryData) {
  const totalSize = binaryData.byteLength;
  let offset = 0;

  function sendNextChunk() {
    if (offset < totalSize) {
      const chunkSize = Math.min(chunkMaxSize, totalSize - offset);
      const chunk = binaryData.slice(offset, offset + chunkSize);
      
      ws.send(chunk);
      outstandingPackets++;
      
      offset += chunkSize;
      logStep(`Sent chunk: ${offset} / ${totalSize}`);
      
      // Wait before sending the next chunk (optional delay to prevent flooding)
      setTimeout(sendNextChunk, 50);
    } else {
      logStep('All chunks sent.');
    }
  }

  sendNextChunk();
}

// Request the list of programs
function requestProgramList() {
  const message = { listPrograms: true };
  ws.send(JSON.stringify(message));
  logStep('Sent request for program list');
}

// Set a specific pattern as active by its ID
function activateProgramById(programId) {
  const message = { setProgram: programId };
  ws.send(JSON.stringify(message));
  logStep(`Activating program with ID: ${programId}`);
}

// Control brightness (0.0 to 1.0)
function setBrightness(level) {
  const message = { brightness: level };
  ws.send(JSON.stringify(message));
  logStep(`Setting brightness to ${level}`);
}

// Set variables for a specific pattern
function setPatternVariables(variables) {
  const message = { setVars: variables };
  ws.send(JSON.stringify(message));
  logStep('Setting pattern variables');
}

// On receiving a message from Pixelblaze
ws.on('message', (data) => {
    let message = data.toString();

    logStep('Received response from Pixelblaze');
    console.log('Raw message:', message);

    // Attempt to parse as JSON first
    try {
        const parsedMessage = JSON.parse(message);
        logStep('Parsed JSON response');
        console.log(parsedMessage);

        // Handle specific JSON responses like system status
        if (parsedMessage.programList) {
            logStep('Program list received');
        }
    } catch (err) {
        logStep('Failed to parse JSON response');

        // Handle non-JSON message (likely the program list)
        if (message.includes('\t')) {
            logStep('Processing raw program list');

            // Split the message by newlines to get each program
            const programEntries = message.split('\n').filter(Boolean); // Removes empty lines
            const programList = programEntries.map(entry => {
                const [id, name] = entry.split('\t');
                return { id, name };
            });

            console.log('Parsed program list:', programList);

            // Example: Find and activate the newly saved program
            const uploadedProgram = programList.find(program => program.name === "AAA");

            if (uploadedProgram) {
                logStep(`Uploaded program found: ${uploadedProgram.name} (ID: ${uploadedProgram.id})`);
                activateProgramById(uploadedProgram.id);

                // Set brightness after activating the pattern
                setTimeout(() => setBrightness(0.8), 2000);

                // Example: Set some pattern variables (customize based on the pattern)
                setTimeout(() => setPatternVariables({ variableName: 1 }), 4000);
            } else {
                logStep('Uploaded program not found in program list');
            }
        } else {
            console.log('Received non-JSON message:', message);
        }
    }
});

// On WebSocket close
ws.on('close', () => {
  logStep('Disconnected from Pixelblaze');
});

// On WebSocket error
ws.on('error', (error) => {
  logStep('Error encountered');
  console.error('WebSocket error:', error);
});

From a quick look at your code, I think the missing step is the compilation from source to byte code. I might be wrong, but it looks like saveNewPattern() is just encoding the text and sending it to the Pixelblaze as part of the blob.

So ‘binary’ needs to contain byte code compiled from your source, not the source itself. To get that, you need to run the compiler, which is normally hosted by and run in the browser. I recommend against trying to permanently embed it in another javascript app, at least not without @wizard’s explicit permission. Aside from the obvious ip issues, it’s firmware version specific and would require frequent updates.

FWIW, the Python pixelblaze-client works around this by requiring that a Pixelblaze be connected when compiling. It temporarily downloads the compiler from the Pixelblaze and runs it in a minimal headless browser emulator to generate the byte code, essentially behaving like a normal user.

Thanks @zranger1. I’ll give this a shot. Much appreciated.