# Two LoRa profiles on one radio
`radio2` lets one LoRa transceiver alternate between two frequencies or modulation
settings. It is useful for listening to two networks, or keeping your usual
channel available during a LoRa firmware update.
There are **at most two active profiles**. `tempradio` temporarily replaces
`radio`; `tempradio2` temporarily replaces `radio2`. These are time-shared receive
windows, not simultaneous receivers. Receiving or transmitting a packet on one
profile makes the other unavailable until that packet finishes.
The second-profile commands are shared by repeater, Companion, room server,
sensor and standalone terminal-chat firmware with a LoRa radio. ESP-NOW-only
devices reject LoRa profile commands. KISS is a raw host-controlled modem: it
exposes equivalent RAM-only binary `SetRadio2`/`SetTempRadio2` commands and
logical KISS Data ports, documented in [the KISS modem protocol](kiss_modem_protocol.md).
It does not use the Mesh CLI's persistent profile file or Mesh retry queues.
## Quick setup
Keep the primary channel configured with `radio`, then add the second:
```text
set radio2 910.5,500,8,5,rx
get radio2
get radio2.status
```
The tuple is **frequency in MHz, bandwidth in kHz, spreading factor, coding-rate
denominator, mode, optional preamble in symbols**. `5` means coding rate 4/5.
| Mode | Receive on profile 2 | Transmit on profile 2 |
| --- | --- | --- |
| `rx` | Yes | Only infrastructure replies explicitly configured with `tx.reply ... force` |
| `rxtx` (also `rx&tx`) | Yes | Yes |
| `off` | No | No |
For transmission on both permanent profiles:
```text
set radio2 910.5,500,8,5,rxtx
```
The same encoded message is queued separately for each allowed transmit profile.
Each has its own direct/flood retry slots and channel-busy backoff. A forwarding
echo on one profile cannot cancel the other profile's retry. Packet storage and
the physical transceiver's airtime budget are shared; exhausted queues can still
drop packets.
## Keep the main channel during an update
```text
set tempradio2 910.5,500,8,5,rxtx,120
get tempradio2
```
This enables the second profile for 120 minutes (2 hours). Locally generated
LoRa OTA requests use the temporary second profile. Infrastructure OTA responses
follow `tx.reply` below (both TX-capable profiles by default). Ordinary local traffic stays
on the primary profile by default. Use an OTA-capable build and the usual OTA
setup on every participating node; a temporary profile does not add OTA support
to firmware built without it.
Local settings take effect after a short reply allowance. On repeaters, immediate
remote `radio2`/`tempradio2` changes (including `off`) wait until their exact reply
copies finish, with at least one successfully transmitted. A reply that cannot
be queued or whose copies all fail cancels the change. Queued replies have a
five-minute deadline; an already-transmitting copy may finish under the normal
radio watchdog. This confirms transmission, not reception by the remote client.
Permanent remote changes are prepared in an uncommitted file before replying;
the saved configuration is replaced only after reply transmission. If that
replacement fails, the old channel remains active and the commit retries.
Local `get radio2.status` reports this pending state. Roles without the tracked remote
reply path require local USB for these immediate secondary-profile changes.
Temporary periods and schedules live in RAM and disappear on reboot. Waiting
for a reply does not extend a temporary lease. Timer expiry also uses monotonic
time, so setting the clock backwards cannot extend a temporary session.
## Repeater, room-server, and sensor replies
These roles default to **`tx.reply both`**: locally generated replies are queued
on `radio` and on an active `radio2` that permits TX. This includes replies to
remote CLI/login/telemetry requests, ACKs, returned paths, delayed GPIO command
completion, room subscription posts, and locally served LoRa OTA catalog,
manifest, data, proof, and leaves responses. Each copy has its own retry state.
Forwarded packets, periodic adverts, sensor pushes, and OTA requests continue
to use the normal crossing rules.
```text
get tx.reply
set tx.reply both
set tx.reply both force
set tx.reply radio
set tx.reply auto
```
| Choice | Replies transmit on |
| --- | --- |
| `both` | Primary plus an active `rxtx` second profile. Default. |
| `radio` | Primary only. |
| `radio2` | Second only; no primary fallback. |
| `auto` | Normal `radio2.cross` behavior, including permanent/temporary isolation. |
| `off` | Neither profile. This also suppresses remote CLI replies and ACKs. |
Append **`force`** to `both` or `radio2` to allow these replies on an active
second profile configured as `rx`. Without `force`, `rx` remains receive-only.
Sending the command again without `force` removes the override. It never turns
on `radio2 off`, changes the saved `rx` mode, or enables ordinary forwarding/TX
there. Existing crossover packet filters still apply to the extra copy.
The explicit choices override `radio2.cross`, including `off` and mixed
permanent/temporary profiles. `radio` means the active primary profile,
including `tempradio`; `radio2` also includes an active `tempradio2`.
For example, an OTA source can retain its normal channel while allowing OTA
and management responses on a temporary receive profile:
```text
set tempradio2 910.5,500,8,5,rx,120
set tx.reply both force
```
The source must have OTA support and an active temporary-radio session as
usual. A node fetching an update still needs `rxtx` for its OTA requests.
Forcing replies does not change OTA's receive gates, scan timing, or preambles.
This setting survives reboot; older saved profiles adopt `both` without force.
The getter reports when the chosen second profile cannot transmit. Already
queued replies retain their choice; disabling/changing a profile invalidates
stale queued packets. A parameterized repeater `tempradio` command waits for
both admitted reply copies to drain before changing the primary settings;
at least one must transmit successfully. Those tracked replies omit alternate
paths and transport retries so no late success copy can outlive the handoff.
Queue capacity and the shared transceiver's airtime budget still limit delivery.
OTA data/proof admission counts both copies against its queue credit and
reserves receive capacity before allowing the response.
Companions use the per-contact/channel choices below; `tx.reply` is an
infrastructure setting.
## Companion messages on both profiles
To send ordinary Companion messages on both `radio` and an active `tempradio2`,
configure the temporary profile in `rxtx` mode and enable crossing:
```text
set radio2.cross on
get tempradio2
get radio2.cross
get radio2.status
```
`rxtx` permits transmission on the second profile; `radio2.cross on` copies new
messages between the two profiles. `TX=a,b` reports primary and secondary
transmit counts. Crossing also applies to OTA traffic.
The crossing setting is saved and remains enabled after the temporary session
ends or the node reboots. Run `set radio2.cross auto` to restore the default
isolation between permanent and temporary profiles. The firmware picker's
Companion tempradio2 instructions include both choices and the status commands.
## Companion TX routing by contact or channel
Companions can save a transmit choice for each contact and each configured
channel. The commands apply to messages sent from the phone app, USB/BLE/TCP
Companion clients, and the text terminal:
```text
set tx.user "Alice Jones" radio2
get tx.user "Alice Jones"
set tx.channel 0 both
get tx.channel 0
set tx.channel #wardriving radio
get tx.channel #wardriving
```
Channel indices match the app and the terminal's `channels` list; Public is
normally slot `0`. Names must match exactly, including capitalization and any
leading `#`. Quotes always select a literal name, so `"1"` is a channel named
`1`, while unquoted `1` selects slot 1. Quote contact names beginning with
`key:` to distinguish them from public-key selectors. Duplicate names are
rejected: use the channel index or a contact's public key instead.
| Choice | Locally addressed traffic transmits on |
| --- | --- |
| `auto` | Existing behavior, including `radio2.cross` and temporary-profile isolation. This is the default. |
| `radio` | The primary profile only. |
| `radio2` | The second profile only; sending fails if it is off or RX-only. |
| `both` | The primary profile and the second profile whenever the second is enabled for TX. |
| `off` | Neither profile for this contact/channel. Reception stays enabled. |
Explicit choices override `radio2.cross`, including `off` and the isolation
between a permanent and temporary profile. They do not turn on radio2 or
change its `rx`/`rxtx` mode. For example, leave `radio2.cross auto` and use
`set tx.channel 0 both` to send Public messages on both `radio` and an active
`tempradio2`, while other channels keep their default routing.
`radio` means the currently active primary profile, whether permanent or
`tempradio`; `radio2` similarly includes `tempradio2`. There are still at most
two active profiles. Getters show the saved choice and `active=` for newly
initiated messages with the current profile configuration. A temporarily
unavailable second profile is reported explicitly.
Contact selection uses the full stored public key after resolving a unique
name or key prefix. It never routes by the short on-air destination hash.
For key selection, prefix 12–64 hexadecimal digits (whole bytes) with `key:`:
```text
set tx.user key:0123456789AB radio2
get tx.user key:0123456789AB
```
Replace the example prefix with your contact's key; use more digits if it is
ambiguous. `get tx.user` and `get tx.channel` report override counts and query
syntax. To return a target to the global behavior:
```text
set tx.user "Alice Jones" auto
set tx.channel 0 auto
```
A user's setting covers addressed texts, login/CLI/telemetry requests, replies,
ACKs, and returned paths to that contact. Thus `off` also prevents ACKs to
that contact. Channel settings cover group text and group data, including
an explicitly routed group datagram. Contact choices do not filter other
people's messages inside a group channel. Adverts, raw traces, raw packets,
and OTA packets without a contact/channel destination retain global routing.
Packets and their retries already queued retain the choice used when created;
changing or disabling the radio profile still invalidates stale queued work.
Message timeout estimates use the selected profiles' airtime, independently
of the scanner's current receive profile.
Choices survive reboot and normal phone-app updates to the same contact or
channel key. Replacing a channel's key or deleting/replacing a contact clears
its choice. They use reserved bytes in existing saved records, so record
sizes and the phone protocol are unchanged. Older firmware ignores these
bytes and may reset the choices when it saves the records.
## Companion channel-name routing aliases
Companions recognize the case-sensitive prefixes `r2-` and `mt-`, immediately
after the optional leading `#`. Keep the prefix in the app's displayed name.
For MeshCore aliases, the firmware strips exactly one prefix when deriving an
automatic hashtag key:
```text
#emergency -> hashtag key for #emergency
#r2-emergency -> the same key for #emergency
#mt-emergency -> the same key for #emergency only in MeshCore fallback mode
```
Only the app's automatic, name-derived hashtag key is normalized; a supplied
private key is not replaced. Non-hashtag names such as `r2-Private` also retain
their supplied key. Empty or nested prefixes (`r2-`, `#r2-mt-emergency`) are
rejected. MeshCore hashtag names remain case-sensitive.
When radio2 is configured (`rx` or `rxtx`), `r2-` binds the channel to radio2 for
both send and receive, including emergency retransmissions. It overrides global
crossing and cannot be changed with `set tx.channel`; rename the channel to
remove the binding. RX-only radio2 cannot send. If radio2 is off/unconfigured,
ignore the routing prefix and use ordinary MeshCore routing instead. The key
stays the same as radio2 is enabled, disabled, or a temporary window expires.
If both the ordinary and `r2-` slots have the same key, a packet received on
radio2 selects the `r2-` slot; a packet received on radio1 selects the ordinary
slot. Global packet deduplication still delivers each packet once, not once in
each slot. Receiving a radio2-only channel on the wrong profile does not mark
that authenticated packet seen, so it can still arrive later on radio2.
The saved channel record retains the original displayed name and app-supplied
key for editing and sharing. Runtime copies resolve the actual wire key.
Repeater/room/sensor hashtag behavior is unchanged; those nodes can join the
canonical MeshCore channel (`#emergency`). Older Companion firmware does not
recognize these aliases.
### Meshtext channel chat
`MESH_ENABLE_MESHTEXT_CHAT` enables the text-only Meshtext backend in the
Companion. It defaults on for ESP32-S3 **Full Companion** builds using
SX1262 LoRa and for the RAK4631 **Full Companion** starting with 1.17.1.10
preview3. An ESP-NOW primary and other builds default off. RAK4631 1.17.1.10
preview2 and earlier builds did not include this backend. This is not
a replacement for stock peer firmware or a cross-protocol bridge.
In an enabled build, an `mt-` name always requests Meshtext, even if radio2 is
off or setup is blocked. It never falls back to sending MeshCore frames. In a
feature-disabled build, `mt-` remains an ordinary MeshCore naming alias while
radio2 is off; with radio2 configured, MT channel changes/sends are unsupported.
The `r2-` MeshCore behavior above is unchanged in either build.
With the region configured, join `#mt-longfast`, for example, through the app's
normal hashtag-channel editor. A recognized preset name selects the stock
peer channel name (`LongFast`) and, **only for an automatically
name-derived hashtag key**, the expanded public `AQ==` key. This public key is
not a secret. An explicitly
supplied private AES-128 or AES-256 key stays private and unchanged. A custom
name such as `mt-Private` uses the exact tail (`Private`) as its wire name and
the supplied key; custom wire names are limited to 11 bytes and need a manual
radio2 profile or another MT channel with a recognized preset. Preset names
are ASCII case-insensitive; the `mt-` marker is still case-sensitive.
Recognized aliases and modulation are:
| Alias tail | Stock wire name | BW (kHz) | SF | CR |
| --- | --- | ---: | ---: | ---: |
| `shortturbo` | `ShortTurbo` | 500 | 7 | 5 |
| `shortfast` | `ShortFast` | 250 | 7 | 5 |
| `shortslow` | `ShortSlow` | 250 | 8 | 5 |
| `mediumturbo` | `MediumTurbo` | 500 | 9 | 5 |
| `mediumfast` | `MediumFast` | 250 | 9 | 5 |
| `mediumslow` | `MediumSlow` | 250 | 10 | 5 |
| `longturbo` | `LongTurbo` | 500 | 11 | 8 |
| `longfast` | `LongFast` | 250 | 11 | 5 |
| `longmod` or `longmoderate` | `LongMod` | 125 | 11 | 8 |
| `longslow` | `LongSlow` | 125 | 12 | 8 |
The selected region must have room for the bandwidth; for example, 500 kHz
presets are unavailable in the supported EU868 plan. Use these CLI commands:
```text
get mt.status
get mt.region
get mt.slot
get mt.hops
get mt.node
get mt.shortname
set mt.node !12345678
set mt.node default
set mt.region auto
set mt.region us
set mt.slot 0
set mt.hops 7
set mt.shortname HALO
set mt.shortname default
send mt.nodeinfo 0
get radio2
set radio2 auto
```
`mt.region` accepts `auto`, `us`, `eu868`, `eu433`, or `anz`. `auto` infers US
only from a primary frequency at least 902 MHz and below 915 MHz, EU868 from
869.4–869.65 MHz, and EU433 from 433–434 MHz. The US/ANZ overlap at 915–928 MHz
is ambiguous: explicitly select `us` or `anz`. `mt.slot` is a 1-based frequency
slot, or `0` for the stock preset-name-derived slot; unavailable slots are
blocked. Check `get mt.status` after configuration; a successful settings save
does not mean the MT backend is active.
Outgoing MT messages default to a hop limit of **7**. `set mt.hops <0..7>`
changes this budget, and `get mt.hops` or `get mt.status` shows it. `0` sends
only the initial over-air hop with no forwarding budget; larger values permit
protocol-compatible peers to forward within that budget. The Companion itself still
does not relay MT packets.
`get mt.node` shows the effective Meshtext sender node ID. By default it is
SHA-derived from the MeshCore public identity and remains stable for that
identity. `set mt.node !12345678` sets a saved override; exactly eight hexadecimal
digits are required, with or without the leading `!`. Input is hexadecimal,
not decimal, and is never silently truncated. IDs `00000000` through `00000003`
and `ffffffff` are reserved and rejected. `set mt.node default` clears the
override and restores the identity-derived ID.
The example ID is illustrative: choose an ID unique among your protocol-compatible
peers. Collisions can merge different senders into one identity and interfere
with packet deduplication. Changing the MT ID does not alter the MeshCore
private key, reset the outgoing packet-ID sequence or send automatic traffic.
Settings changes are refused while an MT transmission is
pending. Neither node IDs nor text labels are authenticated.
`set mt.shortname