p5.serial.js

// --------------------------------------------
//  Serial class for Arduino by Tetsuaki Baba (C)2022-2024
// --------------------------------------------

// Arduino CODE ------------------------------------
/*
void setup() {
  Serial.begin(9600);
  pinMode(4, OUTPUT);
}
int count = 0;
void loop() {
  Serial.write(count);
  delay(1000);
  count++;
  if ( count > 100 )count = 0;

  if( Serial.available() > 0 ){
    int value = Serial.read();
    if( value == 1){
       digitalWrite(4, HIGH);
    }
    else if( value == 2){
      digitalWrite(4, LOW);
    }
  }
}
*/


/**
 * web serial class for p5.js
 * 
 * @class Serial
 * @constructor
 * 
 */
class Serial {
    /**
     * Creates an instance of Serial.
     */
    constructor() {
        this.is_opened = false;
        this.defaultGotCSV = this.gotCSV;
        this.defaultGotByte = this.gotByte;
        this.defaultGotBytes = this.gotBytes;
        this.buffer = "";
    }

    isGotCSVOverridden() {
        return this.gotCSV != this.defaultGotCSV;
    }
    isGotByteOverridden() {
        return this.gotByte != this.defaultGotByte;
    }
    isGotBytesOverridden() {
        return this.gotBytes != this.defaultGotBytes;
    }

    /**
     * Request to open serial port
     * 
     * @async
     * @param {int}[br=9600] br - baud rate
     * 
     */
    async begin(br = 9600) {
        const filters = [
            { usbVendorId: 0x10c4 }, // grove beginner kit for Arduino
            { usbVendorId: 0x2341 }, // ArduinoSA
            { usbVendorId: 0x9025 }, // Marduino
        ];

        try {
            this.port = await navigator.serial.requestPort({ filters });
            this.portInfo = this.port.getInfo();
            await this.port.open({ baudRate: br });
            console.log(
                `vendorId: ${this.portInfo.usbVendorId} | productId: ${this.portInfo.usbProductId} `
            );
            this.is_opened = true;
            this.buffer = "";
            while (this.port.readable && this.is_opened) {
                const reader = this.port.readable.getReader();
                try {
                    while (true) {
                        const { value, done } = await reader.read();
                        // gotCSVがオーバーライドされている場合は、改行が来るまでバッファにためる
                        // If gotCSV is overridden, buffer the data until a line feed comes.
                        if (this.isGotCSVOverridden()) {
                            // 新しいデータをデコードしてバッファに追加
                            this.buffer += new TextDecoder().decode(value);

                            // バッファ内に改行が含まれているか確認
                            if (this.buffer.includes("\n")) {

                                // '\r'を削除
                                this.buffer = this.buffer.replace("\r", "");
                                // 改行でバッファを分割
                                const lines = this.buffer.split("\n");

                                // 最後の要素をバッファに保持(不完全な行の可能性があるため)
                                this.buffer = lines.pop();

                                // 各行を処理
                                // 各行を処理
                                for (let line of lines) {
                                    // カンマ区切りの文字列を配列に変換して渡す
                                    const csvArray = line.split(",");
                                    this.gotCSV(csvArray);
                                }
                            }
                        }


                        if (this.isGotByteOverridden()) {
                            for (let v of value) {
                                this.gotByte(v);
                            }
                        }


                        if (this.isGotBytesOverridden()) {
                            this.gotBytes(value);
                        }


                        if (this.is_opened == false) break;
                    }
                } catch (error) {
                    // Handle |error|...
                    console.log(error);
                    this.is_opened = false;
                } finally {
                    reader.releaseLock();
                }

            }
            this.port.close();
        } catch (ex) {
            if (ex.name === "NotFoundError") {
                console.log("Device NOT found");
                this.is_opended = false;
            } else {
                console.log(ex);
            }
        }
    }

    /**
     * 
     * @param {int} value 
     */
    async write(value) {
        if (this.is_opened == false) return;
        const writer = this.port.writable.getWriter();
        await writer.write(new Int8Array([value]));
        writer.releaseLock();
    }

    /**
     * 
     * @param {int} value 
     */
    async writeByte(value) {
        if (this.is_opened == false) return;
        const writer = this.port.writable.getWriter();
        await writer.write(new Int8Array([value]));
        writer.releaseLock();
    }

    async writeBytes(values) {
        if (this.is_opened == false) return;
        const writer = this.port.writable.getWriter();
        await writer.write(new Int8Array(values));
        writer.releaseLock();
    }


    async writeCSV(string) {
        if (this.is_opened == false) return;

        // もし文字列が空であれば何もしない
        if (string === "") {
            return;
        }
        // もし最後が改行でない場合は改行を追加する
        if (string.charAt(string.length - 1) !== "\n") {
            string += "\n";
            console.warn("added '\n'. because the last character is not '\n'.");
        }

        const writer = this.port.writable.getWriter();
        await writer.write(new TextEncoder().encode(string));
        writer.releaseLock();
    }

    /**
     * 
     * @param {string} values 
     */
    async print(values) {
        const encoder = new TextEncoder();
        const writer = this.port.writable.getWriter();
        await writer.write(encoder.encode(values));
        writer.releaseLock();
    }

    /**
     * 
     * @param {string} values 
     */
    async println(values) {
        const encoder = new TextEncoder();
        const writer = this.port.writable.getWriter();
        await writer.write(encoder.encode(values + "\n"));
        writer.releaseLock();
    }

    /**
     * Close serial port
     * 
     * @async
     */
    async close() {
        this.is_opened = false;
    }
    async read() { } // better to avoid using this method for arduino and p5 bigginers because it is a bit complicated to handle by themselves. Use callback function instead.

    /**
     * This method is called when a single value is received.
     * @param {int} value 
     */
    gotByte(value) {
    }

    /**
     * This method is called when multiple values are received.
     * @param {array} values 
     */
    gotBytes(values) {
    }

    /**
     * This method is called when a CSV string is received.
     * @param {array} array 
     */
    gotCSV(array) {
    }
}