Companion: say which profile raw and channel data arrived on

MeshCore Open TAK labels a MediaMesh photo or voice clip "Received via
MediaMesh", "via main mesh" or both. Wadamesh standalones tell the
app the profile in a frame byte stock firmware leaves reserved; a
Cascade companion left it unset, so the app showed nothing.

While radio2 is on (rx or rxtx):
- PUSH_CODE_RAW_DATA (0x84) byte 3: 0xA0 primary, 0xA1 radio2
  (stock 0xFF).
- RESP_CODE_CHANNEL_DATA_RECV (0x1B) byte 2: the same (stock 0).
With radio2 off, or for a packet from the UDP bridge, the stock values
stay, so existing clients see no change.

Test: test/test_companion_rx_via.py (in run-unit-tests); the two
harnesses that compile these handlers get a stub.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011i5tJJtj95MB4oUgncCUe5
This commit is contained in:
Claude committed 2026-10-24 17:31:53 -07:00
1 parent 06edf9aa8a
commit 0e756bf24a
7 files changed
+108 -4

No files matched your search

+1
View File
@@ -80,6 +80,7 @@ struct MyMesh {
void onContactResponse(const ContactInfo&, const uint8_t*, uint8_t);
void onControlDataRecv(mesh::Packet*);
void onRawDataRecv(mesh::Packet*);
uint8_t rxViaTag(const mesh::Packet*, uint8_t none) const { return none; }
void onTraceRecv(mesh::Packet*,uint32_t,uint32_t,uint8_t,const uint8_t*,const uint8_t*,uint8_t);
void clearBinaryTraceReply();
void checkGuards() const {
+82
View File
@@ -0,0 +1,82 @@
"""Companion receive-profile byte: raw and channel data frames say which
profile a packet arrived on (0xA0 primary, 0xA1 radio2) while radio2 is on,
and keep the stock reserved value otherwise or for UDP-bridge ingress."""
from pathlib import Path
import os
import subprocess
import sys
import tempfile
import unittest
from test_replay_reset_integration import extract_braced
ROOT = Path(__file__).resolve().parents[1]
HARNESS = r'''
#include <cassert>
#include <cstdint>
#include <initializer_list>
#include <RadioProfiles.h>
namespace mesh {
struct Packet { uint8_t radio_origin = 0; bool udp_bridge_ingress = false; };
struct Radio {
RadioProfiles* p = nullptr;
const RadioProfiles* profiles() const { return p; }
};
}
struct MyMesh {
mesh::Radio* _radio = nullptr;
uint8_t rxViaTag(const mesh::Packet* pkt, uint8_t none) const;
};
@METHOD@
int main() {
mesh::RadioProfiles profiles;
mesh::Radio radio;
MyMesh mesh_;
mesh::Packet primary, second, bridged;
second.radio_origin = 1;
bridged.radio_origin = 1; bridged.udp_bridge_ingress = true;
// No radio, no profiles, radio2 off: the stock reserved values.
assert(mesh_.rxViaTag(&primary, 0xFF) == 0xFF);
mesh_._radio = &radio;
assert(mesh_.rxViaTag(&second, 0) == 0);
radio.p = &profiles;
profiles.secondary.mode = mesh::RadioProfileMode::Off;
assert(mesh_.rxViaTag(&second, 0xFF) == 0xFF);
assert(mesh_.rxViaTag(nullptr, 0) == 0);
// radio2 listening or sending: tagged by the profile it arrived on.
for (auto mode : {mesh::RadioProfileMode::Rx, mesh::RadioProfileMode::RxTx}) {
profiles.secondary.mode = mode;
assert(mesh_.rxViaTag(&primary, 0xFF) == 0xA0);
assert(mesh_.rxViaTag(&second, 0) == 0xA1);
assert(mesh_.rxViaTag(&bridged, 0) == 0); // came over the UDP bridge, not a radio
}
return 0;
}
'''
class CompanionRxViaTest(unittest.TestCase):
def test_rx_via_tag(self):
source = (ROOT / 'examples/companion_radio/MyMesh.cpp').read_text(encoding='utf-8')
method = extract_braced(source, 'uint8_t MyMesh::rxViaTag(')
self.assertIn('rxViaTag(pkt, 0)', source) # channel data, reserved1
self.assertIn('rxViaTag(packet, 0xFF)', source) # raw data, reserved
with tempfile.TemporaryDirectory(prefix='companion-rx-via-') as directory:
work = Path(directory)
cpp = work / 'test.cpp'
cpp.write_text(HARNESS.replace('@METHOD@', method), encoding='utf-8')
flags = ['-fsanitize=address,undefined', '-fno-sanitize-recover=all'] if sys.platform.startswith('linux') else []
binary = work / 'test.exe'
compiled = subprocess.run([os.environ.get('CXX', 'g++'), '-std=c++17', '-Wall', '-Wextra', '-Werror',
'-DWITH_UDP_BRIDGE', *flags, f'-I{ROOT / "src"}', str(cpp), '-o', str(binary)],
capture_output=True, text=True, timeout=60)
self.assertEqual(compiled.returncode, 0, compiled.stderr)
checked = subprocess.run([str(binary)], capture_output=True, text=True, timeout=10)
self.assertEqual(checked.returncode, 0, checked.stderr)
if __name__ == '__main__':
unittest.main()
+1
View File
@@ -92,6 +92,7 @@ struct MyMesh {
if(pending_route==route)pending_route=nullptr;
}
int findChannelIdxForPacket(const mesh::GroupChannel&,mesh::Packet*) const { return 0; }
uint8_t rxViaTag(const mesh::Packet*,uint8_t none) const { return none; }
int getOfflineQueueCapacity() const;
Frame& offlineQueueFrameAt(int);
bool addToOfflineQueue(const uint8_t*,int);