From dcb5fabd9e53a2feffb31768cbcd3945276e48db Mon Sep 17 00:00:00 2001 From: Darrenliu09 Date: Tue, 7 Jul 2026 14:59:07 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=88=9B=E5=AE=A2+?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 创客 + 硬件串口扩展,支持 ESP32/STM32 / 树莓派 / Arduino 等多开发板,Web Serial 串口通信;内置代码编辑器、5x5 点阵显示屏绘图工具、摄像头采集;包含数字 / 模拟引脚、舵机、串口、屏幕亮度全套控制积木;中英文双语自动适配,无外部密钥、无第三方 API 调用,无安全泄露风险,MPL2.0 开源,TurboWarp 非沙盒环境完整测试可用。 --- .../Darren_liu/\345\210\233\345\256\242+" | 1415 +++++++++++++++++ 1 file changed, 1415 insertions(+) create mode 100644 "extensions/Darren_liu/\345\210\233\345\256\242+" 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..ce1d0139a3 --- /dev/null +++ "b/extensions/Darren_liu/\345\210\233\345\256\242+" @@ -0,0 +1,1415 @@ +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 = ` + + + + + + + +
+
Code Editor - Maker+
+
+ + + + +
+
+ +
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 = ` + + + + + + + +
+

5x5 Pixel Grid

+
+
+
+

RGB Color Picker

+
+
Red (R)
+ + + +
Green (G)
+ + + +
Blue (B)
+ + + +
Hex Code
+ + +
+
+
Preview
+
+
+ +
+ + +
+
+
+

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 + +=========================================================================== +*/ From 2491aa0c9dbeb3d0922c325d312e18b2259e197d Mon Sep 17 00:00:00 2001 From: Darrenliu09 Date: Wed, 8 Jul 2026 20:32:47 +0800 Subject: [PATCH 2/2] Add MPL 2.0 license header to comply with contribution guidelines Added full Mozilla Public License 2.0 copyright header at the top of makerplus.js as requested by maintainer review feedback. docsURI already links to official TurboWarp documentation page, no further modification required for this field. All extension core logic and serial communication functions remain unchanged. --- .../Darren_liu/\345\210\233\345\256\242+" | 1651 +++++++++-------- 1 file changed, 830 insertions(+), 821 deletions(-) diff --git "a/extensions/Darren_liu/\345\210\233\345\256\242+" "b/extensions/Darren_liu/\345\210\233\345\256\242+" index ce1d0139a3..d8e7fff14b 100644 --- "a/extensions/Darren_liu/\345\210\233\345\256\242+" +++ "b/extensions/Darren_liu/\345\210\233\345\256\242+" @@ -1,376 +1,384 @@ -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(); - } +// 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'; - detectLanguage() { - const lang = navigator.language || navigator.userLanguage; - if (lang.startsWith('zh')) return 'zh'; - return 'en'; - } + 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(); + } - 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; - } + detectLanguage() { + const lang = navigator.language || navigator.userLanguage; + if (lang.startsWith('zh')) return 'zh'; + return '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)' + 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: '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: '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: '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: '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: '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: '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: '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: '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: '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: '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: '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: '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: '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 + }, + { + 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'] + } } - ], - 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 = ` + openCodeEditor() { + const html = ` @@ -441,42 +449,42 @@ function log(msg, type) { function compileCode() { var code = document.getElementById('codeEditor').value; var runBtn = document.querySelector('.run'); - + compiledCode = []; isValid = false; - + document.getElementById('output').innerHTML = ''; log('=== Compilation Started ===', 'info'); - + var lines = code.split('\n'); var errors = []; var warnings = []; - + for (var i = 0; i < lines.length; i++) { var line = lines[i].trim(); var lineNum = i + 1; - + if (line === '' || line.startsWith('//')) continue; - + var cleanLine = line.replace(/;/g, '').trim(); if (cleanLine === '') continue; - + var match = cleanLine.match(/^(\w+)\s*\((.*)\)$/); if (!match) { errors.push('Line ' + lineNum + ': Invalid syntax - "' + line + '"'); continue; } - + var cmd = match[1]; var args = match[2].split(',').map(function(a) { return a.trim(); }); - + var validCommands = ['digitalWrite', 'digitalRead', 'analogRead', 'analogWrite', 'pinMode', 'servoWrite', 'delay']; - + if (validCommands.indexOf(cmd) === -1) { errors.push('Line ' + lineNum + ': Unknown command - "' + cmd + '"'); continue; } - + switch(cmd) { case 'digitalWrite': if (args.length !== 2) { @@ -559,10 +567,10 @@ function compileCode() { compiledCode.push(cleanLine); } } - + warnings.forEach(function(w) { log(w, 'info'); }); errors.forEach(function(e) { log(e, 'error'); }); - + if (errors.length === 0) { isValid = true; runBtn.disabled = false; @@ -581,7 +589,7 @@ function runCode() { log('Please compile first!', 'error'); return; } - + var code = compiledCode.join('\n'); window.opener.postMessage({ type: 'runCode', code: code }, '*'); log('Code sent to device!', 'success'); @@ -601,14 +609,14 @@ function closeWindow() { `; - - 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 = ` + 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 = ` @@ -656,24 +664,24 @@ canvas { display: none; }
Red (R)
- +
Green (G)
- +
Blue (B)
- +
Hex Code
- +
Preview
- +
@@ -726,18 +734,18 @@ for (var row = 0; row < 5; row++) { cell.dataset.col = col; cell.style.backgroundColor = '#1e1e1e'; grid[row][col] = { r: 0, g: 0, b: 0 }; - + cell.addEventListener('click', function() { var r = parseInt(this.dataset.row); var c = parseInt(this.dataset.col); grid[r][c] = { ...currentColor }; this.style.backgroundColor = rgbToHex(currentColor.r, currentColor.g, currentColor.b); - + document.querySelectorAll('.grid-cell').forEach(function(c) { c.classList.remove('selected'); }); this.classList.add('selected'); selectedCell = { row: r, col: c }; }); - + rowDiv.appendChild(cell); } document.getElementById('grid').appendChild(rowDiv); @@ -747,9 +755,9 @@ function updateColor() { var r = parseInt(document.getElementById('rSlider').value); var g = parseInt(document.getElementById('gSlider').value); var b = parseInt(document.getElementById('bSlider').value); - + currentColor = { r, g, b }; - + document.getElementById('rValue').value = r; document.getElementById('gValue').value = g; document.getElementById('bValue').value = b; @@ -807,14 +815,14 @@ function loadImage() { var img = document.getElementById('previewImage'); img.src = url; img.style.display = 'block'; - + img.onload = function() { var canvas = document.createElement('canvas'); var ctx = canvas.getContext('2d'); canvas.width = 5; canvas.height = 5; ctx.drawImage(img, 0, 0, 5, 5); - + for (var row = 0; row < 5; row++) { for (var col = 0; col < 5; col++) { var pixel = ctx.getImageData(col, row, 1, 1).data; @@ -851,11 +859,11 @@ function sendCameraFrame() { var video = document.getElementById('cameraVideo'); var canvas = document.getElementById('cameraCanvas'); var ctx = canvas.getContext('2d'); - + canvas.width = 5; canvas.height = 5; ctx.drawImage(video, 0, 0, 5, 5); - + for (var row = 0; row < 5; row++) { for (var col = 0; col < 5; col++) { var pixel = ctx.getImageData(col, row, 1, 1).data; @@ -864,7 +872,7 @@ function sendCameraFrame() { cell.style.backgroundColor = rgbToHex(pixel[0], pixel[1], pixel[2]); } } - + sendDisplayData(); } @@ -881,489 +889,489 @@ document.getElementById('brightnessSlider').addEventListener('input', function() `; - - 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()}`); - }); - } + 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'); + } - selectBoard(args) { - this.boardType = args.BOARD; - } + 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()}`); + }); + } - getCurrentBoard() { - return this.boardType || (this.language === 'zh' ? '未选择' : 'Not selected'); - } + selectBoard(args) { + this.boardType = args.BOARD; + } - 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; + getCurrentBoard() { + return this.boardType || (this.language === 'zh' ? '未选择' : 'Not selected'); } - } - async disconnect() { - try { - if (this.reader) { - await this.reader.cancel(); - this.reader = null; - } - if (this.writer) { - await this.writer.close(); - this.writer = null; + 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; } - if (this.port) { - await this.port.close(); - this.port = null; + } + + 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); } - this.isConnected = false; - this.sensorData = {}; - } catch (error) { - console.error('Disconnect failed:', error); } - } - checkConnected() { - return this.isConnected; - } + 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 setPinMode(args) { + if (!this.isConnected) return; + const pin = args.PIN; + const mode = args.MODE; + let modeCode = 'OUTPUT'; - 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}`); - } + 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'; - 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; - } + await this.sendCommand(`PIN_MODE:${pin}:${modeCode}`); + } - 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 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 analogWrite(args) { - if (!this.isConnected) return; - const pin = args.PIN; - const value = args.VALUE; - await this.sendCommand(`ANALOG_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 servoWrite(args) { - if (!this.isConnected) return; - const pin = args.PIN; - const angle = args.ANGLE; - await this.sendCommand(`SERVO_WRITE:${pin}:${angle}`); - } + 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 delay(args) { - const ms = args.MS; - return new Promise(resolve => setTimeout(resolve, ms)); - } + async analogWrite(args) { + if (!this.isConnected) return; + const pin = args.PIN; + const value = args.VALUE; + await this.sendCommand(`ANALOG_WRITE:${pin}:${value}`); + } - async sendSerial(args) { - if (!this.isConnected) return; - const data = args.DATA; - await this.sendCommand(`SERIAL_SEND:${data}`); - } + async servoWrite(args) { + if (!this.isConnected) return; + const pin = args.PIN; + const angle = args.ANGLE; + await this.sendCommand(`SERVO_WRITE:${pin}:${angle}`); + } - readSerial() { - return this.sensorData['SERIAL'] || ''; - } + async delay(args) { + const ms = args.MS; + return new Promise(resolve => setTimeout(resolve, ms)); + } - 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' + 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)' + } }, - 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' + 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' + } }, - 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' + '树莓派': { + 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' + } }, - 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' + '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' + } }, - 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' + '香橙派': { + 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' + } }, - 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' + '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' + } }, - 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' + 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' + } }, - 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' + 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'); } - }; - - 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'; } - - 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};`; + 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}`); } - 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 clearDisplay() { + if (!this.isConnected) return; + await this.sendCommand('DISPLAY:CLEAR'); + } - async displayImage(args) { - if (!this.isConnected) return; - const url = args.URL; - await this.sendCommand(`DISPLAY:IMAGE:${url}`); - } + async displayText(args) { + if (!this.isConnected) return; + const text = args.TEXT; + await this.sendCommand(`DISPLAY:TEXT:${text}`); + } - async startCamera() { - window.addEventListener('message', (event) => { - if (event.data.type === 'displayData') { - this.sendDisplayData(); - } - }); - } + async displayImage(args) { + if (!this.isConnected) return; + const url = args.URL; + await this.sendCommand(`DISPLAY:IMAGE:${url}`); + } - async stopCamera() { - await this.clearDisplay(); - } + async startCamera() { + window.addEventListener('message', (event) => { + if (event.data.type === 'displayData') { + this.sendDisplayData(); + } + }); + } - async setBrightness(args) { - if (!this.isConnected) return; - const value = args.VALUE; - await this.sendCommand(`BRIGHTNESS:${value}`); - } + async stopCamera() { + await this.clearDisplay(); + } - 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 setBrightness(args) { + if (!this.isConnected) return; + const value = args.VALUE; + await this.sendCommand(`BRIGHTNESS:${value}`); } - } - 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); + 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); } - } 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]); + 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); } - } else if (trimmed.startsWith('A:')) { - const parts = trimmed.substring(2).split(':'); - if (parts.length >= 2) { - this.sensorData['A' + parts[0]] = parseInt(parts[1]); + } 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); } - } else if (trimmed.startsWith('SERIAL:')) { - this.sensorData['SERIAL'] = trimmed.substring(7); } } } -} -Scratch.extensions.register(new MakerPlusExtension()); + Scratch.extensions.register(new MakerPlusExtension()); /* =========================================================================== @@ -1413,3 +1421,4 @@ Scratch.extensions.register(new MakerPlusExtension()); =========================================================================== */ +})(Scratch);