Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e9891dd
Add MeshCore TCP bridge for HA compatibility
yellowcooln Jan 25, 2026
358b609
Handle string contact types in MeshCore bridge
yellowcooln Jan 25, 2026
229d7ef
Emit login success for MeshCore bridge
yellowcooln Jan 25, 2026
2402f62
Delay binary responses for pending request registration
yellowcooln Jan 25, 2026
a4067a9
Handle set_other_params command in MeshCore bridge
yellowcooln Jan 25, 2026
0f51b58
Inject RF packets for HA repeater commands
yellowcooln Jan 25, 2026
a057a71
Forward RF protocol responses to HA over TCP
yellowcooln Jan 25, 2026
73757a3
Record bridge RF transmissions in packet storage
yellowcooln Jan 25, 2026
28547da
Add AGENTS.md documenting MeshCore bridge changes
yellowcooln Jan 25, 2026
202acc0
Fix HA telemetry, battery, and contacts
yellowcooln Jan 26, 2026
9b8dc0e
Harden CPU temp and telemetry filtering
yellowcooln Jan 26, 2026
5f58cde
Add telemetry debug logging
yellowcooln Jan 26, 2026
c268af3
Filter remote telemetry to temperature only
yellowcooln Jan 26, 2026
69848ff
Force remote temperature to channel 1
yellowcooln Jan 26, 2026
a8e53cc
Swap LPP order for HA
yellowcooln Jan 26, 2026
ab649d2
Add LPP length prefix for HA
yellowcooln Jan 26, 2026
b6ecdd6
Add channels config and bridge message support
yellowcooln Jan 29, 2026
13255c0
Copy channels config on install/upgrade
yellowcooln Jan 29, 2026
d63a45f
Add verbose MeshCore bridge logging
yellowcooln Jan 29, 2026
ca79918
Revert debug flag in config example
yellowcooln Jan 29, 2026
a8f8ce2
Allow flood routing for direct messages
yellowcooln Jan 29, 2026
c889dbc
Flood when no path and for channel messages
yellowcooln Jan 29, 2026
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
93 changes: 93 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# AGENTS.md

This file documents the manual changes made to integrate pyMC_Repeater with Home Assistant’s meshcore-ha using a MeshCore TCP bridge.

## Summary
A MeshCore TCP bridge was added so meshcore-ha can connect to pyMC_Repeater via TCP without modifying HA. The bridge:
- Implements MeshCore TCP framing and essential commands.
- Responds to MeshCore protocol queries (appstart, device info, contacts, status, telemetry, etc.).
- Transmits actual RF packets for login/logout, status, telemetry, and path discovery/reset.
- Decrypts RF protocol responses and forwards them back to HA over TCP.
- Normalizes radio/telemetry/battery reporting to keep HA sensors clean and accurate.

## Files Added/Modified

### New
- `repeater/meshcore_bridge.py`
- TCP server that emulates MeshCore protocol for meshcore-ha.
- Generates SELF_INFO, DEVICE_INFO, CONTACTS, STATUS_RESPONSE, TELEMETRY_RESPONSE, LOGIN_SUCCESS, etc.
- Injects real RF packets using `pymc_core` for:
- Login / logout
- Status request
- Telemetry request
- Path discovery / reset path (best-effort trace)
- Decrypts RF protocol responses (PAYLOAD_TYPE_RESPONSE / PATH) and forwards to HA.
- Sends DEVICE_INFO + CONTACTS on APPSTART so HA can populate contacts/node count.
- Reports battery at 4200 mV (full LiPo) in both BATTERY and status payloads.
- Uses Hz for radio frequency/bandwidth in SELF_INFO (no kHz scaling).
- Self telemetry reports CPU temperature on channel 1 (Cayenne LPP, °C).
- Filters self telemetry to only channel 1 temperature.
- Drops Digital Input/Output LPP types globally to avoid noisy HA sensors.
- Filters CONTACTS to match UI “Tracking” (contact types + last 7 days) and uses out_path_len=0.

### Modified
- `repeater/main.py`
- Starts/stops the MeshCore TCP bridge based on config.
- `repeater/packet_router.py`
- Routes RF response packets to the bridge for decryption/forwarding.
- `config.yaml.example`
- Adds `meshcore_bridge` configuration block.

## Config
Add this to `/etc/pymc_repeater/config.yaml`:
```yaml
meshcore_bridge:
enabled: true
host: "0.0.0.0"
port: 5000
```
Use a different port if your UI is not on 8000 or if 5000 is in use.

## MeshCore TCP Bridge Behavior

### Implemented commands
- `send_appstart` → `SELF_INFO`
- `send_appstart` also sends `DEVICE_INFO` and `CONTACTS` for HA contact discovery
- `device_query` → `DEVICE_INFO`
- `get_contacts` → contacts from pyMC neighbor DB
- `send_login` → `MSG_SENT` + `LOGIN_SUCCESS` (and RF login packet)
- `send_logout` → `MSG_SENT` (and RF logout packet)
- `send_statusreq` and `binary_req(STATUS)` → `MSG_SENT`, RF status request, and TCP status response
- `get_self_telemetry` and `binary_req(TELEMETRY)` → `MSG_SENT`, RF telemetry request, and TCP telemetry response
- `set_other_params` → `OK`
- `reset_path` → `OK` (and RF trace packet)
- `path_discovery` → `MSG_SENT` (and RF trace packet)
- `get_time`, `set_time`, `get_bat`, `get_msg`, `send_advert` → basic responses

### RF injection
RF packets are sent using `pymc_core.protocol.PacketBuilder` and `Dispatcher.send_packet`.

### RF response forwarding
The bridge:
- Intercepts PAYLOAD_TYPE_RESPONSE/PATH packets.
- Decrypts them using shared secret.
- For status: converts pyMC’s 58-byte stats struct into MeshCore’s expected format.
- For telemetry: forwards the CayenneLPP payload to HA.

## Known limitations
- Path discovery/reset uses trace packets as best-effort.
- Some MeshCore admin/config commands are ACK-only (no device config changes).
- Contact list depends on pyMC neighbor DB; it may be empty on fresh setups.

## Commits (tcp-bridge branch)
- `Add MeshCore TCP bridge for HA compatibility`
- `Handle string contact types in MeshCore bridge`
- `Emit login success for MeshCore bridge`
- `Delay binary responses for pending request registration`
- `Handle set_other_params command in MeshCore bridge`
- `Inject RF packets for HA repeater commands`
- `Forward RF protocol responses to HA over TCP`
- `Fix radio units, battery, telemetry, and contact reporting for HA`

## Usage in HA
Use meshcore-ha TCP mode and point it to the bridge host/port.
6 changes: 6 additions & 0 deletions channels.yaml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
channels:
- name: "general"
# Use a 16-byte hex string or any UTF-8 string (will be used as-is).
secret: "00112233445566778899aabbccddeeff"
- name: "alerts"
secret: "alerts-secret"
9 changes: 9 additions & 0 deletions config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ repeater:
# Controls how long users stay logged in before needing to re-authenticate
jwt_expiry_minutes: 60

# MeshCore TCP bridge (optional, for Home Assistant meshcore-ha TCP mode)
meshcore_bridge:
# Enable MeshCore TCP bridge
enabled: false

# TCP listen host/port (meshcore-ha default TCP port is 5000)
host: "0.0.0.0"
port: 5000

# Mesh Network Configuration
mesh:
# Global flood policy - controls whether the repeater allows or denies flooding by default
Expand Down
1 change: 1 addition & 0 deletions debian/pymc-repeater.install
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
config.yaml.example usr/share/pymc_repeater/
channels.yaml.example usr/share/pymc_repeater/
radio-presets.json usr/share/pymc_repeater/
radio-settings.json usr/share/pymc_repeater/
7 changes: 7 additions & 0 deletions debian/pymc-repeater.postinst
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ case "$1" in
chmod 640 /etc/pymc_repeater/config.yaml
fi

# Copy channels config if no channels file exists
if [ ! -f /etc/pymc_repeater/channels.yaml ]; then
cp /usr/share/pymc_repeater/channels.yaml.example /etc/pymc_repeater/channels.yaml
chown pymc-repeater:pymc-repeater /etc/pymc_repeater/channels.yaml
chmod 640 /etc/pymc_repeater/channels.yaml
fi

# Install pymc_core from PyPI if not already installed
if ! python3 -c "import pymc_core" 2>/dev/null; then
echo "Installing pymc_core dependency from PyPI..."
Expand Down
22 changes: 18 additions & 4 deletions manage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ install_repeater() {
echo "25"; echo "# Installing system dependencies..."
apt-get update -qq
apt-get install -y libffi-dev jq pip python3-rrdtool wget swig build-essential python3-dev
pip install --break-system-packages setuptools_scm >/dev/null 2>&1 || true
pip install setuptools_scm >/dev/null 2>&1 || true

# Install mikefarah yq v4 if not already installed
if ! command -v yq &> /dev/null || [[ "$(yq --version 2>&1)" != *"mikefarah/yq"* ]]; then
Expand Down Expand Up @@ -261,6 +261,13 @@ install_repeater() {
if [ ! -f "$CONFIG_DIR/config.yaml" ]; then
cp "$SCRIPT_DIR/config.yaml.example" "$CONFIG_DIR/config.yaml"
fi
# Channels config (for MeshCore HA channel support)
if [ -f "$SCRIPT_DIR/channels.yaml.example" ]; then
cp "$SCRIPT_DIR/channels.yaml.example" "$CONFIG_DIR/channels.yaml.example"
if [ ! -f "$CONFIG_DIR/channels.yaml" ]; then
cp "$SCRIPT_DIR/channels.yaml.example" "$CONFIG_DIR/channels.yaml"
fi
fi

echo "55"; echo "# Installing systemd service..."
cp "$SCRIPT_DIR/pymc-repeater.service" /etc/systemd/system/
Expand Down Expand Up @@ -323,7 +330,7 @@ EOF
echo "Note: Using optimized binary wheels for faster installation"
echo ""

if pip install --break-system-packages --force-reinstall --no-cache-dir .; then
if pip install --no-cache-dir .; then
echo ""
echo "✓ Python package installation completed successfully!"

Expand Down Expand Up @@ -399,7 +406,7 @@ upgrade_repeater() {
apt-get update -qq

apt-get install -y libffi-dev jq pip python3-rrdtool wget swig build-essential python3-dev
pip install --break-system-packages setuptools_scm >/dev/null 2>&1 || true
pip install setuptools_scm >/dev/null 2>&1 || true

# Install mikefarah yq v4 if not already installed
if ! command -v yq &> /dev/null || [[ "$(yq --version 2>&1)" != *"mikefarah/yq"* ]]; then
Expand Down Expand Up @@ -453,6 +460,13 @@ upgrade_repeater() {
else
echo " ⚠ Configuration validation failed, keeping existing config"
fi
# Ensure channels config exists after upgrade
if [ -f "$SCRIPT_DIR/channels.yaml.example" ]; then
cp "$SCRIPT_DIR/channels.yaml.example" "$CONFIG_DIR/channels.yaml.example" 2>/dev/null || true
if [ ! -f "$CONFIG_DIR/channels.yaml" ]; then
cp "$SCRIPT_DIR/channels.yaml.example" "$CONFIG_DIR/channels.yaml" 2>/dev/null || true
fi
fi

echo "[6/9] Fixing permissions..."
chown -R "$SERVICE_USER:$SERVICE_USER" "$INSTALL_DIR" "$CONFIG_DIR" "$LOG_DIR" /var/lib/pymc_repeater 2>/dev/null || true
Expand Down Expand Up @@ -508,7 +522,7 @@ EOF
echo ""

# Upgrade packages (uses cache for unchanged dependencies - much faster)
if python3 -m pip install --break-system-packages --upgrade --upgrade-strategy eager .; then
if python3 -m pip install --upgrade --upgrade-strategy eager .; then
echo ""
echo "✓ Package and dependencies updated successfully!"
else
Expand Down
15 changes: 15 additions & 0 deletions repeater/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from repeater.config_manager import ConfigManager
from repeater.engine import RepeaterHandler
from repeater.web.http_server import HTTPStatsServer, _log_buffer
from repeater.meshcore_bridge import MeshcoreTCPBridge
from repeater.handler_helpers import TraceHelper, DiscoveryHelper, AdvertHelper, LoginHelper, TextHelper, PathHelper, ProtocolRequestHelper
from repeater.packet_router import PacketRouter
from repeater.identity_manager import IdentityManager
Expand All @@ -27,6 +28,7 @@ def __init__(self, config: dict, radio=None):
self.identity_manager = None
self.config_manager = None
self.http_server = None
self.meshcore_bridge = None
self.trace_helper = None
self.advert_helper = None
self.discovery_helper = None
Expand Down Expand Up @@ -500,6 +502,17 @@ async def run(self):
except Exception as e:
logger.error(f"Failed to start HTTP server: {e}")

# Start MeshCore TCP bridge (optional)
bridge_cfg = self.config.get("meshcore_bridge", {})
if bridge_cfg.get("enabled", False):
bridge_host = bridge_cfg.get("host", "0.0.0.0")
bridge_port = int(bridge_cfg.get("port", 5000))
try:
self.meshcore_bridge = MeshcoreTCPBridge(self, host=bridge_host, port=bridge_port)
await self.meshcore_bridge.start()
except Exception as e:
logger.error(f"Failed to start MeshCore TCP bridge: {e}")

# Run dispatcher (handles RX/TX via pymc_core)
try:
await self.dispatcher.run_forever()
Expand All @@ -509,6 +522,8 @@ async def run(self):
await self.router.stop()
if self.http_server:
self.http_server.stop()
if self.meshcore_bridge:
await self.meshcore_bridge.stop()


def main():
Expand Down
Loading