esp32blockly/public/hbridgemotor-addon.js

376 lines
13 KiB
JavaScript

// h-bridge Motor Driver addon
//
// Adds a "h-bridge Motor" toolbox category with:
// - motor_init: set up a motor with IN1 and IN2 pins
// - motor_speed: set speed from -255 (full reverse) to +255 (full forward)
// - motor_stop: stop a motor
//
// Generates device-appropriate MicroPython:
// - ESP32 / RP2040: machine.Pin + machine.PWM
// - micro:bit: pinN.write_analog()
// --- Block definitions ---
api.Blockly.Blocks['hbridge_motor_init'] = {
init() {
this.appendDummyInput()
.appendField('init motor')
.appendField(new api.Blockly.FieldDropdown([
['A', 'A'],
['B', 'B'],
]), 'MOTOR')
.appendField('IN1 pin')
.appendField(new api.Blockly.FieldNumber(2, 0, 48, 1), 'IN1')
.appendField('IN2 pin')
.appendField(new api.Blockly.FieldNumber(3, 0, 48, 1), 'IN2');
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(30);
this.setTooltip('Initialize an h-bridge motor channel with two pins');
},
};
api.Blockly.Blocks['hbridge_motor_speed'] = {
init() {
this.appendDummyInput()
.appendField('set motor')
.appendField(new api.Blockly.FieldDropdown([
['A', 'A'],
['B', 'B'],
]), 'MOTOR');
this.appendValueInput('SPEED')
.setCheck('Number')
.appendField('speed');
this.setInputsInline(true);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(30);
this.setTooltip('Set motor speed: -255 (full reverse) to +255 (full forward)');
},
};
api.Blockly.Blocks['hbridge_motor_stop'] = {
init() {
this.appendDummyInput()
.appendField('stop motor')
.appendField(new api.Blockly.FieldDropdown([
['A', 'A'],
['B', 'B'],
]), 'MOTOR');
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(30);
this.setTooltip('Stop an h-bridge motor (brake)');
},
};
// --- Helpers used by generators ---
function isMicrobit() {
return api.getDeviceId() === 'microbit';
}
function mbPin(pin) {
var n = Math.max(0, Math.min(20, parseInt(pin, 10) || 0));
return 'pin' + n;
}
// --- Code generators ---
api.pythonGenerator.forBlock['hbridge_motor_init'] = function (block) {
var motor = block.getFieldValue('MOTOR');
var in1 = block.getFieldValue('IN1');
var in2 = block.getFieldValue('IN2');
var m = motor.toLowerCase();
if (isMicrobit()) {
// micro:bit: no explicit init needed, just store pin numbers for later use.
// We define variables so speed/stop blocks can reference them.
api.pythonGenerator.definitions_['import_microbit'] = 'from microbit import *';
api.pythonGenerator.definitions_['hb_motor_' + m + '_pins'] =
'motor_' + m + '_in1 = ' + mbPin(in1) + '\n' +
'motor_' + m + '_in2 = ' + mbPin(in2);
api.pythonGenerator.definitions_['hb_set_motor_mb'] = [
'def _hb_set_motor(p1, p2, speed):',
' speed = max(-255, min(255, int(speed)))',
' duty = abs(speed) * 4',
' if speed > 0:',
' p1.write_analog(duty)',
' p2.write_analog(0)',
' elif speed < 0:',
' p1.write_analog(0)',
' p2.write_analog(duty)',
' else:',
' p1.write_analog(0)',
' p2.write_analog(0)',
].join('\n');
} else {
// ESP32 / RP2040: machine.PWM (pins stored as numbers, PWM created on demand)
api.pythonGenerator.definitions_['import_machine'] =
'from machine import Pin, PWM';
api.pythonGenerator.definitions_['hb_motor_' + m + '_in1'] =
'motor_' + m + '_in1 = ' + in1;
api.pythonGenerator.definitions_['hb_motor_' + m + '_in2'] =
'motor_' + m + '_in2 = ' + in2;
api.pythonGenerator.definitions_['hb_set_motor'] = [
'def _hb_set_motor(p1, p2, speed):',
' speed = max(-255, min(255, int(speed)))',
' duty = abs(speed) * 4',
' if speed > 0:',
' try: PWM(Pin(p2)).deinit()',
' except: pass',
' Pin(p2, Pin.OUT).value(0)',
' PWM(Pin(p1), freq=1000, duty=duty)',
' elif speed < 0:',
' try: PWM(Pin(p1)).deinit()',
' except: pass',
' Pin(p1, Pin.OUT).value(0)',
' PWM(Pin(p2), freq=1000, duty=duty)',
' else:',
' try: PWM(Pin(p1)).deinit()',
' except: pass',
' try: PWM(Pin(p2)).deinit()',
' except: pass',
' Pin(p1, Pin.OUT).value(0)',
' Pin(p2, Pin.OUT).value(0)',
].join('\n');
}
return '';
};
api.pythonGenerator.forBlock['hbridge_motor_speed'] = function (block) {
var motor = block.getFieldValue('MOTOR');
var m = motor.toLowerCase();
var speed = api.pythonGenerator.valueToCode(
block, 'SPEED', api.pythonGenerator.ORDER_NONE
) || '0';
return '_hb_set_motor(motor_' + m + '_in1, motor_' + m + '_in2, ' + speed + ')\n';
};
api.pythonGenerator.forBlock['hbridge_motor_stop'] = function (block) {
var motor = block.getFieldValue('MOTOR');
var m = motor.toLowerCase();
return '_hb_set_motor(motor_' + m + '_in1, motor_' + m + '_in2, 0)\n';
};
// --- Dual-motor block definitions ---
api.Blockly.Blocks['hbridge_dual_init'] = {
init() {
this.appendDummyInput().appendField('init dual motors');
this.appendDummyInput()
.appendField('left IN1')
.appendField(new api.Blockly.FieldNumber(2, 0, 48, 1), 'L_IN1')
.appendField('IN2')
.appendField(new api.Blockly.FieldNumber(3, 0, 48, 1), 'L_IN2');
this.appendDummyInput()
.appendField('right IN1')
.appendField(new api.Blockly.FieldNumber(4, 0, 48, 1), 'R_IN1')
.appendField('IN2')
.appendField(new api.Blockly.FieldNumber(5, 0, 48, 1), 'R_IN2');
this.setInputsInline(false);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(30);
this.setTooltip('Initialize two h-bridge motors (left and right) with 4 pins.');
},
};
api.Blockly.Blocks['hbridge_dual_speed'] = {
init() {
this.appendDummyInput().appendField('set motors');
this.appendValueInput('LEFT')
.setCheck('Number')
.appendField('left');
this.appendValueInput('RIGHT')
.setCheck('Number')
.appendField('right');
this.setInputsInline(true);
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(30);
this.setTooltip('Set left and right motor speeds independently (-255 to 255).');
},
};
api.Blockly.Blocks['hbridge_dual_forward'] = {
init() {
this.appendDummyInput().appendField('drive forward');
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(30);
this.setTooltip('Drive both motors forward using the speed from "set motors".');
},
};
api.Blockly.Blocks['hbridge_dual_backward'] = {
init() {
this.appendDummyInput().appendField('drive backward');
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(30);
this.setTooltip('Drive both motors backward using the speed from "set motors".');
},
};
api.Blockly.Blocks['hbridge_dual_left'] = {
init() {
this.appendDummyInput().appendField('turn left');
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(30);
this.setTooltip('Turn left (left motor backward, right motor forward).');
},
};
api.Blockly.Blocks['hbridge_dual_right'] = {
init() {
this.appendDummyInput().appendField('turn right');
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(30);
this.setTooltip('Turn right (left motor forward, right motor backward).');
},
};
api.Blockly.Blocks['hbridge_dual_stop'] = {
init() {
this.appendDummyInput().appendField('stop motors');
this.setPreviousStatement(true, null);
this.setNextStatement(true, null);
this.setColour(30);
this.setTooltip('Stop both motors.');
},
};
// --- Dual-motor generators ---
api.pythonGenerator.forBlock['hbridge_dual_init'] = function (block) {
var l1 = block.getFieldValue('L_IN1');
var l2 = block.getFieldValue('L_IN2');
var r1 = block.getFieldValue('R_IN1');
var r2 = block.getFieldValue('R_IN2');
if (isMicrobit()) {
api.pythonGenerator.definitions_['import_microbit'] = 'from microbit import *';
api.pythonGenerator.definitions_['hb_dual_pins'] =
'motor_l_in1 = ' + mbPin(l1) + '\n' +
'motor_l_in2 = ' + mbPin(l2) + '\n' +
'motor_r_in1 = ' + mbPin(r1) + '\n' +
'motor_r_in2 = ' + mbPin(r2);
api.pythonGenerator.definitions_['hb_set_motor_mb'] = [
'def _hb_set_motor(p1, p2, speed):',
' speed = max(-255, min(255, int(speed)))',
' duty = abs(speed) * 4',
' if speed > 0:',
' p1.write_analog(duty)',
' p2.write_analog(0)',
' elif speed < 0:',
' p1.write_analog(0)',
' p2.write_analog(duty)',
' else:',
' p1.write_analog(0)',
' p2.write_analog(0)',
].join('\n');
} else {
api.pythonGenerator.definitions_['import_machine'] =
'from machine import Pin, PWM';
api.pythonGenerator.definitions_['hb_motor_l_in1'] = 'motor_l_in1 = ' + l1;
api.pythonGenerator.definitions_['hb_motor_l_in2'] = 'motor_l_in2 = ' + l2;
api.pythonGenerator.definitions_['hb_motor_r_in1'] = 'motor_r_in1 = ' + r1;
api.pythonGenerator.definitions_['hb_motor_r_in2'] = 'motor_r_in2 = ' + r2;
api.pythonGenerator.definitions_['hb_set_motor'] = [
'def _hb_set_motor(p1, p2, speed):',
' speed = max(-255, min(255, int(speed)))',
' duty = abs(speed) * 4',
' if speed > 0:',
' try: PWM(Pin(p2)).deinit()',
' except: pass',
' Pin(p2, Pin.OUT).value(0)',
' PWM(Pin(p1), freq=1000, duty=duty)',
' elif speed < 0:',
' try: PWM(Pin(p1)).deinit()',
' except: pass',
' Pin(p1, Pin.OUT).value(0)',
' PWM(Pin(p2), freq=1000, duty=duty)',
' else:',
' try: PWM(Pin(p1)).deinit()',
' except: pass',
' try: PWM(Pin(p2)).deinit()',
' except: pass',
' Pin(p1, Pin.OUT).value(0)',
' Pin(p2, Pin.OUT).value(0)',
].join('\n');
}
return '';
};
api.pythonGenerator.forBlock['hbridge_dual_speed'] = function (block) {
var left = api.pythonGenerator.valueToCode(
block, 'LEFT', api.pythonGenerator.ORDER_NONE) || '0';
var right = api.pythonGenerator.valueToCode(
block, 'RIGHT', api.pythonGenerator.ORDER_NONE) || '0';
return '_hb_set_motor(motor_l_in1, motor_l_in2, ' + left + ')\n' +
'_hb_set_motor(motor_r_in1, motor_r_in2, ' + right + ')\n';
};
api.pythonGenerator.forBlock['hbridge_dual_forward'] = function () {
return '_hb_set_motor(motor_l_in1, motor_l_in2, 255)\n' +
'_hb_set_motor(motor_r_in1, motor_r_in2, 255)\n';
};
api.pythonGenerator.forBlock['hbridge_dual_backward'] = function () {
return '_hb_set_motor(motor_l_in1, motor_l_in2, -255)\n' +
'_hb_set_motor(motor_r_in1, motor_r_in2, -255)\n';
};
api.pythonGenerator.forBlock['hbridge_dual_left'] = function () {
return '_hb_set_motor(motor_l_in1, motor_l_in2, -255)\n' +
'_hb_set_motor(motor_r_in1, motor_r_in2, 255)\n';
};
api.pythonGenerator.forBlock['hbridge_dual_right'] = function () {
return '_hb_set_motor(motor_l_in1, motor_l_in2, 255)\n' +
'_hb_set_motor(motor_r_in1, motor_r_in2, -255)\n';
};
api.pythonGenerator.forBlock['hbridge_dual_stop'] = function () {
return '_hb_set_motor(motor_l_in1, motor_l_in2, 0)\n' +
'_hb_set_motor(motor_r_in1, motor_r_in2, 0)\n';
};
// --- Register toolbox category ---
api.registerCategories([
{
kind: 'category',
name: 'H-Bridge Motor',
colour: '30',
contents: [
{ kind: 'block', type: 'hbridge_motor_init' },
{
kind: 'block',
type: 'hbridge_motor_speed',
inputs: {
SPEED: { shadow: { type: 'math_number', fields: { NUM: 200 } } },
},
},
{ kind: 'block', type: 'hbridge_motor_stop' },
{ kind: 'block', type: 'hbridge_dual_init' },
{
kind: 'block',
type: 'hbridge_dual_speed',
inputs: {
LEFT: { shadow: { type: 'math_number', fields: { NUM: 200 } } },
RIGHT: { shadow: { type: 'math_number', fields: { NUM: 200 } } },
},
},
{ kind: 'block', type: 'hbridge_dual_forward' },
{ kind: 'block', type: 'hbridge_dual_backward' },
{ kind: 'block', type: 'hbridge_dual_left' },
{ kind: 'block', type: 'hbridge_dual_right' },
{ kind: 'block', type: 'hbridge_dual_stop' },
],
},
]);