mirror of
https://github.com/torlando-tech/pyxis.git
synced 2026-09-25 06:04:51 +00:00
[verified] fix: accept LXST profile and mode signals
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <climits>
|
||||
|
||||
// Bounded MessagePack parser for LXST {FIELD_SIGNALLING: [signal, ...]} packets.
|
||||
// Current LXST sends profile and duplex mode together, while older releases sent
|
||||
// one signal per packet. Accept canonical and valid wider integer containers.
|
||||
struct LXSTSignalParser {
|
||||
static constexpr size_t MAX_SIGNALS = 8;
|
||||
|
||||
static size_t parse(const uint8_t* data, size_t length, int* output,
|
||||
size_t output_capacity) {
|
||||
if (!data || !output || output_capacity == 0) return 0;
|
||||
|
||||
size_t position = 0;
|
||||
uint32_t map_count = 0;
|
||||
if (!readContainerCount(data, length, position, 0x80, 0x8F,
|
||||
0xDE, 0xDF, map_count) ||
|
||||
map_count != 1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t field = 0;
|
||||
if (!readNonNegativeInteger(data, length, position, field) || field != 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t signal_count = 0;
|
||||
if (!readContainerCount(data, length, position, 0x90, 0x9F,
|
||||
0xDC, 0xDD, signal_count) ||
|
||||
signal_count == 0 || signal_count > MAX_SIGNALS ||
|
||||
signal_count > output_capacity) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int parsed[MAX_SIGNALS] = {};
|
||||
for (uint32_t index = 0; index < signal_count; ++index) {
|
||||
uint64_t value = 0;
|
||||
if (!readNonNegativeInteger(data, length, position, value) ||
|
||||
value > static_cast<uint64_t>(INT_MAX)) {
|
||||
return 0;
|
||||
}
|
||||
parsed[index] = static_cast<int>(value);
|
||||
}
|
||||
|
||||
if (position != length) return 0;
|
||||
for (uint32_t index = 0; index < signal_count; ++index) {
|
||||
output[index] = parsed[index];
|
||||
}
|
||||
return signal_count;
|
||||
}
|
||||
|
||||
private:
|
||||
static bool take(const uint8_t* data, size_t length, size_t& position,
|
||||
uint8_t& value) {
|
||||
if (position >= length) return false;
|
||||
value = data[position++];
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool readBigEndian(const uint8_t* data, size_t length,
|
||||
size_t& position, size_t bytes,
|
||||
uint64_t& value) {
|
||||
if (bytes > length - position) return false;
|
||||
value = 0;
|
||||
for (size_t index = 0; index < bytes; ++index) {
|
||||
value = (value << 8) | data[position++];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool readContainerCount(const uint8_t* data, size_t length,
|
||||
size_t& position, uint8_t fixed_min,
|
||||
uint8_t fixed_max, uint8_t count16_marker,
|
||||
uint8_t count32_marker, uint32_t& count) {
|
||||
uint8_t marker = 0;
|
||||
if (!take(data, length, position, marker)) return false;
|
||||
if (marker >= fixed_min && marker <= fixed_max) {
|
||||
count = marker & 0x0F;
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t wide_count = 0;
|
||||
if (marker == count16_marker) {
|
||||
if (!readBigEndian(data, length, position, 2, wide_count)) return false;
|
||||
} else if (marker == count32_marker) {
|
||||
if (!readBigEndian(data, length, position, 4, wide_count)) return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if (wide_count > UINT32_MAX) return false;
|
||||
count = static_cast<uint32_t>(wide_count);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool readNonNegativeInteger(const uint8_t* data, size_t length,
|
||||
size_t& position, uint64_t& value) {
|
||||
uint8_t marker = 0;
|
||||
if (!take(data, length, position, marker)) return false;
|
||||
if (marker <= 0x7F) {
|
||||
value = marker;
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t bytes = 0;
|
||||
bool signed_integer = false;
|
||||
switch (marker) {
|
||||
case 0xCC: bytes = 1; break;
|
||||
case 0xCD: bytes = 2; break;
|
||||
case 0xCE: bytes = 4; break;
|
||||
case 0xCF: bytes = 8; break;
|
||||
case 0xD0: bytes = 1; signed_integer = true; break;
|
||||
case 0xD1: bytes = 2; signed_integer = true; break;
|
||||
case 0xD2: bytes = 4; signed_integer = true; break;
|
||||
case 0xD3: bytes = 8; signed_integer = true; break;
|
||||
default: return false;
|
||||
}
|
||||
if (!readBigEndian(data, length, position, bytes, value)) return false;
|
||||
if (signed_integer) {
|
||||
const uint64_t sign_bit = bytes == 8
|
||||
? (UINT64_MAX / 2 + 1)
|
||||
: (uint64_t{1} << (bytes * 8 - 1));
|
||||
if ((value & sign_bit) != 0) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "Tone.h"
|
||||
#include "../LVGL/LVGLLock.h"
|
||||
#include "lxst_audio.h"
|
||||
#include "LXSTSignalParser.h"
|
||||
#include "ULBWVoiceProfilePolicy.h"
|
||||
#include <microReticulum/Packet.h>
|
||||
#include <microReticulum/Transport.h>
|
||||
@@ -1522,60 +1523,60 @@ void UIManager::call_on_packet(const Bytes& data) {
|
||||
uint8_t field = buf[1];
|
||||
|
||||
if (field == 0x00) {
|
||||
// Signalling: {0x00: [signal]}
|
||||
// fixarray(1) = 0x91, then signal is a msgpack integer:
|
||||
// 0x00-0x7F = fixint (1 byte)
|
||||
// 0xCC XX = uint8 (2 bytes)
|
||||
// 0xCD XX XX = uint16 (3 bytes)
|
||||
if (buf[2] != 0x91) return;
|
||||
|
||||
int signal = -1;
|
||||
if (buf[3] <= 0x7F) {
|
||||
// fixint: value is the byte itself
|
||||
signal = buf[3];
|
||||
} else if (buf[3] == 0xCC && data.size() >= 5) {
|
||||
// uint8
|
||||
signal = buf[4];
|
||||
} else if (buf[3] == 0xCD && data.size() >= 6) {
|
||||
// uint16 (big-endian)
|
||||
signal = ((int)buf[4] << 8) | buf[5];
|
||||
}
|
||||
|
||||
if (signal < 0) {
|
||||
char dbg[64];
|
||||
snprintf(dbg, sizeof(dbg), "LXST: Unparseable signal (0x%02X), %d bytes", buf[3], (int)data.size());
|
||||
WARNING(dbg);
|
||||
// Signalling: {0x00: [signal, ...]}. LXST 0.5.1 combines the
|
||||
// preferred profile and duplex mode in one two-element array; older
|
||||
// peers send one signal per packet.
|
||||
int signals[LXSTSignalParser::MAX_SIGNALS] = {};
|
||||
const size_t signal_count = LXSTSignalParser::parse(
|
||||
buf, data.size(), signals, LXSTSignalParser::MAX_SIGNALS);
|
||||
if (signal_count == 0) {
|
||||
WARNING("LXST: Unparseable signalling packet");
|
||||
return;
|
||||
}
|
||||
|
||||
// Remote sends PREFERRED_PROFILE + profile_id to request a transmit
|
||||
// profile. Pyxis is ULBW-only for LoRa: never adopt the request, and
|
||||
// respond with ULBW so the peer switches its transmit pipeline to 700C.
|
||||
if (signal >= LXST_PREFERRED_PROFILE) {
|
||||
int remote_profile = signal - LXST_PREFERRED_PROFILE;
|
||||
char dbg[80];
|
||||
snprintf(dbg, sizeof(dbg),
|
||||
"LXST: Remote prefers profile 0x%02X, responding 0x%02X",
|
||||
remote_profile, _preferred_profile);
|
||||
INFO(dbg);
|
||||
call_send_signal(ULBWVoiceProfilePolicy::preferredProfileSignal());
|
||||
return;
|
||||
}
|
||||
for (size_t i = 0; i < signal_count; ++i) {
|
||||
const int signal = signals[i];
|
||||
|
||||
{
|
||||
char dbg[48];
|
||||
snprintf(dbg, sizeof(dbg), "LXST: Received signal 0x%02X (queued)", signal);
|
||||
INFO(dbg);
|
||||
}
|
||||
// Remote sends PREFERRED_PROFILE + profile_id to request a
|
||||
// transmit profile. Pyxis is ULBW-only for LoRa: never adopt the
|
||||
// request, and respond with ULBW so the peer switches to 700C.
|
||||
if (signal >= LXST_PREFERRED_PROFILE) {
|
||||
const int remote_profile = signal - LXST_PREFERRED_PROFILE;
|
||||
char dbg[80];
|
||||
snprintf(dbg, sizeof(dbg),
|
||||
"LXST: Remote prefers profile 0x%02X, responding 0x%02X",
|
||||
remote_profile, _preferred_profile);
|
||||
INFO(dbg);
|
||||
call_send_signal(ULBWVoiceProfilePolicy::preferredProfileSignal());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Enqueue for processing in call_update() under LVGL lock
|
||||
uint8_t w = _call_signal_write;
|
||||
uint8_t next_w = (w + 1) % SIGNAL_QUEUE_SIZE;
|
||||
if (next_w != _call_signal_read) { // Not full
|
||||
_call_signal_queue[w] = (uint8_t)signal;
|
||||
_call_signal_write = next_w;
|
||||
} else {
|
||||
WARNING("LXST: Signal queue full, dropping signal!");
|
||||
// Pyxis voice is always full duplex. Accept FDX and explicitly
|
||||
// counter an HDX request without treating mode as call status.
|
||||
if (signal >= LXST_PREFERRED_MODE) {
|
||||
const int remote_mode = signal - LXST_PREFERRED_MODE;
|
||||
if (remote_mode != LXST_MODE_FULL_DUPLEX) {
|
||||
INFO("LXST: Remote requested non-FDX mode, responding FDX");
|
||||
call_send_signal(LXST_PREFERRED_MODE + LXST_MODE_FULL_DUPLEX);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
char dbg[48];
|
||||
snprintf(dbg, sizeof(dbg), "LXST: Received signal 0x%02X (queued)", signal);
|
||||
INFO(dbg);
|
||||
}
|
||||
|
||||
// Enqueue for processing in call_update() under LVGL lock.
|
||||
uint8_t w = _call_signal_write;
|
||||
uint8_t next_w = (w + 1) % SIGNAL_QUEUE_SIZE;
|
||||
if (next_w != _call_signal_read) { // Not full
|
||||
_call_signal_queue[w] = static_cast<uint8_t>(signal);
|
||||
_call_signal_write = next_w;
|
||||
} else {
|
||||
WARNING("LXST: Signal queue full, dropping signal!");
|
||||
}
|
||||
}
|
||||
|
||||
} else if (field == 0x01) {
|
||||
|
||||
@@ -377,6 +377,8 @@ private:
|
||||
static constexpr uint8_t LXST_CODEC_CODEC2 = 0x02;
|
||||
|
||||
// LXST profile negotiation
|
||||
static constexpr int LXST_PREFERRED_MODE = 0xF0;
|
||||
static constexpr int LXST_MODE_FULL_DUPLEX = 0x01;
|
||||
static constexpr int LXST_PREFERRED_PROFILE = 0xFF;
|
||||
static constexpr int LXST_PROFILE_ULBW = 0x10; // Codec2 700C; sole production profile
|
||||
static constexpr int LXST_PROFILE_VLBW = 0x20; // protocol value; unsupported locally
|
||||
|
||||
@@ -24,6 +24,7 @@ System Python 3.9 has pytest pre-installed; Homebrew Python does not.
|
||||
- `native/test_call_start_mailbox.{cpp,py}` — exact 16-byte LXST outgoing-call handoff, duplicate rejection, and reusable producer/consumer stress
|
||||
- `native/test_call_generation_guard.{cpp,py}` — atomic, generation-scoped call admission, stale-owner protection, and repeated two-thread reservation races
|
||||
- `native/test_call_link_ownership.{cpp,py}` — exact 128-bit LXST link ownership publication, mixed-ID publication stress, and generation-bound deferred-close concurrency
|
||||
- `native/test_lxst_signal_parser.{cpp,py}` — bounded MessagePack parsing for legacy one-signal and current LXST profile+duplex signalling arrays
|
||||
|
||||
### Adding a new native C++ test
|
||||
|
||||
|
||||
+13
-13
@@ -228,9 +228,10 @@ def build_signal_packet(signal_value):
|
||||
"""
|
||||
Build a signalling packet matching all implementations.
|
||||
|
||||
Format: {0x00: [signal_value]}
|
||||
Format: {0x00: [signal_value, ...]}
|
||||
"""
|
||||
packet_data = {FIELD_SIGNALLING: [signal_value]}
|
||||
signals = signal_value if isinstance(signal_value, list) else [signal_value]
|
||||
packet_data = {FIELD_SIGNALLING: signals}
|
||||
return msgpack.packb(packet_data)
|
||||
|
||||
|
||||
@@ -256,17 +257,16 @@ def parse_pyxis_rx(wire_bytes):
|
||||
result = {"field": field, "frames": [], "signals": []}
|
||||
|
||||
if field == FIELD_SIGNALLING:
|
||||
# {0x00: [signal]}
|
||||
if buf[2] != 0x91:
|
||||
return {"error": f"expected fixarray(1), got 0x{buf[2]:02x}"}
|
||||
if buf[3] <= 0x7F:
|
||||
result["signals"].append(buf[3])
|
||||
elif buf[3] == 0xCC and len(buf) >= 5:
|
||||
result["signals"].append(buf[4])
|
||||
elif buf[3] == 0xCD and len(buf) >= 6:
|
||||
result["signals"].append((buf[4] << 8) | buf[5])
|
||||
else:
|
||||
return {"error": f"unparseable signal 0x{buf[3]:02x}"}
|
||||
# {0x00: [signal, ...]}; production C++ parsing is covered by the
|
||||
# native LXSTSignalParser golden-vector test.
|
||||
unpacked = msgpack.unpackb(wire_bytes)
|
||||
signals = unpacked.get(FIELD_SIGNALLING)
|
||||
if not isinstance(signals, list) or not 1 <= len(signals) <= 8:
|
||||
return {"error": "invalid signalling list"}
|
||||
if not all(isinstance(signal, int) and 0 <= signal <= 0x7FFFFFFF
|
||||
for signal in signals):
|
||||
return {"error": "invalid signal value"}
|
||||
result["signals"] = signals
|
||||
|
||||
elif field == FIELD_FRAMES:
|
||||
fmt = buf[2]
|
||||
|
||||
@@ -260,6 +260,16 @@ class TestSignallingFormat:
|
||||
assert pyxis_result["signals"] == [signal]
|
||||
assert lxst_result["signals"] == [signal]
|
||||
|
||||
def test_lxst_0_5_1_profile_and_mode_signal_list(self):
|
||||
"""Current LXST combines MQ profile and FDX mode in one packet."""
|
||||
signals = [PREFERRED_PROFILE + PROFILE_MQ, 0xF0 + 0x01]
|
||||
wire = build_signal_packet(signals)
|
||||
|
||||
# Golden bytes from authoritative LXST 0.5.1 umsgpack output.
|
||||
assert wire == bytes([0x81, 0x00, 0x92, 0xCD, 0x01, 0x3F, 0xCC, 0xF1])
|
||||
assert parse_pyxis_rx(wire)["signals"] == signals
|
||||
assert parse_lxst_python_rx(wire)["signals"] == signals
|
||||
|
||||
def test_pyxis_manual_signal_construction(self):
|
||||
"""
|
||||
Verify Pyxis call_send_signal() manual msgpack matches standard msgpack.
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
|
||||
#include "../../lib/lxst_audio/LXSTSignalParser.h"
|
||||
|
||||
namespace {
|
||||
int passed = 0;
|
||||
int failed = 0;
|
||||
void expect(bool condition, const char* name) {
|
||||
if (condition) ++passed;
|
||||
else { ++failed; std::cerr << "FAIL: " << name << '\n'; }
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
int signals[LXSTSignalParser::MAX_SIGNALS] = {};
|
||||
|
||||
const uint8_t legacy[] = {0x81, 0x00, 0x91, 0xCD, 0x01, 0x0F};
|
||||
size_t count = LXSTSignalParser::parse(legacy, sizeof(legacy), signals,
|
||||
LXSTSignalParser::MAX_SIGNALS);
|
||||
expect(count == 1, "legacy one-signal packet accepted");
|
||||
expect(signals[0] == 0x10F, "legacy ULBW profile decoded");
|
||||
|
||||
// Authoritative LXST 0.5.1 caller vector:
|
||||
// {FIELD_SIGNALLING: [PREFERRED_PROFILE+MQ, PREFERRED_MODE+FDX]}
|
||||
const uint8_t current[] = {
|
||||
0x81, 0x00, 0x92, 0xCD, 0x01, 0x3F, 0xCC, 0xF1
|
||||
};
|
||||
count = LXSTSignalParser::parse(current, sizeof(current), signals,
|
||||
LXSTSignalParser::MAX_SIGNALS);
|
||||
expect(count == 2, "current LXST profile+mode packet accepted");
|
||||
expect(signals[0] == 0x13F, "MQ profile decoded");
|
||||
expect(signals[1] == 0xF1, "full-duplex mode decoded");
|
||||
|
||||
const uint8_t array16[] = {
|
||||
0x81, 0x00, 0xDC, 0x00, 0x02, 0xCC, 0xF1, 0xCD, 0x01, 0x0F
|
||||
};
|
||||
count = LXSTSignalParser::parse(array16, sizeof(array16), signals,
|
||||
LXSTSignalParser::MAX_SIGNALS);
|
||||
expect(count == 2, "array16 signalling accepted");
|
||||
expect(signals[0] == 0xF1 && signals[1] == 0x10F,
|
||||
"array16 values decoded in order");
|
||||
|
||||
const uint8_t truncated[] = {0x81, 0x00, 0x92, 0xCD, 0x01, 0x3F, 0xCC};
|
||||
expect(LXSTSignalParser::parse(truncated, sizeof(truncated), signals,
|
||||
LXSTSignalParser::MAX_SIGNALS) == 0,
|
||||
"truncated list rejected atomically");
|
||||
|
||||
const uint8_t noncanonical_uint32[] = {0x81, 0x00, 0x91, 0xCE, 0, 0, 1, 0};
|
||||
count = LXSTSignalParser::parse(noncanonical_uint32,
|
||||
sizeof(noncanonical_uint32), signals,
|
||||
LXSTSignalParser::MAX_SIGNALS);
|
||||
expect(count == 1, "noncanonical uint32 signal accepted");
|
||||
expect(signals[0] == 0x100, "noncanonical uint32 value decoded");
|
||||
|
||||
const uint8_t noncanonical_int16[] = {0x81, 0x00, 0x91, 0xD1, 0x01, 0x0F};
|
||||
count = LXSTSignalParser::parse(noncanonical_int16,
|
||||
sizeof(noncanonical_int16), signals,
|
||||
LXSTSignalParser::MAX_SIGNALS);
|
||||
expect(count == 1, "noncanonical positive int16 signal accepted");
|
||||
expect(signals[0] == 0x10F, "noncanonical positive int16 decoded");
|
||||
|
||||
expect(LXSTSignalParser::parse(current, sizeof(current), signals, 1) == 0,
|
||||
"caller capacity is enforced");
|
||||
|
||||
const uint8_t too_many[] = {
|
||||
0x81, 0x00, 0x99, 0, 1, 2, 3, 4, 5, 6, 7, 8
|
||||
};
|
||||
expect(LXSTSignalParser::parse(too_many, sizeof(too_many), signals,
|
||||
LXSTSignalParser::MAX_SIGNALS) == 0,
|
||||
"peer-controlled signal count is bounded");
|
||||
|
||||
const uint8_t trailing[] = {0x81, 0x00, 0x91, 0x04, 0x00};
|
||||
expect(LXSTSignalParser::parse(trailing, sizeof(trailing), signals,
|
||||
LXSTSignalParser::MAX_SIGNALS) == 0,
|
||||
"trailing bytes rejected");
|
||||
|
||||
std::cout << passed << " passed, " << failed << " failed\n";
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
"""Compile and execute LXST signalling-list parser regressions."""
|
||||
|
||||
import shutil
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
TEST_SOURCE = HERE / "test_lxst_signal_parser.cpp"
|
||||
|
||||
|
||||
def test_lxst_signal_parser(tmp_path):
|
||||
cxx = shutil.which("c++")
|
||||
if not cxx:
|
||||
pytest.skip("no C++ compiler found")
|
||||
binary = tmp_path / "test_lxst_signal_parser"
|
||||
compiled = subprocess.run(
|
||||
[cxx, "-std=c++17", "-Wall", "-Wextra", "-Werror", str(TEST_SOURCE), "-o", str(binary)],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert compiled.returncode == 0, compiled.stderr
|
||||
ran = subprocess.run([str(binary)], capture_output=True, text=True, timeout=30)
|
||||
assert ran.returncode == 0, ran.stdout + ran.stderr
|
||||
assert "15 passed, 0 failed" in ran.stdout
|
||||
@@ -30,7 +30,10 @@ from LXST.Primitives.Telephony import Profiles
|
||||
from sideband.voice import ReticulumTelephone
|
||||
|
||||
PORT = os.environ.get("PYXIS_SERIAL_PORT") or sorted(glob.glob("/dev/cu.usbmodem*"))[0]
|
||||
PROFILE = Profiles.BANDWIDTH_ULTRA_LOW
|
||||
# Deliberately originate at Sideband's MQ/Opus profile. Pyxis is ULBW-only and
|
||||
# must parse the combined LXST 0.5.1 [profile, duplex-mode] signal and force the
|
||||
# peer's transmit pipeline to Codec2-700C.
|
||||
PROFILE = Profiles.QUALITY_MEDIUM
|
||||
RUN_SECONDS = float(os.environ.get("PYXIS_CALL_SECONDS", "7"))
|
||||
IDENTIFY_TIMEOUT_SECONDS = 15.0
|
||||
IDENTIFY_TIMEOUT_MARGIN = float(os.environ.get("PYXIS_IDENTIFY_TIMEOUT_MARGIN", "3"))
|
||||
@@ -414,6 +417,18 @@ def run_audio(dev, label):
|
||||
val(qos,"decode_ok")>0 and val(qos,"decode_fail")==0 and val(qos,"pcm_n")>0)
|
||||
return ok,{"sideband":d,"qos":qos,"stats":stat,"rms":rms}
|
||||
|
||||
def assert_sideband_ulbw(phone, label, timeout=8):
|
||||
deadline=time.monotonic()+timeout
|
||||
seen=[]
|
||||
while time.monotonic()<deadline:
|
||||
profile=phone.telephone.active_profile
|
||||
seen.append(profile)
|
||||
if profile==Profiles.BANDWIDTH_ULTRA_LOW:
|
||||
print(f"{label} SIDEBAND_PROFILE=ULBW",flush=True)
|
||||
return
|
||||
time.sleep(.2)
|
||||
raise AssertionError(f"{label}: Sideband remained non-ULBW; profiles={seen}")
|
||||
|
||||
def main():
|
||||
print(f"PORT={PORT}",flush=True)
|
||||
print(f"RNS_VERSION={RNS.__version__}",flush=True)
|
||||
@@ -565,6 +580,7 @@ def main():
|
||||
deadline=time.monotonic()+15
|
||||
while not phone.is_in_call and time.monotonic()<deadline: time.sleep(.2)
|
||||
assert phone.is_in_call,"Sideband did not become active after identification timeout"
|
||||
assert_sideband_ulbw(phone,"TEST_IDENTIFY_TIMEOUT")
|
||||
ok,data=run_audio(dev,"TEST_IDENTIFY_TIMEOUT")
|
||||
phone.hangup()
|
||||
idle_ok,hangup_states=dev.wait_state("IDLE",15)
|
||||
@@ -590,6 +606,7 @@ def main():
|
||||
deadline=time.monotonic()+15
|
||||
while not phone.is_in_call and time.monotonic()<deadline: time.sleep(.2)
|
||||
assert phone.is_in_call,"Sideband did not become active on incoming call"
|
||||
assert_sideband_ulbw(phone,"TEST1")
|
||||
ok,data=run_audio(dev,"TEST1")
|
||||
results.append(("pyxis_to_sideband_bidirectional",ok,data))
|
||||
dev.cmd("T:CALL_HANGUP"); dev.wait_state("IDLE",15); time.sleep(1)
|
||||
@@ -605,6 +622,7 @@ def main():
|
||||
deadline=time.monotonic()+15
|
||||
while not phone.is_in_call and time.monotonic()<deadline: time.sleep(.2)
|
||||
assert phone.is_in_call,"Sideband did not become active"
|
||||
assert_sideband_ulbw(phone,"TEST2")
|
||||
ok,data=run_audio(dev,"TEST2")
|
||||
results.append(("sideband_to_pyxis_bidirectional",ok,data))
|
||||
phone.hangup(); dev.wait_state("IDLE",15); time.sleep(1)
|
||||
|
||||
Reference in New Issue
Block a user