Add digital output block and GPIO handling

- Introduced a new Blockly block for setting digital output pins (`digitalOut`).
- Implemented the corresponding handler in Python to manage digital output actions.
- Updated dummy, GPIO, and real hardware interfaces to support digital output functionality.
- Modified the workspace configuration to include the new digital output block in the Blockly environment.
- Added potential enhancements to the README for future improvements.
master
a2nr 2026-03-09 23:08:46 +07:00
parent c82e30b9b1
commit dced16a00b
11 changed files with 973 additions and 159 deletions

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,8 @@ Feature Task : Penjabaran Pekerjaan yang ready untuk dikerjakan. Ta
# Potential Enhancements # Potential Enhancements
this list is short by priority this list is short by priority
- **Potensial inefective development**: in handlers/hardware use interface.py to all hardware (dummy, ros2, and hardware) class that posibly haavily change.
- ** UI bug **: stop button not actualy stop execution. tried with long delay with loop and press stop button, program still continue
- **Launch files**: `blockly_bringup` package with ROS2 launch files to start all nodes with one command - **Launch files**: `blockly_bringup` package with ROS2 launch files to start all nodes with one command
- **Sensor integration**: Subscriber nodes for sensor data feeding back into Blockly visual feedback - **Sensor integration**: Subscriber nodes for sensor data feeding back into Blockly visual feedback
- **RealHardware implementation**: Fill in ROS2 publishers/service clients for actual Pi hardware nodes (topics TBD) - **RealHardware implementation**: Fill in ROS2 publishers/service clients for actual Pi hardware nodes (topics TBD)

View File

@ -0,0 +1,33 @@
BlockRegistry.register({
name: 'digitalOut', // Must match Python handler name
category: 'Robot',
categoryColor: '#5b80a5',
color: '#4CAF50',
tooltip: 'set a digital output pin to HIGH (turn on LED)',
definition: {
init: function () {
this.appendValueInput('digitalOut')
.appendField(' gpio:')
// FieldNumber(default, min, max, step)
.appendField(new Blockly.FieldNumbint(er(1, 0, 27, 1), 'GPIO')
.setCheck('Boolean')
.appendField(' state:');
this.setPreviousStatement(true, null); // connect above
this.setNextStatement(true, null); // connect below
this.setColour('#4CAF50');
this.setTooltip('set a digital output pin to HIGH (turn on LED)');
}
},
generator: function (block) {
const GPIO = block.getFieldValue('GPIO');
const STATE = Blockly.JavaScript.valueToCode(block, 'digitalOut', Blockly.JavaScript.ORDER_ATOMIC) || 'false';
return (
'highlightBlock(\'' + block.id + '\');\n' +
'await executeAction(\'digital_out\', { gpio: \'' + GPIO + '\', state: \'' + STATE + '\' });\n'
);
}
});

View File

@ -9,7 +9,6 @@
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
const BLOCK_FILES = [ const BLOCK_FILES = [
'led_on.js', 'digitalOut.js',
'led_off.js',
'delay.js', 'delay.js',
]; ];

View File

@ -15,3 +15,11 @@ def handle_led_off(params: dict[str, str], hardware) -> tuple[bool, str]:
pin = int(params["pin"]) pin = int(params["pin"])
hardware.set_led(pin, False) hardware.set_led(pin, False)
return (True, f"LED on pin {pin} turned OFF") return (True, f"LED on pin {pin} turned OFF")
@handler("digital_out")
def handle_digital_out(params: dict[str, str], hardware) -> tuple[bool, str]:
gpio = int(params["gpio"])
state = bool(params["state"])
hardware.set_digital_out(gpio, state)
state_str = "HIGH" if state else "LOW"
return (True, f"GPIO pin {gpio} set to {state_str}")

View File

@ -22,3 +22,6 @@ class DummyHardware(HardwareInterface):
def is_ready(self) -> bool: def is_ready(self) -> bool:
self.call_log.append("is_ready()") self.call_log.append("is_ready()")
return True return True
def set_digital_out(self, gpio: int, state: bool) -> None:
self.call_log.append(f"set_digital_out(gpio={gpio}, state={state})")

View File

@ -30,3 +30,9 @@ class GpioHardware(HardwareInterface):
def is_ready(self) -> bool: def is_ready(self) -> bool:
return self._initialized return self._initialized
def set_digital_out(self, gpio: int, state: bool) -> None:
if not self._initialized:
raise RuntimeError("GPIO not available on this platform")
self._gpio.setup(gpio, self._gpio.OUT)
self._gpio.output(gpio, self._gpio.HIGH if state else self._gpio.LOW)

View File

@ -32,3 +32,14 @@ class HardwareInterface(ABC):
True if hardware is initialized and ready. True if hardware is initialized and ready.
""" """
... ...
@abstractmethod
def set_digital_out(self, gpio: int, state: bool) -> None:
"""
Set a GPIO pin to HIGH or LOW.
Args:
gpio: GPIO pin number.
state: True for HIGH, False for LOW.
"""
...

View File

@ -29,3 +29,7 @@ class RealHardware(HardwareInterface):
def is_ready(self) -> bool: def is_ready(self) -> bool:
# TODO: check if Pi hardware nodes are reachable # TODO: check if Pi hardware nodes are reachable
return True return True
def set_digital_out(self, gpio: int, state: bool) -> None:
# TODO: publish to Pi hardware node
self._logger.info(f"RealHardware.set_digital_out(gpio={gpio}, state={state}) — stub")

View File

@ -3,17 +3,75 @@
"languageVersion": 0, "languageVersion": 0,
"blocks": [ "blocks": [
{ {
"type": "controls_if", "type": "controls_repeat_ext",
"id": "WLmpkq!^,rcoKGH+bQgN", "id": "j+usYB.X4%o``Le;Q,LA",
"x": 210, "x": 190,
"y": 70, "y": 150,
"inputs": { "inputs": {
"DO0": { "TIMES": {
"block": { "shadow": {
"type": "led_on", "type": "math_number",
"id": "._%j-2DZ*BZJlyW:1#`Q", "id": "cI`]|Us0+)Ol~XY0L`wE",
"fields": { "fields": {
"PIN": 1 "NUM": 3
}
}
},
"DO": {
"block": {
"type": "digitalOut",
"id": "3}G7M%~([nNy9YHlug!|",
"fields": {
"GPIO": 17
},
"inputs": {
"digitalOut": {
"block": {
"type": "logic_boolean",
"id": "Sw__`~LvxpKyo}./q]1/",
"fields": {
"BOOL": "TRUE"
}
}
}
},
"next": {
"block": {
"type": "delay",
"id": "?o-l1IBd(^YR,u[;k$-|",
"fields": {
"DURATION_MS": 500
},
"next": {
"block": {
"type": "digitalOut",
"id": "t}@.X|Ac7F?J;C4v`5ic",
"fields": {
"GPIO": 17
},
"inputs": {
"digitalOut": {
"block": {
"type": "logic_boolean",
"id": "]I]_@v6=ErAYdiolo|+n",
"fields": {
"BOOL": "FALSE"
}
}
}
},
"next": {
"block": {
"type": "delay",
"id": "45BA([9g7/_tItSnUwB-",
"fields": {
"DURATION_MS": 500
}
}
}
}
}
}
} }
} }
} }