src.nth.io/

summaryrefslogtreecommitdiff
path: root/openhop/repeater
diff options
context:
space:
mode:
authorLuke Hoersten <[email protected]>2026-07-26 10:36:29 -0500
committerLuke Hoersten <[email protected]>2026-07-26 10:36:29 -0500
commit38323823b85b2ae8deef788a0f49e36c789c12f4 (patch)
tree94a3227a737ba0198dd06d3b7e001f219fe32bdc /openhop/repeater
parent1aaf4479d72edbd0c716d2e9122a7b613626bf12 (diff)
openhop/repeater: add MeshCore repeater role
Deploys the openHop MeshCore repeater on a Raspberry Pi with a RAK6421 WisMesh Pi HAT: installs the daemon into a venv from a versioned git tag, templates config.yaml and a systemd unit, and loads board pin maps and RF region presets by name from vars/. identity_file is configurable and defaults to null (daemon generates its own keypair).
Diffstat (limited to 'openhop/repeater')
-rw-r--r--openhop/repeater/defaults/main.yaml38
-rw-r--r--openhop/repeater/handlers/main.yaml8
-rw-r--r--openhop/repeater/tasks/main.yaml102
-rw-r--r--openhop/repeater/templates/config.yaml.j253
-rw-r--r--openhop/repeater/templates/openhop-repeater.service.j218
-rw-r--r--openhop/repeater/vars/boards/rak6421-slot1.yaml15
-rw-r--r--openhop/repeater/vars/boards/rak6421-slot2.yaml15
-rw-r--r--openhop/repeater/vars/rf/eu.yaml12
-rw-r--r--openhop/repeater/vars/rf/us.yaml11
9 files changed, 272 insertions, 0 deletions
diff --git a/openhop/repeater/defaults/main.yaml b/openhop/repeater/defaults/main.yaml
new file mode 100644
index 0000000..d26e73e
--- /dev/null
+++ b/openhop/repeater/defaults/main.yaml
@@ -0,0 +1,38 @@
+---
+
+repeater_version: "1.1.1"
+# https://github.com/openhop-dev/openhop_repeater/tags
+# openhop_repeater is NOT on PyPI, so it installs from this git tag.
+# Its dependency openhop_core[hardware] resolves from PyPI automatically.
+repeater_repo: "https://github.com/openhop-dev/openhop_repeater"
+
+# Service layout — mirrors upstream's own systemd unit and docs.
+repeater_user: "repeater"
+repeater_venv_dir: "/opt/openhop_repeater/venv"
+repeater_config_dir: "/etc/openhop_repeater"
+repeater_data_dir: "/var/lib/openhop_repeater"
+
+# --- Node identity ---
+repeater_node_name: "{{inventory_hostname}}"
+repeater_latitude: 0.0
+repeater_longitude: 0.0
+repeater_owner_info: ""
+# Path to the node's identity file (its MeshCore public/private keypair). The
+# daemon generates one here on first boot and reuses it thereafter, so pointing
+# this at persistent storage keeps a stable identity across reinstalls. Null
+# lets the daemon generate an ephemeral identity each run.
+repeater_identity_file: null
+
+# --- Hardware + RF presets (loaded by name from the role's vars/ dir) ---
+# WARNING: the RAK13302 has a 1W SKY66122 PA. NEVER power the HAT without the
+# antenna connected, or you risk destroying the radio front-end.
+#
+# repeater_board -> loads the pin map from vars/boards/<name>.yaml
+# available: rak6421-slot1, rak6421-slot2
+# (add a new board by dropping another file in vars/boards/)
+repeater_board: "rak6421-slot1"
+#
+# repeater_rf_preset -> loads the RF params from vars/rf/<name>.yaml
+# available: us, eu
+# (every node on a mesh must share the same RF preset)
+repeater_rf_preset: "us"
diff --git a/openhop/repeater/handlers/main.yaml b/openhop/repeater/handlers/main.yaml
new file mode 100644
index 0000000..583691b
--- /dev/null
+++ b/openhop/repeater/handlers/main.yaml
@@ -0,0 +1,8 @@
+---
+
+- name: restart openhop-repeater
+ become: yes
+ systemd:
+ name: "openhop-repeater.service"
+ state: "restarted"
+ daemon_reload: "yes"
diff --git a/openhop/repeater/tasks/main.yaml b/openhop/repeater/tasks/main.yaml
new file mode 100644
index 0000000..b24044c
--- /dev/null
+++ b/openhop/repeater/tasks/main.yaml
@@ -0,0 +1,102 @@
+---
+
+- name: load board pin preset
+ include_vars: "boards/{{repeater_board}}.yaml"
+
+- name: load RF region preset
+ include_vars: "rf/{{repeater_rf_preset}}.yaml"
+
+- name: enable SPI for the LoRa HAT
+ become: yes
+ lineinfile:
+ path: "/boot/firmware/config.txt"
+ line: "dtparam=spi=on"
+ regexp: "^#?dtparam=spi="
+ register: spi_result
+
+- name: remind to reboot for SPI to take effect
+ debug:
+ msg: "config.txt updated — reboot {{inventory_hostname}} for dtparam=spi=on to take effect"
+ when: spi_result.changed
+
+- name: apt install openhop repeater dependencies
+ become: yes
+ apt:
+ name:
+ - "git"
+ - "python3-venv"
+ - "python3-dev"
+ - "build-essential"
+
+- name: add openhop repeater user
+ become: yes
+ user:
+ name: "{{repeater_user}}"
+ system: "yes"
+ shell: "/bin/false"
+ home: "{{repeater_data_dir}}"
+ create_home: "no"
+ groups: "spi,gpio"
+ append: "yes"
+
+- name: create openhop repeater directories
+ become: yes
+ file:
+ path: "{{item.path}}"
+ state: "directory"
+ owner: "{{item.owner}}"
+ group: "{{item.owner}}"
+ mode: "{{item.mode}}"
+ loop:
+ - { path: "/opt/openhop_repeater", owner: "root", mode: "0755" }
+ - { path: "{{repeater_config_dir}}", owner: "root", mode: "0755" }
+ - { path: "{{repeater_data_dir}}", owner: "{{repeater_user}}", mode: "0750" }
+
+- name: check installed openhop_repeater version
+ become: yes
+ stat:
+ path: "{{repeater_venv_dir}}/.installed-{{repeater_version}}"
+ register: repeater_installed
+
+- name: install openhop_repeater into venv
+ become: yes
+ pip:
+ name: "git+{{repeater_repo}}@{{repeater_version}}"
+ virtualenv: "{{repeater_venv_dir}}"
+ virtualenv_command: "python3 -m venv"
+ when: not repeater_installed.stat.exists
+ notify: restart openhop-repeater
+
+- name: stamp installed openhop_repeater version
+ become: yes
+ copy:
+ dest: "{{repeater_venv_dir}}/.installed-{{repeater_version}}"
+ content: "{{repeater_version}}\n"
+ mode: "0644"
+ when: not repeater_installed.stat.exists
+
+- name: configure openhop repeater
+ become: yes
+ template:
+ src: "config.yaml.j2"
+ dest: "{{repeater_config_dir}}/config.yaml"
+ owner: "root"
+ group: "{{repeater_user}}"
+ mode: "0640"
+ notify: restart openhop-repeater
+
+- name: install openhop-repeater systemd service
+ become: yes
+ template:
+ src: "openhop-repeater.service.j2"
+ dest: "/lib/systemd/system/openhop-repeater.service"
+ mode: "0644"
+ notify: restart openhop-repeater
+
+- name: ensure openhop-repeater service is started
+ become: yes
+ systemd:
+ name: "openhop-repeater.service"
+ enabled: "yes"
+ state: "started"
+ daemon_reload: "yes"
diff --git a/openhop/repeater/templates/config.yaml.j2 b/openhop/repeater/templates/config.yaml.j2
new file mode 100644
index 0000000..ddd2772
--- /dev/null
+++ b/openhop/repeater/templates/config.yaml.j2
@@ -0,0 +1,53 @@
+# Managed by Ansible (openhop/repeater role) — do not edit by hand.
+
+repeater:
+ node_name: "{{ repeater_node_name }}"
+ latitude: {{ repeater_latitude }}
+ longitude: {{ repeater_longitude }}
+ identity_file: {{ ('"' + repeater_identity_file + '"') if repeater_identity_file else 'null' }}
+ owner_info: "{{ repeater_owner_info }}"
+ cache_ttl: 3600
+ max_flood_hops: 64
+ use_score_for_tx: false
+ score_threshold: 0.3
+ send_advert_interval_hours: 10
+ allow_discovery: true
+
+radio_type: sx1262
+
+sx1262:
+ bus_id: {{ repeater_sx1262_bus_id }}
+ cs_id: {{ repeater_sx1262_cs_id }}
+ cs_pin: {{ repeater_sx1262_cs_pin }}
+ reset_pin: {{ repeater_sx1262_reset_pin }}
+ busy_pin: {{ repeater_sx1262_busy_pin }}
+ irq_pin: {{ repeater_sx1262_irq_pin }}
+ txen_pin: {{ repeater_sx1262_txen_pin }}
+ rxen_pin: {{ repeater_sx1262_rxen_pin }}
+ en_pin: -1
+ txled_pin: -1
+ rxled_pin: -1
+ use_dio3_tcxo: {{ repeater_sx1262_use_dio3_tcxo | string | lower }}
+ dio3_tcxo_voltage: {{ repeater_sx1262_dio3_tcxo_voltage }}
+ use_dio2_rf: {{ repeater_sx1262_use_dio2_rf | string | lower }}
+ is_waveshare: {{ repeater_sx1262_is_waveshare | string | lower }}
+
+radio:
+ frequency: {{ repeater_frequency_hz }}
+ tx_power: {{ repeater_tx_power_dbm }}
+ bandwidth: {{ repeater_bandwidth_hz }}
+ spreading_factor: {{ repeater_spreading_factor }}
+ coding_rate: {{ repeater_coding_rate }}
+ preamble_length: {{ repeater_preamble_length }}
+ implicit_header: false
+
+# Observer/telemetry disabled — repeater-only deployment.
+mqtt_brokers:
+ iata_code: "Test"
+ status_interval: 300
+ brokers: []
+
+glass:
+ enabled: false
+ base_url: "http://localhost:8080"
+ inform_interval_seconds: 30
diff --git a/openhop/repeater/templates/openhop-repeater.service.j2 b/openhop/repeater/templates/openhop-repeater.service.j2
new file mode 100644
index 0000000..53aa737
--- /dev/null
+++ b/openhop/repeater/templates/openhop-repeater.service.j2
@@ -0,0 +1,18 @@
+[Unit]
+Description=openHop MeshCore Repeater
+After=network-online.target
+Wants=network-online.target
+
+[Service]
+Type=simple
+User={{repeater_user}}
+Group={{repeater_user}}
+WorkingDirectory={{repeater_data_dir}}
+ExecStart={{repeater_venv_dir}}/bin/python -m repeater.main --config {{repeater_config_dir}}/config.yaml
+Restart=always
+RestartSec=5
+StandardOutput=journal
+StandardError=journal
+
+[Install]
+WantedBy=multi-user.target
diff --git a/openhop/repeater/vars/boards/rak6421-slot1.yaml b/openhop/repeater/vars/boards/rak6421-slot1.yaml
new file mode 100644
index 0000000..464a194
--- /dev/null
+++ b/openhop/repeater/vars/boards/rak6421-slot1.yaml
@@ -0,0 +1,15 @@
+---
+# RAK6421 WisMesh Pi HAT — RAK13302 (SX1262 + 1W SKY66122 FEM) in SLOT 1.
+# Source: meshtastic/firmware bin/config.d/lora-RAK6421-13302-slot1.yaml
+repeater_sx1262_bus_id: 0 # spidev0.0
+repeater_sx1262_cs_id: 0 # CE0 (BCM 8)
+repeater_sx1262_cs_pin: 8
+repeater_sx1262_reset_pin: 16
+repeater_sx1262_busy_pin: 24
+repeater_sx1262_irq_pin: 22 # DIO1
+repeater_sx1262_use_dio3_tcxo: true
+repeater_sx1262_dio3_tcxo_voltage: 1.8
+repeater_sx1262_use_dio2_rf: true
+repeater_sx1262_txen_pin: 12 # SKY66122 FEM enable (verify TX vs RX)
+repeater_sx1262_rxen_pin: 13 # SKY66122 FEM enable (verify TX vs RX)
+repeater_sx1262_is_waveshare: false
diff --git a/openhop/repeater/vars/boards/rak6421-slot2.yaml b/openhop/repeater/vars/boards/rak6421-slot2.yaml
new file mode 100644
index 0000000..71a5fb9
--- /dev/null
+++ b/openhop/repeater/vars/boards/rak6421-slot2.yaml
@@ -0,0 +1,15 @@
+---
+# RAK6421 WisMesh Pi HAT — RAK13302 (SX1262 + 1W SKY66122 FEM) in SLOT 2.
+# Source: meshtastic/firmware bin/config.d/lora-RAK6421-13302-slot2.yaml
+repeater_sx1262_bus_id: 0 # spidev0.1
+repeater_sx1262_cs_id: 1 # CE1 (BCM 7)
+repeater_sx1262_cs_pin: 7
+repeater_sx1262_reset_pin: 24
+repeater_sx1262_busy_pin: 19
+repeater_sx1262_irq_pin: 18 # DIO1
+repeater_sx1262_use_dio3_tcxo: true
+repeater_sx1262_dio3_tcxo_voltage: 1.8
+repeater_sx1262_use_dio2_rf: true
+repeater_sx1262_txen_pin: 26 # SKY66122 FEM enable (verify TX vs RX)
+repeater_sx1262_rxen_pin: 23 # SKY66122 FEM enable (verify TX vs RX)
+repeater_sx1262_is_waveshare: false
diff --git a/openhop/repeater/vars/rf/eu.yaml b/openhop/repeater/vars/rf/eu.yaml
new file mode 100644
index 0000000..11fca12
--- /dev/null
+++ b/openhop/repeater/vars/rf/eu.yaml
@@ -0,0 +1,12 @@
+---
+# MeshCore EU 863-870 MHz preset.
+# WARNING: every node on your mesh MUST share identical frequency / bandwidth /
+# spreading_factor / coding_rate. Confirm these against your local
+# MeshCore EU community settings, and respect the sub-band duty-cycle /
+# power limits before deploying.
+repeater_frequency_hz: 869525000 # 869.525 MHz
+repeater_bandwidth_hz: 250000
+repeater_spreading_factor: 11 # 7-12
+repeater_coding_rate: 5 # 5-8
+repeater_preamble_length: 32
+repeater_tx_power_dbm: 14 # keep within the chosen EU sub-band limit
diff --git a/openhop/repeater/vars/rf/us.yaml b/openhop/repeater/vars/rf/us.yaml
new file mode 100644
index 0000000..e20b1c6
--- /dev/null
+++ b/openhop/repeater/vars/rf/us.yaml
@@ -0,0 +1,11 @@
+---
+# MeshCore US 902-928 MHz preset.
+# WARNING: every node on your mesh MUST share identical frequency / bandwidth /
+# spreading_factor / coding_rate. Confirm these against your local
+# MeshCore US community settings before deploying.
+repeater_frequency_hz: 910525000 # 910.525 MHz
+repeater_bandwidth_hz: 250000
+repeater_spreading_factor: 11 # 7-12
+repeater_coding_rate: 5 # 5-8
+repeater_preamble_length: 32
+repeater_tx_power_dbm: 22 # SX1262 register max; SKY66122 boosts to ~1W