WebSerial in p5.js

Multiple libraries exist to use WebSerial in p5.js. The one that worked best form me is p5.webserial.

To use this include:

<script src="https://unpkg.com/@gohai/p5.webserial@^1/libraries/p5.webserial.js"></script>

to your index.html file after the p5.js script inclusion.

Opening serial ports in JavaScript

You can only open a serial port in response to a user action, like a mouse click or a key press. This is a security measure to prevent malicious websites from accessing your serial ports.

If you don’t want to do this each time you open your webpage, you can use the WebSerial API to request a serial port from the user. The user can then select a serial port and the browser remembers this choice. The next time the user opens the webpage, the browser will automatically open the serial port.

let port;
const BAUDRATE = 9600;

function setup() {
  createCanvas(400, 400);
  port = createSerial();

  // fetch the list of serial ports that were previously approved
  let usedPorts = usedSerialPorts();
  if (usedPorts.length > 0) {
    port.open(usedPorts[0], BAUDRATE);
  }
  else { // if no ports have been approved yet
    // add a button to open the serial port
    let button = createButton('open serial port');
    button.mousePressed(() => {
      // open the port
      port.open(BAUDRATE); // this will show a popup window asking the user to select a serial port
    });
  }
}

From microcontroller to p5.js

To send a sensor value from your microcontroller to p5.js, you can use the Serial.println() function. This function sends a string of characters to the serial port. You can then use the Serial.readString() function in p5.js to read this string of characters.

The following example sends the value of an analog sensor connected to pin A0 to the serial port. The p5.js program reads this value and displays it on the screen.

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);  
}

void loop() {
  // put your main code here, to run repeatedly:
  Serial.println(analogRead(A0));
  delay(10);
}

Then reading the serial port in p5.js:

// Assuming you have run the setup function from the previous example

function draw() {
  background(220);
  while (port.available() > 0) {
    let data = port.readUntil('\n');
    if (data !== null) {
      let sensorValue = Number(data);
      text(sensorValue, 10, 10);
    }
  }
}

Note: in many examples you will see the following code to check if there is data available on the serial port:

if (port.available() > 0) {
    // read the data
}

Be careful though, this code will only read one character from the serial port. If you want to read the entire string, you need to use a while loop:

while (port.available() > 0) {
    // read the data
}

From p5.js to microcontroller

To send data from p5.js to your microcontroller, you can use the Serial.write() function. This function sends a single byte to the serial port. You can then use the Serial.read() function in your microcontroller to read this byte.

To read binary messages in your Arduino code you will have to know what each byte means. As an example, lets make a web-based color picker for an RGB LED. The color picker will send the RGB values to the microcontroller. The microcontroller will then set the color of the LED.

The p5 application sends a message containing the color information to the microcontroller. A single message consists of 4 bytes:

Byte 1 Byte 2 Byte 3 Byte 4
RedValue GreenValue BlueValue End indicator (’e')

Whenever the microcontroller receives the end indicator, it knows that it has received a complete message. It can then set the color of the LED.

Using an RGB LED with the Adafruit NeoPixel library this would look like this:

#include <Adafruit_NeoPixel.h>
// Define the pins used for the NeoPixel
#define PIN D2
#define NUMPIXELS 3

// Create a NeoPixel object
Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GBR + NEO_KHZ800);

// The input buffer to store the received bytes
uint8_t inputBuffer[4];
// A counter to keep track of how many bytes have been received
uint8_t byteCounter = 0;

void setup() {
  pixels.begin();
  pixels.clear();
  pixels.show();
  Serial.begin(9600);
}

void loop() {
  // put your main code here, to run repeatedly:

  // Check if there is data available on the serial port
  while (Serial.available()){
    // Read one byte
    uint8_t inputByte = Serial.read();
    // Check if the byte is the end indicator
    if (inputByte == 'e' && byteCounter > 2){
      // reset the byte counter
      byteCounter = 0;
      // set the color of the LED
      pixels.fill((*(uint32_t *)inputBuffer));
      pixels.show();
    }
    else{
      // store the byte in the input buffer
      inputBuffer[byteCounter++] = inputByte;
    }
  }
}

Sourced