diff --git a/src/blocks/arduino_generators.js b/src/blocks/arduino_generators.js index 5ac52c1..e15198c 100644 --- a/src/blocks/arduino_generators.js +++ b/src/blocks/arduino_generators.js @@ -418,8 +418,8 @@ arduinoGenerator.forBlock['arduino_map'] = function (block) { const unsupportedBlocks = [ 'wifi_connect', 'wifi_get_ip', 'neopixel_init', 'neopixel_set_color', 'neopixel_show', - 'colour_rgb', 'tuple_create_3', - 'i2c_init', 'i2c_scan', 'i2c_writeto', 'i2c_readfrom', + 'colour_rgb', 'tuple_create_3', 'tuple_index', + 'i2c_init', 'i2c_scan', 'i2c_writeto', 'i2c_readfrom', 'tcs34725_read_colours', 'hid_key_press', 'hid_key_down', 'hid_key_up', 'hid_keyboard_type', 'hid_mouse_move', 'hid_mouse_click', 'hid_mouse_scroll', 'hid_gamepad_button', 'hid_gamepad_axis', diff --git a/src/blocks/categories/i2c.js b/src/blocks/categories/i2c.js index 05da78e..22610f3 100644 --- a/src/blocks/categories/i2c.js +++ b/src/blocks/categories/i2c.js @@ -3,9 +3,17 @@ export const i2cCategory = { name: 'I2C', colour: '200', contents: [ - { kind: 'block', type: 'i2c_init' }, - { kind: 'block', type: 'i2c_scan' }, - { kind: 'block', type: 'i2c_writeto' }, - { kind: 'block', type: 'i2c_readfrom' }, + { kind: 'block', type: 'i2c_init', fields: { CH: '0' } }, + { kind: 'block', type: 'i2c_scan', fields: { CH: '0' } }, + { kind: 'block', type: 'i2c_writeto', fields: { CH: '0' } }, + { kind: 'block', type: 'i2c_readfrom', fields: { CH: '0' } }, + { + kind: 'block', + type: 'tcs34725_read_colours', + fields: { + CH: '0', + ADDR: 41, + }, + }, ], }; diff --git a/src/blocks/categories/neopixel.js b/src/blocks/categories/neopixel.js index 105cc8d..8840005 100644 --- a/src/blocks/categories/neopixel.js +++ b/src/blocks/categories/neopixel.js @@ -22,6 +22,22 @@ export const neopixelCategory = { C: { shadow: { type: 'math_number', fields: { NUM: 0 } } }, }, }, + { + kind: 'block', + type: 'tuple_index', + inputs: { + TUPLE: { + shadow: { + type: 'tuple_create_3', + inputs: { + A: { shadow: { type: 'math_number', fields: { NUM: 255 } } }, + B: { shadow: { type: 'math_number', fields: { NUM: 128 } } }, + C: { shadow: { type: 'math_number', fields: { NUM: 0 } } }, + }, + }, + }, + }, + }, { kind: 'block', type: 'neopixel_set_color', diff --git a/src/blocks/categories/sensors.js b/src/blocks/categories/sensors.js index 9621bc6..6bccc1f 100644 --- a/src/blocks/categories/sensors.js +++ b/src/blocks/categories/sensors.js @@ -11,5 +11,25 @@ export const sensorsCategory = { ECHO: { shadow: { type: 'math_number', fields: { NUM: 3 } } }, }, }, + { + kind: 'block', + type: 'tcs34725_read_colours', + fields: { + CH: '0', + ADDR: 41, + }, + }, + { + kind: 'block', + type: 'tuple_index', + inputs: { + TUPLE: { + shadow: { + type: 'tcs34725_read_colours', + fields: { CH: '0', ADDR: 41 }, + }, + }, + }, + }, ], }; diff --git a/src/blocks/esp32_blocks.js b/src/blocks/esp32_blocks.js index c6df4de..c792299 100644 --- a/src/blocks/esp32_blocks.js +++ b/src/blocks/esp32_blocks.js @@ -276,6 +276,29 @@ Blockly.Blocks['tuple_create_3'] = { }, }; +Blockly.Blocks['tuple_index'] = { + init() { + this.appendValueInput('TUPLE') + .setCheck(null) + .appendField('item') + .appendField( + new Blockly.FieldDropdown([ + ['0', '0'], + ['1', '1'], + ['2', '2'], + ]), + 'I', + ) + .appendField('of tuple'); + this.setOutput(true, 'Number'); + this.setInputsInline(true); + this.setColour(10); + this.setTooltip( + 'Read one element from a tuple by index. Use with the TCS34725 block or any (a, b, c) value.', + ); + }, +}; + Blockly.Blocks['neopixel_set_color'] = { init() { this.appendValueInput('INDEX') @@ -304,11 +327,18 @@ Blockly.Blocks['neopixel_show'] = { }; // ─── I2C ────────────────────────────────────────────────── +// Hardware I2C peripheral id (0 or 1). Generated code uses i2c0 / i2c1 variables. +const I2C_CHANNEL_OPTIONS = [ + ['ch 0', '0'], + ['ch 1', '1'], +]; Blockly.Blocks['i2c_init'] = { init() { this.appendDummyInput() - .appendField('init I2C SDA pin') + .appendField('init I2C') + .appendField(new Blockly.FieldDropdown(I2C_CHANNEL_OPTIONS), 'CH') + .appendField('SDA pin') .appendField(new Blockly.FieldNumber(8, 0, 48, 1), 'SDA') .appendField('SCL pin') .appendField(new Blockly.FieldNumber(9, 0, 48, 1), 'SCL') @@ -317,17 +347,19 @@ Blockly.Blocks['i2c_init'] = { this.setPreviousStatement(true, null); this.setNextStatement(true, null); this.setColour(200); - this.setTooltip('Initialize I2C bus'); + this.setTooltip('Initialize an I2C bus. ESP32/RP2040: channel 0 or 1 maps to I2C(0) or I2C(1).'); }, }; Blockly.Blocks['i2c_scan'] = { init() { this.appendDummyInput() - .appendField('I2C scan devices'); + .appendField('I2C') + .appendField(new Blockly.FieldDropdown(I2C_CHANNEL_OPTIONS), 'CH') + .appendField('scan devices'); this.setOutput(true, 'Array'); this.setColour(200); - this.setTooltip('Scan for I2C devices, returns list of addresses'); + this.setTooltip('Scan for I2C devices on the selected bus, returns list of addresses'); }, }; @@ -335,26 +367,45 @@ Blockly.Blocks['i2c_writeto'] = { init() { this.appendValueInput('DATA') .setCheck(null) - .appendField('I2C write to address') + .appendField('I2C') + .appendField(new Blockly.FieldDropdown(I2C_CHANNEL_OPTIONS), 'CH') + .appendField('write to address') .appendField(new Blockly.FieldNumber(0, 0, 127, 1), 'ADDR') .appendField('data'); this.setPreviousStatement(true, null); this.setNextStatement(true, null); this.setColour(200); - this.setTooltip('Write data bytes to an I2C device'); + this.setTooltip('Write data bytes to an I2C device on the selected bus'); }, }; Blockly.Blocks['i2c_readfrom'] = { init() { this.appendDummyInput() - .appendField('I2C read from address') + .appendField('I2C') + .appendField(new Blockly.FieldDropdown(I2C_CHANNEL_OPTIONS), 'CH') + .appendField('read from address') .appendField(new Blockly.FieldNumber(0, 0, 127, 1), 'ADDR') .appendField('bytes') .appendField(new Blockly.FieldNumber(1, 1, 256, 1), 'NBYTES'); this.setOutput(true, null); this.setColour(200); - this.setTooltip('Read N bytes from an I2C device'); + this.setTooltip('Read N bytes from an I2C device on the selected bus'); + }, +}; + +Blockly.Blocks['tcs34725_read_colours'] = { + init() { + this.appendDummyInput() + .appendField('TCS34725 read RGB') + .appendField(new Blockly.FieldDropdown(I2C_CHANNEL_OPTIONS), 'CH') + .appendField('address') + .appendField(new Blockly.FieldNumber(41, 1, 127, 1), 'ADDR'); + this.setOutput(true, null); + this.setColour(65); + this.setTooltip( + 'Read raw RGB channel counts as (R, G, B), each 0–65535. If there is no init I2C block for this channel, the bus is set up on GPIO 21 (SDA) and 22 (SCL) at 400 kHz.', + ); }, }; diff --git a/src/blocks/esp32_generators.js b/src/blocks/esp32_generators.js index 69a71d7..12d9461 100644 --- a/src/blocks/esp32_generators.js +++ b/src/blocks/esp32_generators.js @@ -256,6 +256,13 @@ pythonGenerator.forBlock['tuple_create_3'] = function (block) { return [`(${a}, ${b}, ${c})`, Order.ATOMIC]; }; +pythonGenerator.forBlock['tuple_index'] = function (block) { + const tupleCode = + pythonGenerator.valueToCode(block, 'TUPLE', Order.MEMBER) || '(0, 0, 0)'; + const i = block.getFieldValue('I') ?? '0'; + return [`${tupleCode}[${i}]`, Order.MEMBER]; +}; + pythonGenerator.forBlock['neopixel_set_color'] = function (block) { const index = pythonGenerator.valueToCode(block, 'INDEX', Order.NONE) || '0'; const color = pythonGenerator.valueToCode(block, 'COLOR', Order.NONE) || '(0, 0, 0)'; @@ -268,35 +275,145 @@ pythonGenerator.forBlock['neopixel_show'] = function () { // ─── I2C ────────────────────────────────────────────────── +function i2cChannel(block) { + return block.getFieldValue('CH') === '1' ? '1' : '0'; +} + +function i2cBusName(ch) { + return ch === '1' ? 'i2c1' : 'i2c0'; +} + +function i2cHardwareId(ch) { + return ch === '1' ? 1 : 0; +} + +/** True if an enabled "init I2C" block exists for this hardware channel (0 or 1). */ +function workspaceHasI2cInitForChannel(block, ch) { + const ws = block.workspace; + if (!ws) return false; + const target = String(ch); + for (const b of ws.getAllBlocks(false)) { + if (!b.isEnabled()) continue; + if (b.type === 'i2c_init' && String(b.getFieldValue('CH') ?? '0') === target) { + return true; + } + } + return false; +} + +/** Emit I2C bus init into definitions (runs before user code) when no init block is present. */ +function ensureAutoI2cBusDefinition(block, ch, sda, scl, freq) { + if (DEVICE() === 'microbit') return; + if (workspaceHasI2cInitForChannel(block, ch)) return; + pythonGenerator.definitions_['import_machine'] = 'from machine import Pin, PWM, ADC, I2C'; + const name = i2cBusName(ch); + const hw = i2cHardwareId(ch); + pythonGenerator.definitions_[`i2c_init_bus_${ch}`] = + `${name} = I2C(${hw}, sda=Pin(${sda}), scl=Pin(${scl}), freq=${freq})`; +} + +const TCS34725_RGB_HELPER = `_tcs34725_inited = [False, False] + +def _tcs34725_rgb(bus, ch_idx, addr): + import time + global _tcs34725_inited + ch_idx = int(ch_idx) & 1 + addr = int(addr) + if not _tcs34725_inited[ch_idx]: + time.sleep_ms(10) + bus.writeto(addr, bytes([0x80, 0x01])) + time.sleep_ms(10) + bus.writeto(addr, bytes([0x80, 0x03])) + bus.writeto(addr, bytes([0x81, 0xFF])) + bus.writeto(addr, bytes([0x8F, 0x03])) + _tcs34725_inited[ch_idx] = True + time.sleep_ms(5) + bus.writeto(addr, bytes([0x94])) + d = bus.readfrom(addr, 8) + r = d[3] << 8 | d[2] + g = d[5] << 8 | d[4] + b = d[7] << 8 | d[6] + return (r, g, b) +`; + +const TCS34725_RGB_HELPER_MB = `_tcs34725_inited_mb = False + +def _tcs34725_rgb_mb(addr): + import time + from microbit import i2c + global _tcs34725_inited_mb + addr = int(addr) + if not _tcs34725_inited_mb: + time.sleep_ms(10) + i2c.write(addr, bytes([0x80, 0x01])) + time.sleep_ms(10) + i2c.write(addr, bytes([0x80, 0x03])) + i2c.write(addr, bytes([0x81, 0xFF])) + i2c.write(addr, bytes([0x8F, 0x03])) + _tcs34725_inited_mb = True + time.sleep_ms(5) + i2c.write(addr, bytes([0x94])) + d = i2c.read(addr, 8) + r = d[3] << 8 | d[2] + g = d[5] << 8 | d[4] + b = d[7] << 8 | d[6] + return (r, g, b) +`; + pythonGenerator.forBlock['i2c_init'] = function (block) { if (DEVICE() === 'microbit') return ''; // micro:bit has built-in i2c + const ch = i2cChannel(block); const sda = block.getFieldValue('SDA'); const scl = block.getFieldValue('SCL'); const freq = block.getFieldValue('FREQ'); pythonGenerator.definitions_['import_machine'] = 'from machine import Pin, PWM, ADC, I2C'; - return `i2c = I2C(0, sda=Pin(${sda}), scl=Pin(${scl}), freq=${freq})\n`; + const name = i2cBusName(ch); + const hw = i2cHardwareId(ch); + return `${name} = I2C(${hw}, sda=Pin(${sda}), scl=Pin(${scl}), freq=${freq})\n`; }; pythonGenerator.forBlock['i2c_scan'] = function (block) { if (DEVICE() === 'microbit') { return ['[]', Order.ATOMIC]; // stub } + const ch = i2cChannel(block); pythonGenerator.definitions_['import_machine'] = 'from machine import Pin, PWM, ADC, I2C'; - return ['i2c.scan()', Order.FUNCTION_CALL]; + const bus = i2cBusName(ch); + return [`${bus}.scan()`, Order.FUNCTION_CALL]; }; pythonGenerator.forBlock['i2c_writeto'] = function (block) { if (DEVICE() === 'microbit') return ''; + const ch = i2cChannel(block); const addr = block.getFieldValue('ADDR'); const data = pythonGenerator.valueToCode(block, 'DATA', Order.NONE) || 'b""'; - return `i2c.writeto(${addr}, ${data})\n`; + const bus = i2cBusName(ch); + return `${bus}.writeto(${addr}, ${data})\n`; }; pythonGenerator.forBlock['i2c_readfrom'] = function (block) { if (DEVICE() === 'microbit') return ['b""', Order.ATOMIC]; + const ch = i2cChannel(block); const addr = block.getFieldValue('ADDR'); const nbytes = block.getFieldValue('NBYTES'); - return [`i2c.readfrom(${addr}, ${nbytes})`, Order.FUNCTION_CALL]; + const bus = i2cBusName(ch); + return [`${bus}.readfrom(${addr}, ${nbytes})`, Order.FUNCTION_CALL]; +}; + +pythonGenerator.forBlock['tcs34725_read_colours'] = function (block) { + const addr = Number(block.getFieldValue('ADDR')) || 41; + if (DEVICE() === 'microbit') { + pythonGenerator.definitions_['import_time'] = 'import time'; + pythonGenerator.definitions_['tcs34725_rgb_mb'] = TCS34725_RGB_HELPER_MB; + return [`_tcs34725_rgb_mb(${addr})`, Order.FUNCTION_CALL]; + } + const ch = i2cChannel(block); + ensureAutoI2cBusDefinition(block, ch, 21, 22, 400000); + pythonGenerator.definitions_['import_time'] = 'import time'; + pythonGenerator.definitions_['tcs34725_rgb'] = TCS34725_RGB_HELPER; + const bus = i2cBusName(ch); + const chIdx = ch === '1' ? 1 : 0; + return [`_tcs34725_rgb(${bus}, ${chIdx}, ${addr})`, Order.FUNCTION_CALL]; }; // ─── Print ──────────────────────────────────────────────── diff --git a/src/robot/robotComponents.js b/src/robot/robotComponents.js index 4be461d..1a010aa 100644 --- a/src/robot/robotComponents.js +++ b/src/robot/robotComponents.js @@ -65,6 +65,16 @@ export const ROBOT_TEMPLATE_LIST = [ blockType: 'i2c_init', applyAs: 'statement', fields: [ + { + key: 'CH', + label: 'Channel', + type: 'dropdown', + options: [ + ['0', '0'], + ['1', '1'], + ], + default: '0', + }, { key: 'SDA', label: 'SDA pin', type: 'number', min: 0, max: 48, default: 8 }, { key: 'SCL', label: 'SCL pin', type: 'number', min: 0, max: 48, default: 9 }, { key: 'FREQ', label: 'Frequency (Hz)', type: 'number', min: 1, max: 1_000_000, default: 400_000 },