feat: update GpioNode to use gpiod v2.x API for batch line requests and improved GPIO handling
parent
9f7ac848d8
commit
0bdd3040e0
|
|
@ -11,6 +11,8 @@ Alur komunikasi:
|
||||||
Jika gpiod tidak tersedia (misalnya di x86 dev machine), node tetap berjalan
|
Jika gpiod tidak tersedia (misalnya di x86 dev machine), node tetap berjalan
|
||||||
dalam simulation mode — hanya logging tanpa akses hardware. Ini memungkinkan
|
dalam simulation mode — hanya logging tanpa akses hardware. Ini memungkinkan
|
||||||
pengembangan dan testing tanpa Pi.
|
pengembangan dan testing tanpa Pi.
|
||||||
|
|
||||||
|
Menggunakan gpiod v2.x API (request_lines / LineSettings / Value).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import rclpy
|
import rclpy
|
||||||
|
|
@ -23,6 +25,7 @@ from blockly_interfaces.msg import GpioWrite, GpioRead
|
||||||
# berjalan dalam simulation mode.
|
# berjalan dalam simulation mode.
|
||||||
try:
|
try:
|
||||||
import gpiod
|
import gpiod
|
||||||
|
from gpiod.line import Direction, Value
|
||||||
HAS_GPIOD = True
|
HAS_GPIOD = True
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HAS_GPIOD = False
|
HAS_GPIOD = False
|
||||||
|
|
@ -40,10 +43,10 @@ class GpioNode(Node):
|
||||||
self.declare_parameter("input_publish_rate", 10.0) # Hz
|
self.declare_parameter("input_publish_rate", 10.0) # Hz
|
||||||
self.declare_parameter("gpio_chip", "/dev/gpiochip0") # gpiod v2.x butuh path lengkap
|
self.declare_parameter("gpio_chip", "/dev/gpiochip0") # gpiod v2.x butuh path lengkap
|
||||||
|
|
||||||
self._output_pins = (
|
self._output_pins = list(
|
||||||
self.get_parameter("output_pins").get_parameter_value().integer_array_value
|
self.get_parameter("output_pins").get_parameter_value().integer_array_value
|
||||||
)
|
)
|
||||||
self._input_pins = (
|
self._input_pins = list(
|
||||||
self.get_parameter("input_pins").get_parameter_value().integer_array_value
|
self.get_parameter("input_pins").get_parameter_value().integer_array_value
|
||||||
)
|
)
|
||||||
rate = (
|
rate = (
|
||||||
|
|
@ -53,9 +56,10 @@ class GpioNode(Node):
|
||||||
self.get_parameter("gpio_chip").get_parameter_value().string_value
|
self.get_parameter("gpio_chip").get_parameter_value().string_value
|
||||||
)
|
)
|
||||||
|
|
||||||
self._chip = None
|
# gpiod v2.x: request_lines() mengembalikan LineRequest object,
|
||||||
self._output_lines: dict[int, object] = {}
|
# bukan per-line object seperti v1.x.
|
||||||
self._input_lines: dict[int, object] = {}
|
self._output_request = None
|
||||||
|
self._input_request = None
|
||||||
|
|
||||||
# Instance flag — True hanya jika gpiod tersedia DAN chip berhasil dibuka.
|
# Instance flag — True hanya jika gpiod tersedia DAN chip berhasil dibuka.
|
||||||
# Berbeda dari module-level HAS_GPIOD yang hanya cek import.
|
# Berbeda dari module-level HAS_GPIOD yang hanya cek import.
|
||||||
|
|
@ -93,29 +97,40 @@ class GpioNode(Node):
|
||||||
self._read_timer = self.create_timer(period, self._read_callback)
|
self._read_timer = self.create_timer(period, self._read_callback)
|
||||||
|
|
||||||
self.get_logger().info(
|
self.get_logger().info(
|
||||||
f"GpioNode ready — outputs={list(self._output_pins)}, "
|
f"GpioNode ready — outputs={self._output_pins}, "
|
||||||
f"inputs={list(self._input_pins)}, "
|
f"inputs={self._input_pins}, "
|
||||||
f"gpiod={'yes' if self._has_hardware else 'no (simulation)'}"
|
f"gpiod={'yes' if self._has_hardware else 'no (simulation)'}"
|
||||||
)
|
)
|
||||||
|
|
||||||
def _setup_gpio(self, chip_name: str) -> None:
|
def _setup_gpio(self, chip_name: str) -> None:
|
||||||
"""Konfigurasi GPIO lines via gpiod.
|
"""Konfigurasi GPIO lines via gpiod v2.x API.
|
||||||
|
|
||||||
Setiap pin di-request dengan consumer name "gpio_node" agar mudah
|
gpiod v2.x menggunakan request_lines() untuk merequest semua pin
|
||||||
diidentifikasi via `gpioinfo` jika ada konflik.
|
sekaligus, berbeda dari v1.x yang per-line. LineSettings menentukan
|
||||||
|
direction (input/output) untuk setiap pin.
|
||||||
"""
|
"""
|
||||||
self._chip = gpiod.Chip(chip_name)
|
# Request output lines sebagai satu batch
|
||||||
|
if self._output_pins:
|
||||||
|
out_config = {
|
||||||
|
pin: gpiod.LineSettings(direction=Direction.OUTPUT)
|
||||||
|
for pin in self._output_pins
|
||||||
|
}
|
||||||
|
self._output_request = gpiod.request_lines(
|
||||||
|
chip_name, consumer="gpio_node", config=out_config
|
||||||
|
)
|
||||||
for pin in self._output_pins:
|
for pin in self._output_pins:
|
||||||
line = self._chip.get_line(pin)
|
|
||||||
line.request(consumer="gpio_node", type=gpiod.LINE_REQ_DIR_OUT)
|
|
||||||
self._output_lines[pin] = line
|
|
||||||
self.get_logger().info(f"Configured pin {pin} as OUTPUT")
|
self.get_logger().info(f"Configured pin {pin} as OUTPUT")
|
||||||
|
|
||||||
|
# Request input lines sebagai satu batch
|
||||||
|
if self._input_pins:
|
||||||
|
in_config = {
|
||||||
|
pin: gpiod.LineSettings(direction=Direction.INPUT)
|
||||||
|
for pin in self._input_pins
|
||||||
|
}
|
||||||
|
self._input_request = gpiod.request_lines(
|
||||||
|
chip_name, consumer="gpio_node", config=in_config
|
||||||
|
)
|
||||||
for pin in self._input_pins:
|
for pin in self._input_pins:
|
||||||
line = self._chip.get_line(pin)
|
|
||||||
line.request(consumer="gpio_node", type=gpiod.LINE_REQ_DIR_IN)
|
|
||||||
self._input_lines[pin] = line
|
|
||||||
self.get_logger().info(f"Configured pin {pin} as INPUT")
|
self.get_logger().info(f"Configured pin {pin} as INPUT")
|
||||||
|
|
||||||
def _write_callback(self, msg: GpioWrite) -> None:
|
def _write_callback(self, msg: GpioWrite) -> None:
|
||||||
|
|
@ -129,14 +144,15 @@ class GpioNode(Node):
|
||||||
state = msg.state
|
state = msg.state
|
||||||
state_str = "HIGH" if state else "LOW"
|
state_str = "HIGH" if state else "LOW"
|
||||||
|
|
||||||
if pin not in self._output_lines and self._has_hardware:
|
if pin not in self._output_pins and self._has_hardware:
|
||||||
self.get_logger().warn(
|
self.get_logger().warn(
|
||||||
f"Pin {pin} not configured as output — ignoring write"
|
f"Pin {pin} not configured as output — ignoring write"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if self._has_hardware:
|
if self._has_hardware and self._output_request:
|
||||||
self._output_lines[pin].set_value(1 if state else 0)
|
value = Value.ACTIVE if state else Value.INACTIVE
|
||||||
|
self._output_request.set_value(pin, value)
|
||||||
|
|
||||||
self.get_logger().info(f"GPIO write: pin={pin} state={state_str}")
|
self.get_logger().info(f"GPIO write: pin={pin} state={state_str}")
|
||||||
|
|
||||||
|
|
@ -147,12 +163,12 @@ class GpioNode(Node):
|
||||||
Setiap pin dipublish sebagai message terpisah agar subscriber
|
Setiap pin dipublish sebagai message terpisah agar subscriber
|
||||||
bisa memfilter berdasarkan pin number.
|
bisa memfilter berdasarkan pin number.
|
||||||
"""
|
"""
|
||||||
for pin, line in self._input_lines.items():
|
for pin in self._input_pins:
|
||||||
msg = GpioRead()
|
msg = GpioRead()
|
||||||
msg.pin = pin
|
msg.pin = pin
|
||||||
|
|
||||||
if self._has_hardware:
|
if self._has_hardware and self._input_request:
|
||||||
msg.state = bool(line.get_value())
|
msg.state = self._input_request.get_value(pin) == Value.ACTIVE
|
||||||
else:
|
else:
|
||||||
msg.state = False # simulation: selalu LOW
|
msg.state = False # simulation: selalu LOW
|
||||||
|
|
||||||
|
|
@ -161,17 +177,14 @@ class GpioNode(Node):
|
||||||
def destroy_node(self) -> None:
|
def destroy_node(self) -> None:
|
||||||
"""Release GPIO lines saat shutdown.
|
"""Release GPIO lines saat shutdown.
|
||||||
|
|
||||||
Penting untuk melepaskan line agar pin bisa digunakan kembali
|
gpiod v2.x: release() dipanggil pada LineRequest object,
|
||||||
oleh proses lain tanpa reboot. Jika tidak di-release, gpiod
|
bukan per-line seperti v1.x.
|
||||||
akan menandai pin sebagai "busy".
|
|
||||||
"""
|
"""
|
||||||
if self._has_hardware:
|
if self._has_hardware:
|
||||||
for line in self._output_lines.values():
|
if self._output_request:
|
||||||
line.release()
|
self._output_request.release()
|
||||||
for line in self._input_lines.values():
|
if self._input_request:
|
||||||
line.release()
|
self._input_request.release()
|
||||||
if self._chip is not None:
|
|
||||||
self._chip.close()
|
|
||||||
self.get_logger().info("GPIO lines released")
|
self.get_logger().info("GPIO lines released")
|
||||||
super().destroy_node()
|
super().destroy_node()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue