Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/calibrate_cad.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import logging
from typing import Any, Dict, List, Optional, Tuple

from common import create_radio
from common import create_radio, RADIO_TYPES

logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -304,7 +304,7 @@ def main():
parser = argparse.ArgumentParser(description="CAD Calibration Tool with Staged Workflow")
parser.add_argument(
"--radio",
choices=["waveshare", "uconsole", "meshadv-mini", "kiss-tnc", "kiss-modem"],
choices=RADIO_TYPES,
default="waveshare",
help="Radio type",
)
Expand Down
72 changes: 50 additions & 22 deletions examples/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
from pymc_core.node.node import MeshNode


RADIO_TYPES = ["waveshare", "uconsole", "meshadv-mini", "kiss-tnc", "kiss-modem", "ch341", "pinedio"]


def create_radio(
radio_type: str = "waveshare",
serial_port: str = "/dev/ttyUSB0",
Expand All @@ -46,6 +49,7 @@ def create_radio(
"kiss-tnc" — KISS TNC over serial
"kiss-modem" — MeshCore KISS modem over serial
"ch341" — SX1262 via CH341 USB-to-SPI adapter
"pinedio" — Similar to CH341 but different pinout
"pymc_usb" — pymc_usb firmware over USB-CDC
"pymc_tcp" — pymc_usb firmware over Wi-Fi/TCP
serial_port: Serial port path. Used by "kiss-tnc", "kiss-modem", and "pymc_usb".
Expand Down Expand Up @@ -116,7 +120,7 @@ def create_radio(
return modem_wrapper

# Check if this is a CH341 configuration
if radio_type == "ch341":
if radio_type in ("ch341", "pinedio"):
from pymc_core.hardware.ch341.ch341_gpio_manager import CH341GPIOManager
from pymc_core.hardware.lora.LoRaRF.SX126x import set_gpio_manager, set_spi_transport
from pymc_core.hardware.sx1262_wrapper import SX1262Radio
Expand All @@ -134,27 +138,51 @@ def create_radio(
set_spi_transport(ch341_spi)
logger.debug("Set CH341 SPI transport globally")

# CH341 pin configuration (using actual CH341 GPIO pins 0-7)
ch341_config = {
"bus_id": 0, # Not used with CH341 but required parameter
"cs_id": 0, # Not used with CH341 but required parameter
"cs_pin": 0, # CH341 GPIO 0 for CS
"reset_pin": 2, # CH341 GPIO 2 for Reset
"busy_pin": 4, # CH341 GPIO 4 for Busy
"irq_pin": 6, # CH341 GPIO 6 for IRQ
"txen_pin": -1, # Not used
"rxen_pin": 1, # CH341 GPIO 1 for RX enable
"frequency": int(869.618 * 1000000), # EU: 869.618 MHz
"tx_power": 22,
"spreading_factor": 8,
"bandwidth": int(62.5 * 1000),
"coding_rate": 8,
"preamble_length": 17,
"use_dio2_rf": True,
"is_waveshare": False, # Waveshare SX1262 LoRa HAT pinout
"use_dio3_tcxo": True, # Enable TCXO on DIO3
"dio3_tcxo_voltage": 1.8, # 1.8V TCXO
}
# NOTE: pin numbers are CH341 GPIO pin numbers, see CH341GPIOPin
# documentation to see how that translates to CH341 pin numbers.
if radio_type == "ch341":
ch341_config = {
"bus_id": 0, # Not used with CH341 but required parameter
"cs_id": 0, # Not used with CH341 but required parameter
"cs_pin": 0, # CH341 GPIO 0 for CS
"reset_pin": 2, # CH341 GPIO 2 for Reset
"busy_pin": 4, # CH341 GPIO 4 for Busy
"irq_pin": 6, # CH341 GPIO 6 for IRQ
"txen_pin": -1, # Not used
"rxen_pin": 1, # CH341 GPIO 1 for RX enable
"frequency": int(869.618 * 1000000), # EU: 869.618 MHz
"tx_power": 22,
"spreading_factor": 8,
"bandwidth": int(62.5 * 1000),
"coding_rate": 8,
"preamble_length": 17,
"use_dio2_rf": True,
"is_waveshare": False, # Waveshare SX1262 LoRa HAT pinout
"use_dio3_tcxo": True, # Enable TCXO on DIO3
"dio3_tcxo_voltage": 1.8, # 1.8V TCXO
}
elif radio_type == "pinedio":
# NOTE pinedio doesn't expose DIO2, therefore no control
# over TXEN/RXEN.
ch341_config = {
"bus_id": 0, # Not used with CH341 but required parameter
"cs_id": 0, # Not used with CH341 but required parameter
"cs_pin": 0, # CH341 GPIO 0 for CS (handled by SPI engine)
"reset_pin": -1, # reset is done via CH341 reset
"busy_pin": 11, # CH341 GPIO 11 = pin 8 (BUSY/SLCT)
"irq_pin": 10, # CH341 GPIO 10 = pin 7 (INT#/DIO1)
"txen_pin": -1, # TXEN is done internally via MDIO2
"rxen_pin": -1, # RXEN is inverse of TXEN
"frequency": int(869.618 * 1000000), # EU: 869.618 MHz
"tx_power": 22,
"spreading_factor": 8,
"bandwidth": int(62.5 * 1000),
"coding_rate": 8,
"preamble_length": 17,
"use_dio2_rf": True,
"is_waveshare": False, # Waveshare SX1262 LoRa HAT pinout
"use_dio3_tcxo": False, # Enable TCXO on DIO3
}

logger.debug(f"CH341 configuration: {ch341_config}")
radio = SX1262Radio(**ch341_config)
Expand Down
4 changes: 2 additions & 2 deletions examples/discover_nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
import random
import time

from common import create_mesh_node
from common import create_mesh_node, RADIO_TYPES

from pymc_core.protocol.packet_builder import PacketBuilder

Expand Down Expand Up @@ -149,7 +149,7 @@ def main():
parser = argparse.ArgumentParser(description="Discover nearby mesh nodes")
parser.add_argument(
"--radio-type",
choices=["waveshare", "uconsole", "meshadv-mini", "kiss-tnc", "kiss-modem"],
choices=RADIO_TYPES,
default="waveshare",
help="Radio hardware type (default: waveshare)",
)
Expand Down
4 changes: 2 additions & 2 deletions examples/login_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import time
from typing import Dict, Optional

from common import create_mesh_node
from common import create_mesh_node, RADIO_TYPES

from pymc_core.node.handlers.login_server import LoginServerHandler
from pymc_core.protocol import Identity, LocalIdentity
Expand Down Expand Up @@ -381,7 +381,7 @@ def main():
)
parser.add_argument(
"--radio-type",
choices=["waveshare", "uconsole", "meshadv-mini", "kiss-tnc", "kiss-modem"],
choices=RADIO_TYPES,
default="waveshare",
help="Radio hardware type (default: waveshare)",
)
Expand Down
4 changes: 2 additions & 2 deletions examples/ping_repeater_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import asyncio
import random

from common import create_mesh_node, print_packet_info
from common import create_mesh_node, print_packet_info, RADIO_TYPES

from pymc_core.protocol.constants import PAYLOAD_TYPE_TRACE
from pymc_core.protocol.packet_builder import PacketBuilder
Expand Down Expand Up @@ -86,7 +86,7 @@ def main():
parser = argparse.ArgumentParser(description="Ping a repeater using trace packets")
parser.add_argument(
"--radio-type",
choices=["waveshare", "uconsole", "meshadv-mini", "kiss-tnc", "kiss-modem"],
choices=RADIO_TYPES,
default="waveshare",
help="Radio hardware type (default: waveshare)",
)
Expand Down
4 changes: 2 additions & 2 deletions examples/respond_to_discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import asyncio

from common import create_mesh_node
from common import create_mesh_node, RADIO_TYPES

from pymc_core.protocol.packet_builder import PacketBuilder

Expand Down Expand Up @@ -121,7 +121,7 @@ def main():
parser = argparse.ArgumentParser(description="Respond to mesh node discovery requests")
parser.add_argument(
"--radio-type",
choices=["waveshare", "uconsole", "meshadv-mini", "kiss-tnc", "kiss-modem"],
choices=RADIO_TYPES,
default="waveshare",
help="Radio hardware type (default: waveshare)",
)
Expand Down
4 changes: 2 additions & 2 deletions examples/send_channel_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import asyncio

from common import create_mesh_node, print_packet_info
from common import create_mesh_node, print_packet_info, RADIO_TYPES

from pymc_core.protocol import Packet
from pymc_core.protocol.packet_builder import PacketBuilder
Expand Down Expand Up @@ -71,7 +71,7 @@ def main():
parser = argparse.ArgumentParser(description="Send a channel message to the Public channel")
parser.add_argument(
"--radio-type",
choices=["waveshare", "uconsole", "meshadv-mini", "kiss-tnc", "kiss-modem"],
choices=RADIO_TYPES,
default="waveshare",
help="Radio hardware type (default: waveshare)",
)
Expand Down
4 changes: 2 additions & 2 deletions examples/send_direct_advert.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

import asyncio

from common import create_mesh_node, print_packet_info
from common import create_mesh_node, print_packet_info, RADIO_TYPES

from pymc_core.protocol.constants import ADVERT_FLAG_IS_CHAT_NODE
from pymc_core.protocol.packet_builder import PacketBuilder
Expand Down Expand Up @@ -51,7 +51,7 @@ def main():
parser = argparse.ArgumentParser(description="Send a direct advertisement packet")
parser.add_argument(
"--radio-type",
choices=["waveshare", "uconsole", "meshadv-mini", "kiss-tnc", "kiss-modem"],
choices=RADIO_TYPES,
default="waveshare",
help="Radio hardware type (default: waveshare)",
)
Expand Down
4 changes: 2 additions & 2 deletions examples/send_flood_advert.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import asyncio
import sys

from common import create_mesh_node, print_packet_info
from common import create_mesh_node, print_packet_info, RADIO_TYPES

from pymc_core.protocol.constants import ADVERT_FLAG_IS_CHAT_NODE
from pymc_core.protocol.packet_builder import PacketBuilder
Expand Down Expand Up @@ -58,7 +58,7 @@ def main():
parser = argparse.ArgumentParser(description="Send a flood advertisement packet")
parser.add_argument(
"--radio-type",
choices=["waveshare", "uconsole", "meshadv-mini", "kiss-tnc", "kiss-modem"],
choices=RADIO_TYPES,
default="waveshare",
help="Radio hardware type (default: waveshare)",
)
Expand Down
4 changes: 2 additions & 2 deletions examples/send_text_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import asyncio

from common import create_mesh_node, print_packet_info
from common import create_mesh_node, print_packet_info, RADIO_TYPES

from pymc_core.protocol import Packet
from pymc_core.protocol.packet_builder import PacketBuilder
Expand Down Expand Up @@ -78,7 +78,7 @@ def main():
parser = argparse.ArgumentParser(description="Send a text message to the mesh network")
parser.add_argument(
"--radio-type",
choices=["waveshare", "uconsole", "meshadv-mini", "kiss-tnc", "kiss-modem"],
choices=RADIO_TYPES,
default="waveshare",
help="Radio hardware type (default: waveshare)",
)
Expand Down
4 changes: 2 additions & 2 deletions examples/send_tracked_advert.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import asyncio
import time

from common import create_mesh_node, print_packet_info
from common import create_mesh_node, print_packet_info, RADIO_TYPES

from pymc_core.protocol.constants import (
ADVERT_FLAG_HAS_LOCATION,
Expand Down Expand Up @@ -93,7 +93,7 @@ def main():
parser = argparse.ArgumentParser(description="Send a location-tracked advertisement")
parser.add_argument(
"--radio-type",
choices=["waveshare", "uconsole", "meshadv-mini", "kiss-tnc", "kiss-modem", "ch341"],
choices=RADIO_TYPES,
default="waveshare",
help="Radio hardware type (default: waveshare)",
)
Expand Down
13 changes: 8 additions & 5 deletions src/pymc_core/hardware/ch341/ch341_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,8 +347,8 @@ def gpio_set_direction(self, pin: int, is_output: bool):
return self._gpio_set_direction_impl(pin, is_output)

def _gpio_set_direction_impl(self, pin: int, is_output: bool):
if pin < 0 or pin > 7:
raise ValueError(f"GPIO pin must be 0-7, got {pin}")
if pin < 0 or pin > 15:
raise ValueError(f"GPIO pin must be 0-15, got {pin}")

if pin > 5 and is_output:
raise ValueError(f"GPIO pin {pin} only supports input mode (pins 6-7 are input-only)")
Expand All @@ -372,8 +372,8 @@ def gpio_get(self, pin: int) -> bool:
self._operation_lock.release()

def _gpio_get_impl(self, pin: int) -> bool:
if pin < 0 or pin > 7:
raise ValueError(f"GPIO pin must be 0-7, got {pin}")
if pin < 0 or pin > 15:
raise ValueError(f"GPIO pin must be 0-15, got {pin}")

GPIO_READ_BYTES = 6
GPIO_READ_TIMEOUT_MS = 200
Expand All @@ -388,7 +388,10 @@ def _gpio_get_impl(self, pin: int) -> bool:
except Exception as e:
raise CH341Error(f"GPIO read IN failed: {e}") from e

return bool(data[0] & (1 << pin))
if pin < 8:
return bool(data[0] & (1 << pin))
else:
return bool(data[1] & (1 << (pin - 8)))

def __enter__(self):
return self
Expand Down
Loading
Loading