diff --git "a/extensions/Darren_liu/\345\210\233\345\256\242+" "b/extensions/Darren_liu/\345\210\233\345\256\242+"
new file mode 100644
index 0000000000..d8e7fff14b
--- /dev/null
+++ "b/extensions/Darren_liu/\345\210\233\345\256\242+"
@@ -0,0 +1,1424 @@
+// MakerPlus Serial Hardware Extension
+// Copyright (c) 2026 Darrenliu09
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+(function (Scratch) {
+ 'use strict';
+
+ class MakerPlusExtension {
+ constructor() {
+ this.port = null;
+ this.reader = null;
+ this.writer = null;
+ this.decoder = new TextDecoder();
+ this.encoder = new TextEncoder();
+ this.sensorData = {};
+ this.isConnected = false;
+ this.boardType = '';
+ this.readLoop = this.readLoop.bind(this);
+ this.language = this.detectLanguage();
+ }
+
+ detectLanguage() {
+ const lang = navigator.language || navigator.userLanguage;
+ if (lang.startsWith('zh')) return 'zh';
+ return 'en';
+ }
+
+ getStrings() {
+ const zh = {
+ name: '创客+',
+ openCodeEditor: '打开代码编辑器',
+ runCode: '运行代码',
+ selectBoard: '选择开发板 [BOARD]',
+ currentBoard: '当前开发板',
+ connect: '连接设备 [BAUD] 波特率',
+ disconnect: '断开连接',
+ isConnected: '设备已连接?',
+ setPinMode: '设置引脚 [PIN] 为 [MODE]',
+ digitalWrite: '引脚 [PIN] 输出 [VALUE]',
+ digitalRead: '读取引脚 [PIN] 数字值',
+ analogRead: '读取引脚 [PIN] 模拟值',
+ analogWrite: '引脚 [PIN] PWM输出 [VALUE]',
+ sendSerial: '发送串口数据 [DATA]',
+ readSerial: '读取串口数据',
+ servoWrite: '舵机引脚 [PIN] 转到 [ANGLE]度',
+ delay: '延迟 [MS] 毫秒',
+ pinInfo: '引脚信息 [PIN]',
+ input: '输入',
+ output: '输出',
+ inputPullup: '上拉输入',
+ inputPulldown: '下拉输入',
+ high: '高电平',
+ low: '低电平',
+ openDisplayEditor: '打开显示屏编辑器',
+ sendDisplayData: '发送显示数据',
+ clearDisplay: '清空显示屏',
+ displayText: '显示文字 [TEXT]',
+ displayImage: '显示图片 [URL]',
+ startCamera: '启动摄像头',
+ stopCamera: '停止摄像头',
+ setBrightness: '设置亮度 [VALUE]',
+ brightness: '亮度'
+ };
+
+ const en = {
+ name: 'Maker+',
+ openCodeEditor: 'Open Code Editor',
+ runCode: 'Run Code',
+ selectBoard: 'Select Board [BOARD]',
+ currentBoard: 'Current Board',
+ connect: 'Connect at [BAUD] baud',
+ disconnect: 'Disconnect',
+ isConnected: 'Connected?',
+ setPinMode: 'Set pin [PIN] to [MODE]',
+ digitalWrite: 'Pin [PIN] write [VALUE]',
+ digitalRead: 'Read pin [PIN] digital',
+ analogRead: 'Read pin [PIN] analog',
+ analogWrite: 'Pin [PIN] PWM [VALUE]',
+ sendSerial: 'Send serial [DATA]',
+ readSerial: 'Read serial data',
+ servoWrite: 'Servo pin [PIN] to [ANGLE]deg',
+ delay: 'Wait [MS] ms',
+ pinInfo: 'Pin info [PIN]',
+ input: 'Input',
+ output: 'Output',
+ inputPullup: 'Input Pullup',
+ inputPulldown: 'Input Pulldown',
+ high: 'High',
+ low: 'Low',
+ openDisplayEditor: 'Open Display Editor',
+ sendDisplayData: 'Send Display Data',
+ clearDisplay: 'Clear Display',
+ displayText: 'Display Text [TEXT]',
+ displayImage: 'Display Image [URL]',
+ startCamera: 'Start Camera',
+ stopCamera: 'Stop Camera',
+ setBrightness: 'Set Brightness [VALUE]',
+ brightness: 'Brightness'
+ };
+
+ return this.language === 'zh' ? zh : en;
+ }
+
+ getInfo() {
+ const s = this.getStrings();
+
+ return {
+ id: 'makerplus',
+ name: s.name,
+ color1: '#4a90d9',
+ color2: '#3a7bc8',
+ color3: '#2a5ba8',
+ docsURI: 'https://docs.turbowarp.org/',
+ blocks: [
+ {
+ opcode: 'openCodeEditor',
+ blockType: Scratch.BlockType.COMMAND,
+ text: s.openCodeEditor
+ },
+ {
+ opcode: 'runCode',
+ blockType: Scratch.BlockType.COMMAND,
+ text: s.runCode,
+ arguments: {
+ CODE: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: 'digitalWrite(2, HIGH)'
+ }
+ }
+ },
+ {
+ opcode: 'selectBoard',
+ blockType: Scratch.BlockType.COMMAND,
+ text: s.selectBoard,
+ arguments: {
+ BOARD: {
+ type: Scratch.ArgumentType.STRING,
+ menu: 'BOARD_TYPES'
+ }
+ }
+ },
+ {
+ opcode: 'getCurrentBoard',
+ blockType: Scratch.BlockType.REPORTER,
+ text: s.currentBoard
+ },
+ '---',
+ {
+ opcode: 'connect',
+ blockType: Scratch.BlockType.COMMAND,
+ text: s.connect,
+ arguments: {
+ BAUD: {
+ type: Scratch.ArgumentType.STRING,
+ menu: 'BAUD_RATES',
+ defaultValue: '115200'
+ }
+ }
+ },
+ {
+ opcode: 'disconnect',
+ blockType: Scratch.BlockType.COMMAND,
+ text: s.disconnect
+ },
+ {
+ opcode: 'checkConnected',
+ blockType: Scratch.BlockType.BOOLEAN,
+ text: s.isConnected
+ },
+ '---',
+ {
+ opcode: 'setPinMode',
+ blockType: Scratch.BlockType.COMMAND,
+ text: s.setPinMode,
+ arguments: {
+ PIN: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 2
+ },
+ MODE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: 'PIN_MODES'
+ }
+ }
+ },
+ {
+ opcode: 'digitalWrite',
+ blockType: Scratch.BlockType.COMMAND,
+ text: s.digitalWrite,
+ arguments: {
+ PIN: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 2
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.STRING,
+ menu: 'DIGITAL_VALUES'
+ }
+ }
+ },
+ {
+ opcode: 'digitalRead',
+ blockType: Scratch.BlockType.REPORTER,
+ text: s.digitalRead,
+ arguments: {
+ PIN: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 0
+ }
+ }
+ },
+ {
+ opcode: 'analogRead',
+ blockType: Scratch.BlockType.REPORTER,
+ text: s.analogRead,
+ arguments: {
+ PIN: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 34
+ }
+ }
+ },
+ {
+ opcode: 'analogWrite',
+ blockType: Scratch.BlockType.COMMAND,
+ text: s.analogWrite,
+ arguments: {
+ PIN: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 5
+ },
+ VALUE: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 128
+ }
+ }
+ },
+ {
+ opcode: 'servoWrite',
+ blockType: Scratch.BlockType.COMMAND,
+ text: s.servoWrite,
+ arguments: {
+ PIN: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 15
+ },
+ ANGLE: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 90
+ }
+ }
+ },
+ {
+ opcode: 'delay',
+ blockType: Scratch.BlockType.COMMAND,
+ text: s.delay,
+ arguments: {
+ MS: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 1000
+ }
+ }
+ },
+ '---',
+ {
+ opcode: 'sendSerial',
+ blockType: Scratch.BlockType.COMMAND,
+ text: s.sendSerial,
+ arguments: {
+ DATA: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: 'hello'
+ }
+ }
+ },
+ {
+ opcode: 'readSerial',
+ blockType: Scratch.BlockType.REPORTER,
+ text: s.readSerial
+ },
+ {
+ opcode: 'pinInfo',
+ blockType: Scratch.BlockType.REPORTER,
+ text: s.pinInfo,
+ arguments: {
+ PIN: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 2
+ }
+ }
+ },
+ '---',
+ {
+ opcode: 'openDisplayEditor',
+ blockType: Scratch.BlockType.COMMAND,
+ text: s.openDisplayEditor
+ },
+ {
+ opcode: 'sendDisplayData',
+ blockType: Scratch.BlockType.COMMAND,
+ text: s.sendDisplayData
+ },
+ {
+ opcode: 'clearDisplay',
+ blockType: Scratch.BlockType.COMMAND,
+ text: s.clearDisplay
+ },
+ {
+ opcode: 'displayText',
+ blockType: Scratch.BlockType.COMMAND,
+ text: s.displayText,
+ arguments: {
+ TEXT: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: 'Hello'
+ }
+ }
+ },
+ {
+ opcode: 'displayImage',
+ blockType: Scratch.BlockType.COMMAND,
+ text: s.displayImage,
+ arguments: {
+ URL: {
+ type: Scratch.ArgumentType.STRING,
+ defaultValue: 'https://example.com/image.png'
+ }
+ }
+ },
+ {
+ opcode: 'startCamera',
+ blockType: Scratch.BlockType.COMMAND,
+ text: s.startCamera
+ },
+ {
+ opcode: 'stopCamera',
+ blockType: Scratch.BlockType.COMMAND,
+ text: s.stopCamera
+ },
+ {
+ opcode: 'setBrightness',
+ blockType: Scratch.BlockType.COMMAND,
+ text: s.setBrightness,
+ arguments: {
+ VALUE: {
+ type: Scratch.ArgumentType.NUMBER,
+ defaultValue: 100
+ }
+ }
+ }
+ ],
+ menus: {
+ BAUD_RATES: {
+ acceptReporters: false,
+ items: ['9600', '19200', '38400', '57600', '115200', '230400']
+ },
+ BOARD_TYPES: {
+ acceptReporters: false,
+ items: this.language === 'zh'
+ ? ['ESP32', 'STM32', '树莓派', '香橙派', 'Arduino', 'NodeMCU']
+ : ['ESP32', 'STM32', 'Raspberry Pi', 'Orange Pi', 'Arduino', 'NodeMCU']
+ },
+ PIN_MODES: {
+ acceptReporters: false,
+ items: this.language === 'zh'
+ ? ['输入', '输出', '上拉输入', '下拉输入']
+ : ['Input', 'Output', 'Input Pullup', 'Input Pulldown']
+ },
+ DIGITAL_VALUES: {
+ acceptReporters: false,
+ items: this.language === 'zh'
+ ? ['高电平', '低电平']
+ : ['High', 'Low']
+ }
+ }
+ };
+ }
+
+ openCodeEditor() {
+ const html = `
+
+
+
+
+
+
+
+
+
+Tip: Use semicolons or newlines to separate commands
+Output will appear here...
+
+
+`;
+
+ const blob = new Blob([html], { type: 'text/html;charset=utf-8' });
+ const url = URL.createObjectURL(blob);
+ window.open(url, '_blank', 'width=800,height=600,top=100,left=100');
+ }
+
+ openDisplayEditor() {
+ const html = `
+
+
+
+
+
+
+
+
+
+
RGB Color Picker
+
+
+
+
+
+
+
+
+
Image Upload
+
+
+
+
+
![]()
+
+
+
+
+
Camera Stream
+
+
+
+
+
+
+
+
+
+
Brightness Control
+
+
Brightness
+
+
+
+
+
+
+
+`;
+
+ const blob = new Blob([html], { type: 'text/html;charset=utf-8' });
+ const url = URL.createObjectURL(blob);
+ window.open(url, '_blank', 'width=1200,height=500,top=100,left=100');
+ }
+
+ runCode(args) {
+ if (!this.isConnected) return;
+ const code = args.CODE;
+ const commands = code.split(/[;\n]/).filter(c => c.trim());
+ commands.forEach(cmd => {
+ this.sendCommand(`CODE:${cmd.trim()}`);
+ });
+ }
+
+ selectBoard(args) {
+ this.boardType = args.BOARD;
+ }
+
+ getCurrentBoard() {
+ return this.boardType || (this.language === 'zh' ? '未选择' : 'Not selected');
+ }
+
+ async connect(args) {
+ try {
+ this.port = await navigator.serial.requestPort();
+ const baudRate = parseInt(args.BAUD);
+ await this.port.open({ baudRate: baudRate });
+
+ this.writer = this.port.writable.getWriter();
+ this.isConnected = true;
+
+ await this.sendCommand('INIT');
+ await this.sendCommand(`BOARD:${this.boardType}`);
+
+ this.readLoop();
+
+ return new Promise(resolve => setTimeout(resolve, 500));
+ } catch (error) {
+ console.error('Connection failed:', error);
+ this.isConnected = false;
+ }
+ }
+
+ async disconnect() {
+ try {
+ if (this.reader) {
+ await this.reader.cancel();
+ this.reader = null;
+ }
+ if (this.writer) {
+ await this.writer.close();
+ this.writer = null;
+ }
+ if (this.port) {
+ await this.port.close();
+ this.port = null;
+ }
+ this.isConnected = false;
+ this.sensorData = {};
+ } catch (error) {
+ console.error('Disconnect failed:', error);
+ }
+ }
+
+ checkConnected() {
+ return this.isConnected;
+ }
+
+ async setPinMode(args) {
+ if (!this.isConnected) return;
+ const pin = args.PIN;
+ const mode = args.MODE;
+ let modeCode = 'OUTPUT';
+
+ if (mode === '输入' || mode === 'Input') modeCode = 'INPUT';
+ else if (mode === '输出' || mode === 'Output') modeCode = 'OUTPUT';
+ else if (mode === '上拉输入' || mode === 'Input Pullup') modeCode = 'INPUT_PULLUP';
+ else if (mode === '下拉输入' || mode === 'Input Pulldown') modeCode = 'INPUT_PULLDOWN';
+
+ await this.sendCommand(`PIN_MODE:${pin}:${modeCode}`);
+ }
+
+ async digitalWrite(args) {
+ if (!this.isConnected) return;
+ const pin = args.PIN;
+ const value = (args.VALUE === '高电平' || args.VALUE === 'High') ? 'HIGH' : 'LOW';
+ await this.sendCommand(`DIGITAL_WRITE:${pin}:${value}`);
+ }
+
+ async digitalRead(args) {
+ if (!this.isConnected) return 0;
+ const pin = args.PIN;
+ await this.sendCommand(`DIGITAL_READ:${pin}`);
+ await new Promise(resolve => setTimeout(resolve, 100));
+ return this.sensorData[`D${pin}`] || 0;
+ }
+
+ async analogRead(args) {
+ if (!this.isConnected) return 0;
+ const pin = args.PIN;
+ await this.sendCommand(`ANALOG_READ:${pin}`);
+ await new Promise(resolve => setTimeout(resolve, 100));
+ return this.sensorData[`A${pin}`] || 0;
+ }
+
+ async analogWrite(args) {
+ if (!this.isConnected) return;
+ const pin = args.PIN;
+ const value = args.VALUE;
+ await this.sendCommand(`ANALOG_WRITE:${pin}:${value}`);
+ }
+
+ async servoWrite(args) {
+ if (!this.isConnected) return;
+ const pin = args.PIN;
+ const angle = args.ANGLE;
+ await this.sendCommand(`SERVO_WRITE:${pin}:${angle}`);
+ }
+
+ async delay(args) {
+ const ms = args.MS;
+ return new Promise(resolve => setTimeout(resolve, ms));
+ }
+
+ async sendSerial(args) {
+ if (!this.isConnected) return;
+ const data = args.DATA;
+ await this.sendCommand(`SERIAL_SEND:${data}`);
+ }
+
+ readSerial() {
+ return this.sensorData['SERIAL'] || '';
+ }
+
+ pinInfo(args) {
+ const pin = args.PIN;
+ const pinMaps = {
+ ESP32: {
+ zh: {
+ 0: 'GPIO0 - 启动按钮',
+ 1: 'GPIO1 - TXD0',
+ 2: 'GPIO2 - 内置LED',
+ 3: 'GPIO3 - RXD0',
+ 4: 'GPIO4 - 通用IO',
+ 5: 'GPIO5 - SPICLK',
+ 12: 'GPIO12 - SPIMISO',
+ 13: 'GPIO13 - SPIMOSI',
+ 14: 'GPIO14 - SPICS0',
+ 15: 'GPIO15 - SPICS1',
+ 21: 'GPIO21 - I2C SDA',
+ 22: 'GPIO22 - I2C SCL',
+ 34: 'GPIO34 - ADC(仅输入)',
+ 35: 'GPIO35 - ADC(仅输入)',
+ 36: 'GPIO36 - ADC(仅输入)',
+ 39: 'GPIO39 - ADC(仅输入)'
+ },
+ en: {
+ 0: 'GPIO0 - Boot Button',
+ 1: 'GPIO1 - TXD0',
+ 2: 'GPIO2 - Built-in LED',
+ 3: 'GPIO3 - RXD0',
+ 4: 'GPIO4 - General IO',
+ 5: 'GPIO5 - SPICLK',
+ 12: 'GPIO12 - SPIMISO',
+ 13: 'GPIO13 - SPIMOSI',
+ 14: 'GPIO14 - SPICS0',
+ 15: 'GPIO15 - SPICS1',
+ 21: 'GPIO21 - I2C SDA',
+ 22: 'GPIO22 - I2C SCL',
+ 34: 'GPIO34 - ADC(input only)',
+ 35: 'GPIO35 - ADC(input only)',
+ 36: 'GPIO36 - ADC(input only)',
+ 39: 'GPIO39 - ADC(input only)'
+ }
+ },
+ STM32: {
+ zh: {
+ PA0: 'PA0 - ADC1_CH0',
+ PA1: 'PA1 - ADC1_CH1',
+ PA2: 'PA2 - USART2_TX',
+ PA3: 'PA3 - USART2_RX',
+ PA4: 'PA4 - SPI1_NSS',
+ PB0: 'PB0 - ADC1_CH8',
+ PB1: 'PB1 - ADC1_CH9',
+ PB6: 'PB6 - I2C1_SCL',
+ PB7: 'PB7 - I2C1_SDA',
+ PC13: 'PC13 - 内置LED'
+ },
+ en: {
+ PA0: 'PA0 - ADC1_CH0',
+ PA1: 'PA1 - ADC1_CH1',
+ PA2: 'PA2 - USART2_TX',
+ PA3: 'PA3 - USART2_RX',
+ PA4: 'PA4 - SPI1_NSS',
+ PB0: 'PB0 - ADC1_CH8',
+ PB1: 'PB1 - ADC1_CH9',
+ PB6: 'PB6 - I2C1_SCL',
+ PB7: 'PB7 - I2C1_SDA',
+ PC13: 'PC13 - Built-in LED'
+ }
+ },
+ '树莓派': {
+ zh: {
+ 1: 'GPIO1 - I2C SDA',
+ 2: 'GPIO2 - I2C SCL',
+ 3: 'GPIO3 - UART TX',
+ 4: 'GPIO4 - UART RX',
+ 14: 'GPIO14 - SPI SCK',
+ 15: 'GPIO15 - SPI MISO',
+ 16: 'GPIO16 - SPI MOSI',
+ 18: 'GPIO18 - PWM0',
+ 22: 'GPIO22 - 通用IO',
+ 23: 'GPIO23 - 通用IO'
+ },
+ en: {
+ 1: 'GPIO1 - I2C SDA',
+ 2: 'GPIO2 - I2C SCL',
+ 3: 'GPIO3 - UART TX',
+ 4: 'GPIO4 - UART RX',
+ 14: 'GPIO14 - SPI SCK',
+ 15: 'GPIO15 - SPI MISO',
+ 16: 'GPIO16 - SPI MOSI',
+ 18: 'GPIO18 - PWM0',
+ 22: 'GPIO22 - General IO',
+ 23: 'GPIO23 - General IO'
+ }
+ },
+ 'Raspberry Pi': {
+ zh: {
+ 1: 'GPIO1 - I2C SDA',
+ 2: 'GPIO2 - I2C SCL',
+ 3: 'GPIO3 - UART TX',
+ 4: 'GPIO4 - UART RX',
+ 14: 'GPIO14 - SPI SCK',
+ 15: 'GPIO15 - SPI MISO',
+ 16: 'GPIO16 - SPI MOSI',
+ 18: 'GPIO18 - PWM0',
+ 22: 'GPIO22 - 通用IO',
+ 23: 'GPIO23 - 通用IO'
+ },
+ en: {
+ 1: 'GPIO1 - I2C SDA',
+ 2: 'GPIO2 - I2C SCL',
+ 3: 'GPIO3 - UART TX',
+ 4: 'GPIO4 - UART RX',
+ 14: 'GPIO14 - SPI SCK',
+ 15: 'GPIO15 - SPI MISO',
+ 16: 'GPIO16 - SPI MOSI',
+ 18: 'GPIO18 - PWM0',
+ 22: 'GPIO22 - General IO',
+ 23: 'GPIO23 - General IO'
+ }
+ },
+ '香橙派': {
+ zh: {
+ 0: 'PA0 - UART TX',
+ 1: 'PA1 - UART RX',
+ 2: 'PA2 - I2C SDA',
+ 3: 'PA3 - I2C SCL',
+ 6: 'PA6 - SPI CS',
+ 7: 'PA7 - SPI SCK',
+ 8: 'PA8 - SPI MISO',
+ 9: 'PA9 - SPI MOSI',
+ 10: 'PA10 - PWM',
+ 11: 'PA11 - 通用IO'
+ },
+ en: {
+ 0: 'PA0 - UART TX',
+ 1: 'PA1 - UART RX',
+ 2: 'PA2 - I2C SDA',
+ 3: 'PA3 - I2C SCL',
+ 6: 'PA6 - SPI CS',
+ 7: 'PA7 - SPI SCK',
+ 8: 'PA8 - SPI MISO',
+ 9: 'PA9 - SPI MOSI',
+ 10: 'PA10 - PWM',
+ 11: 'PA11 - General IO'
+ }
+ },
+ 'Orange Pi': {
+ zh: {
+ 0: 'PA0 - UART TX',
+ 1: 'PA1 - UART RX',
+ 2: 'PA2 - I2C SDA',
+ 3: 'PA3 - I2C SCL',
+ 6: 'PA6 - SPI CS',
+ 7: 'PA7 - SPI SCK',
+ 8: 'PA8 - SPI MISO',
+ 9: 'PA9 - SPI MOSI',
+ 10: 'PA10 - PWM',
+ 11: 'PA11 - 通用IO'
+ },
+ en: {
+ 0: 'PA0 - UART TX',
+ 1: 'PA1 - UART RX',
+ 2: 'PA2 - I2C SDA',
+ 3: 'PA3 - I2C SCL',
+ 6: 'PA6 - SPI CS',
+ 7: 'PA7 - SPI SCK',
+ 8: 'PA8 - SPI MISO',
+ 9: 'PA9 - SPI MOSI',
+ 10: 'PA10 - PWM',
+ 11: 'PA11 - General IO'
+ }
+ },
+ Arduino: {
+ zh: {
+ 0: 'D0 - RX',
+ 1: 'D1 - TX',
+ 2: 'D2 - 外部中断0',
+ 3: 'D3 - 外部中断1/PWM',
+ 4: 'D4 - 通用IO',
+ 5: 'D5 - PWM',
+ 6: 'D6 - PWM',
+ 7: 'D7 - 通用IO',
+ 8: 'D8 - 通用IO',
+ A0: 'A0 - ADC0',
+ A1: 'A1 - ADC1',
+ A2: 'A2 - ADC2',
+ A3: 'A3 - ADC3',
+ A4: 'A4 - ADC4/I2C SDA',
+ A5: 'A5 - ADC5/I2C SCL'
+ },
+ en: {
+ 0: 'D0 - RX',
+ 1: 'D1 - TX',
+ 2: 'D2 - INT0',
+ 3: 'D3 - INT1/PWM',
+ 4: 'D4 - General IO',
+ 5: 'D5 - PWM',
+ 6: 'D6 - PWM',
+ 7: 'D7 - General IO',
+ 8: 'D8 - General IO',
+ A0: 'A0 - ADC0',
+ A1: 'A1 - ADC1',
+ A2: 'A2 - ADC2',
+ A3: 'A3 - ADC3',
+ A4: 'A4 - ADC4/I2C SDA',
+ A5: 'A5 - ADC5/I2C SCL'
+ }
+ },
+ NodeMCU: {
+ zh: {
+ 0: 'GPIO0 - 启动按钮',
+ 2: 'GPIO2 - 内置LED',
+ 4: 'GPIO4 - 通用IO',
+ 5: 'GPIO5 - 通用IO',
+ 12: 'GPIO12 - MISO',
+ 13: 'GPIO13 - MOSI',
+ 14: 'GPIO14 - SCK',
+ 15: 'GPIO15 - CS',
+ 16: 'GPIO16 - 通用IO'
+ },
+ en: {
+ 0: 'GPIO0 - Boot Button',
+ 2: 'GPIO2 - Built-in LED',
+ 4: 'GPIO4 - General IO',
+ 5: 'GPIO5 - General IO',
+ 12: 'GPIO12 - MISO',
+ 13: 'GPIO13 - MOSI',
+ 14: 'GPIO14 - SCK',
+ 15: 'GPIO15 - CS',
+ 16: 'GPIO16 - General IO'
+ }
+ }
+ };
+
+ const board = this.boardType || 'ESP32';
+ const map = pinMaps[board];
+
+ if (map) {
+ const langMap = this.language === 'zh' ? map.zh : map.en;
+ return langMap[pin] || (this.language === 'zh' ? '引脚' + pin + ' - 未知' : 'Pin ' + pin + ' - Unknown');
+ }
+
+ return this.language === 'zh' ? 'GPIO' + pin + ' - 未知引脚' : 'GPIO' + pin + ' - Unknown pin';
+ }
+
+ sendDisplayData() {
+ if (!this.isConnected) return;
+ window.addEventListener('message', (event) => {
+ if (event.data.type === 'displayData') {
+ const grid = JSON.parse(event.data.data);
+ let dataStr = 'DISPLAY:';
+ for (let row = 0; row < 5; row++) {
+ for (let col = 0; col < 5; col++) {
+ const pixel = grid[row][col];
+ dataStr += `${pixel.r},${pixel.g},${pixel.b};`;
+ }
+ }
+ this.sendCommand(dataStr);
+ } else if (event.data.type === 'setBrightness') {
+ this.sendCommand(`BRIGHTNESS:${event.data.value}`);
+ }
+ });
+ }
+
+ async clearDisplay() {
+ if (!this.isConnected) return;
+ await this.sendCommand('DISPLAY:CLEAR');
+ }
+
+ async displayText(args) {
+ if (!this.isConnected) return;
+ const text = args.TEXT;
+ await this.sendCommand(`DISPLAY:TEXT:${text}`);
+ }
+
+ async displayImage(args) {
+ if (!this.isConnected) return;
+ const url = args.URL;
+ await this.sendCommand(`DISPLAY:IMAGE:${url}`);
+ }
+
+ async startCamera() {
+ window.addEventListener('message', (event) => {
+ if (event.data.type === 'displayData') {
+ this.sendDisplayData();
+ }
+ });
+ }
+
+ async stopCamera() {
+ await this.clearDisplay();
+ }
+
+ async setBrightness(args) {
+ if (!this.isConnected) return;
+ const value = args.VALUE;
+ await this.sendCommand(`BRIGHTNESS:${value}`);
+ }
+
+ async sendCommand(command) {
+ if (!this.writer) return;
+ try {
+ await this.writer.write(this.encoder.encode(command + '\n'));
+ } catch (error) {
+ console.error('Send command failed:', error);
+ }
+ }
+
+ async readLoop() {
+ if (!this.port) return;
+ try {
+ this.reader = this.port.readable.getReader();
+ while (true) {
+ const { value, done } = await this.reader.read();
+ if (done) break;
+ const text = this.decoder.decode(value);
+ this.parseData(text);
+ }
+ } catch (error) {
+ console.error('Read failed:', error);
+ } finally {
+ this.reader = null;
+ }
+ }
+
+ parseData(text) {
+ const lines = text.split('\n');
+ for (const line of lines) {
+ const trimmed = line.trim();
+ if (!trimmed) continue;
+
+ if (trimmed.startsWith('D:')) {
+ const parts = trimmed.substring(2).split(':');
+ if (parts.length >= 2) {
+ this.sensorData['D' + parts[0]] = parseInt(parts[1]);
+ }
+ } else if (trimmed.startsWith('A:')) {
+ const parts = trimmed.substring(2).split(':');
+ if (parts.length >= 2) {
+ this.sensorData['A' + parts[0]] = parseInt(parts[1]);
+ }
+ } else if (trimmed.startsWith('SERIAL:')) {
+ this.sensorData['SERIAL'] = trimmed.substring(7);
+ }
+ }
+ }
+ }
+
+ Scratch.extensions.register(new MakerPlusExtension());
+
+/*
+===========================================================================
+ Usage Instructions
+===========================================================================
+
+ Important Notes:
+
+ 1. Unsandboxed Mode Requirement:
+ - This extension needs serial port access, so it must run in unsandboxed mode
+ - Check "Run extension without sandbox" when adding the extension
+ - If not checked, the extension cannot connect to devices
+
+ 2. Browser Requirements:
+ - Must use Chrome, Edge, Brave or other Chromium browsers
+ - Firefox and Safari do not support Web Serial API
+
+ 3. Usage Steps:
+ a) Upload ESP32_TurboWarp_Firmware.ino to your board
+ b) Connect the board to your computer via USB
+ c) Use "Select Board" to choose your board type
+ d) Use "Connect" to select the serial device
+ e) Start using the control blocks
+
+ 4. Code Editor:
+ - Click "Open Code Editor" to open a separate code editing window
+ - Supports Arduino-like syntax:
+ digitalWrite(pin, HIGH/LOW)
+ digitalRead(pin)
+ analogRead(pin)
+ analogWrite(pin, value)
+ pinMode(pin, INPUT/OUTPUT)
+ servoWrite(pin, angle)
+ delay(ms)
+
+ 5. Supported Boards:
+ - ESP32 (ESP32-WROOM, ESP32-S, ESP32-C3, etc.)
+ - STM32 (STM32F103, STM32F4, etc.)
+ - Raspberry Pi
+ - Orange Pi
+ - Arduino (Uno, Nano, etc.)
+ - NodeMCU (ESP8266)
+
+ 6. Pin Information:
+ - Use "Pin Info" block to check pin functions for each board
+ - Different boards have different pin definitions
+
+===========================================================================
+*/
+})(Scratch);