Bobcat Miner 300

Documentation below currently based on only observing the G295 model.

LoRa interface: SPI over mPCIe

Antenna connector: IPEX1

Software: a Debian image exists

'3/5 (101)' means gpiochip 3, line 5, which is 101 in the global pin count.

Currently unclear which CS line is which of 3/1 and 3/2

Bobcat Miner 300 Spec label Top side Bottom side Spec label Bobcat Miner 300
4/21 (149) WAKE# 1 2 3.3V
SCK 3/19 (115) Reserved**** 3 4 GND GND
MISO 3/18 (114) Reserved**** 5 6 1.5V MOSI 3/17 (113)
CS (spidev5.0) on 3/1 (97) or 3/2 (98) CLKREQ# 7 8 VCC**
GND GND 9 10 I/O**
REFCLK- 11 12 CLK**
REFCLK+ 13 14 RST**
GND N/C or GND 15 16 VPP**
key key
CS (spidev5.1) on 3/2 (98) or 3/1 (97) Reserved 17 18 GND GND
4/19 (147) Reserved 19 20 Reserved***
GND GND 21 22 PERST# 4/22 (150)
3/3 (99) PERn0 23 24 +3.3Vaux
PERp0 25 26 GND GND
GND GND 27 28 +1.5V
GND GND 29 30 SMB_CLK 3/24 (120)
PETn0 31 32 SMB_DATA 3/23 (119)
PETp0 33 34 GND GND
GND GND 35 36 USB_D-
Reserved* 37 38 USB_D+
Reserved* 39 40 GND GND
Reserved* 41 42 LED_WWAN#
GND Reserved* 43 44 LED_WLAN# 3/22 (118)
i2c SCL 4/23 (151) or 4/24 (152) Reserved* 45 46 LED_WPAN# 3/29 (125)
i2c SDA 4/24 (152) or 4/23 (151) Reserved* 47 48 +1.5V
Reserved* 49 50 GND GND
Reserved* 51 52 +3.3V

hacky working pymc setup

There is a newradios branch in pymc core & repeater that likely makes all of this possible without local patching (other than perhaps the board definition, in core, while repeater has configuration for that).

The pinout chosen here may not be the best. Because the bobcat puts SPI on different pins than other boards seen so far, we need to figure out if routing SPI to two sets of pins can create a miniPCIe board that works for the bobcat -and- for other devices.

diff against pymc_core revision ba3ff4c26b9fadda1995c1ef6e21c7f143768c8d (main branch as of 12 Jan 2026)
diff --git a/examples/common.py b/examples/common.py
index 826b542..8e68faa 100644
--- a/examples/common.py
+++ b/examples/common.py
@@ -121,6 +121,23 @@ def create_radio(radio_type: str = "waveshare", serial_port: str = "/dev/ttyUSB0
                 "coding_rate": 5,
                 "preamble_length": 17,
             },
+            "wibra": {
+                "bus_id": 5,
+                "cs_id": 0,
+                "cs_pin": -1,
+                "reset_pin": 150 - 128,
+                "busy_pin": 147 - 128,
+                "irq_pin": 149 - 128,
+                "txen_pin": -1,
+                "frequency": int(869.618 * 1000000),  # EU: 869.525 MHz,
+                "tx_power": 22,
+                "spreading_factor": 8,
+                "bandwidth": int(62.5 * 1000),
+                "coding_rate": 8,
+                "preamble_length": 17,
+                "use_dio3_tcxo": True
+            }
+
         }

         if radio_type not in configs:
diff --git a/examples/discover_nodes.py b/examples/discover_nodes.py
index 097be79..2db003f 100644
--- a/examples/discover_nodes.py
+++ b/examples/discover_nodes.py
@@ -44,6 +44,11 @@ async def discover_nodes(
     """
     mesh_node, identity = create_mesh_node("DiscoveryNode", radio_type, serial_port)

+    print(mesh_node.radio)
+    print(dir(mesh_node.radio))
+    print(dir(mesh_node.radio.lora))
+    print(''.join(map(chr, mesh_node.radio.lora.readRegister(0x320, 16))))
+
     # Dictionary to store discovered nodes
     discovered_nodes = {}

@@ -149,7 +154,7 @@ def main():
     parser = argparse.ArgumentParser(description="Discover nearby mesh nodes")
     parser.add_argument(
         "--radio-type",
-        choices=["waveshare", "uconsole", "meshadv-mini", "kiss-tnc"],
+        choices=["waveshare", "uconsole", "meshadv-mini", "kiss-tnc", "wibra"],
         default="waveshare",
         help="Radio hardware type (default: waveshare)",
     )
diff --git a/src/pymc_core/hardware/gpio_manager.py b/src/pymc_core/hardware/gpio_manager.py
index 399a5da..fa7f900 100644
--- a/src/pymc_core/hardware/gpio_manager.py
+++ b/src/pymc_core/hardware/gpio_manager.py
@@ -44,7 +44,7 @@ logger = logging.getLogger("GPIOPinManager")
 class GPIOPinManager:
     """Manages GPIO pins abstraction using Linux GPIO character device interface"""

-    def __init__(self, gpio_chip: str = "/dev/gpiochip0"):
+    def __init__(self, gpio_chip: str = "/dev/gpiochip4"):
         """
         Initialize GPIO Pin Manager

diff --git a/src/pymc_core/hardware/lora/LoRaRF/SX126x.py b/src/pymc_core/hardware/lora/LoRaRF/SX126x.py
index 31bedd4..3a672ef 100644
--- a/src/pymc_core/hardware/lora/LoRaRF/SX126x.py
+++ b/src/pymc_core/hardware/lora/LoRaRF/SX126x.py
@@ -468,7 +468,7 @@ class SX126x(BaseLoRa):
                 self._cs_define = 17  # Default to GPIO 17 (safe alternative)
         else:
             # Keep original hardcoded value for unknown buses
-            self._cs_define = 21
+            self._cs_define = 1

         # open spi line and set bus id, chip select, and spi speed
         spi.open(bus, cs)
@@ -1484,6 +1484,7 @@ class SX126x(BaseLoRa):
         # Adaptive CS control based on CS pin type
         if self._cs_define != 8:  # Manual CS pin (like Waveshare GPIO 21)
             # Simple CS control for manual pins
+            print("_cs_define", self._cs_define)
             _get_output(self._cs_define).write(False)
             buf = [opCode]
             for i in range(nBytes):

pinout:

  • SPI (CLK/MOSI/MISO/CE) on minipcie pins 3, 6, 5, 7.
  • RST on 22
  • BUSY on 19
  • IRQ/DIO1 on 1

plus 3v3+GND from a wide choice of pins

photo of bobcat with sx1262 connected to minipcie via protoboard

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9