Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions docs/automation-bridge.md
Original file line number Diff line number Diff line change
Expand Up @@ -3299,6 +3299,15 @@ hardware tests. Raw RS-BA1 datagram logging is intentionally off by default and
should only be enabled briefly when these structured diagnostics are
insufficient.

**`civ power standby|wake|probe|setting`** sends the documented CI-V power command through
the active authenticated Icom session. `wake` applies the active model's
verified native-LAN framing; unsupported models fail closed. `standby` first
quiesces CI-V polling, and `probe` sends one identity read while quiesced so a
hardware test can distinguish an asleep radio from an artificially quiet
client. This is an explicit hardware-test action only: disconnect never powers
the radio down. `setting` reads the IC-9700's Power OFF Setting (`1A 05 01 46`)
without exposing the raw CI-V injector.

### `controls`

The CI-V control and meter registry, joined against what is actually wired.
Expand Down
27 changes: 22 additions & 5 deletions src/core/AutomationServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,13 @@
#include <QLocalServer>
#include <QScopeGuard>
#include <QLocalSocket>
#include <QApplication>

Check warning on line 28 in src/core/AutomationServer.cpp

View workflow job for this annotation

GitHub Actions / Static checks

EB2-known

src/core/AutomationServer.cpp uses QtWidgets (QApplication) — tracked legacy (baseline 20); the count may only shrink
#include <QScreen>
#include <QWidget>

Check warning on line 30 in src/core/AutomationServer.cpp

View workflow job for this annotation

GitHub Actions / Static checks

EB2-known

src/core/AutomationServer.cpp uses QtWidgets (QWidget) — tracked legacy (baseline 20); the count may only shrink
#include <QMainWindow>

Check warning on line 31 in src/core/AutomationServer.cpp

View workflow job for this annotation

GitHub Actions / Static checks

EB2-known

src/core/AutomationServer.cpp uses QtWidgets (QMainWindow) — tracked legacy (baseline 20); the count may only shrink
#include <QMenu>

Check warning on line 32 in src/core/AutomationServer.cpp

View workflow job for this annotation

GitHub Actions / Static checks

EB2-known

src/core/AutomationServer.cpp uses QtWidgets (QMenu) — tracked legacy (baseline 20); the count may only shrink
#include <QMenuBar>

Check warning on line 33 in src/core/AutomationServer.cpp

View workflow job for this annotation

GitHub Actions / Static checks

EB2-known

src/core/AutomationServer.cpp uses QtWidgets (QMenuBar) — tracked legacy (baseline 20); the count may only shrink
#include <QTabBar>

Check warning on line 34 in src/core/AutomationServer.cpp

View workflow job for this annotation

GitHub Actions / Static checks

EB2-known

src/core/AutomationServer.cpp uses QtWidgets (QTabBar) — tracked legacy (baseline 20); the count may only shrink
#include <QEnterEvent>
#include <QMouseEvent>
#include <QWheelEvent>
Expand Down Expand Up @@ -67,11 +67,11 @@
#include <utility>

// Best-effort value extraction for common control types.
#include <QAbstractButton>

Check warning on line 70 in src/core/AutomationServer.cpp

View workflow job for this annotation

GitHub Actions / Static checks

EB2-known

src/core/AutomationServer.cpp uses QtWidgets (QAbstractButton) — tracked legacy (baseline 20); the count may only shrink
#include <QAbstractSlider>

Check warning on line 71 in src/core/AutomationServer.cpp

View workflow job for this annotation

GitHub Actions / Static checks

EB2-known

src/core/AutomationServer.cpp uses QtWidgets (QAbstractSlider) — tracked legacy (baseline 20); the count may only shrink
#include <QAbstractItemView> // invoke selectRow: QTableWidget/QTreeWidget/QListWidget row select

Check warning on line 72 in src/core/AutomationServer.cpp

View workflow job for this annotation

GitHub Actions / Static checks

EB2-known

src/core/AutomationServer.cpp uses QtWidgets (QAbstractItemView) — tracked legacy (baseline 20); the count may only shrink
#include <QItemSelectionModel>
#include <QComboBox>

Check warning on line 74 in src/core/AutomationServer.cpp

View workflow job for this annotation

GitHub Actions / Static checks

EB2-known

src/core/AutomationServer.cpp uses QtWidgets (QComboBox) — tracked legacy (baseline 20); the count may only shrink
#include <QLineEdit>
#include <QLabel>
#include <QSpinBox>
Expand Down Expand Up @@ -3446,7 +3446,7 @@
});

add("civ", {},
"civ <send <hex>|trace [all]|session|scheduler|incident> — CI-V "
"civ <send <hex>|power <standby|wake|probe|setting>|trace [all]|session|scheduler|incident> — CI-V "
"inject, frame trace, lease/scheduler health, or last incident "
"(Icom; send is TX-gated)",
parseActionRest,
Expand Down Expand Up @@ -7963,12 +7963,13 @@
return err(QStringLiteral("no backend available"));

const QString a = action.trimmed().toLower();
if (a.isEmpty() || (a != QLatin1String("send") && a != QLatin1String("trace")
if (a.isEmpty() || (a != QLatin1String("send") && a != QLatin1String("power")
&& a != QLatin1String("trace")
&& a != QLatin1String("session")
&& a != QLatin1String("incident")
&& a != QLatin1String("scheduler"))) {
return err(QStringLiteral(
"civ requires an action (send|trace|session|scheduler|incident)"));
"civ requires an action (send|power|trace|session|scheduler|incident)"));
}
if (a == QLatin1String("send") && !m_txAllowed) {
return err(QStringLiteral(
Expand Down Expand Up @@ -8000,13 +8001,29 @@
failure = msg;
}, Qt::DirectConnection);

const QString verb = a == QLatin1String("send") ? QStringLiteral("civ.send")
QString verb;
QVariant extensionArg = arg.trimmed();
if (a == QLatin1String("power")) {
const QString state = arg.trimmed().toLower();
if (state != QLatin1String("standby") && state != QLatin1String("wake")
&& state != QLatin1String("probe") && state != QLatin1String("setting")) {
disconnect(okConn);
disconnect(errConn);
return err(QStringLiteral("civ power requires standby|wake|probe|setting"));
}
verb = state == QLatin1String("wake") ? QStringLiteral("power.wake")
: state == QLatin1String("probe") ? QStringLiteral("power.probe")
: state == QLatin1String("setting") ? QStringLiteral("power.setting")
: QStringLiteral("power.standby");
} else {
verb = a == QLatin1String("send") ? QStringLiteral("civ.send")
: a == QLatin1String("trace") ? QStringLiteral("civ.trace")
: a == QLatin1String("incident") ? QStringLiteral("civ.incident")
: a == QLatin1String("scheduler")
? QStringLiteral("civ.scheduler.status")
: QStringLiteral("civ.session");
backend->invokeExtension(QStringLiteral("icom"), verb, rid, arg.trimmed());
}
backend->invokeExtension(QStringLiteral("icom"), verb, rid, extensionArg);
disconnect(okConn);
disconnect(errConn);

Expand Down
5 changes: 5 additions & 0 deletions src/core/backends/IRadioBackend.h
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,11 @@ class IRadioBackend : public QObject {
void disconnected();
void connectionError(const QString& reason);

// A non-fatal connection phase the operator is expected to wait through.
// Unlike configurationWarning this names no fault, and unlike
// connectionError it must never start reconnect policy by itself.
void connectionProgress(const QString& message);

// A problem with the RADIO'S CONFIGURATION that the operator should fix,
// but which does not end the session. Distinct from connectionError, which
// every consumer treats as fatal: RadioModel starts its reconnect timer on
Expand Down
24 changes: 24 additions & 0 deletions src/core/backends/icom/CivCodec.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,20 @@ std::vector<std::uint8_t> buildFrameSub(std::uint8_t to, std::uint8_t cmd, std::
return buildFrame(to, cmd, body);
}

std::vector<std::uint8_t> cmdPowerOn(std::uint8_t to, std::size_t extraPreambleBytes,
std::uint8_t from)
{
std::vector<std::uint8_t> frame = buildFrameSub(to, cmd::kPower, 0x01, {});
frame[3] = from;
frame.insert(frame.begin(), extraPreambleBytes, kCivPreamble);
return frame;
}

std::vector<std::uint8_t> cmdPowerOff(std::uint8_t to)
{
return buildFrameSub(to, cmd::kPower, 0x00, {});
}

// Which commands carry a subcommand is a per-command fact, not a positional
// one. Treating every second byte as a subcommand would turn command 0x05's
// first frequency digit into a "subcommand"; treating none of them as one
Expand Down Expand Up @@ -85,6 +99,16 @@ bool commandHasSubcommand(std::uint8_t command)

std::optional<CivFrame> parseFrame(std::span<const std::uint8_t> frame)
{
// Wake synchronization fill is a run of FE bytes before the standard FE FE
// envelope. Parse from the final pair immediately before the first address.
std::size_t firstNonPreamble = 0;
while (firstNonPreamble < frame.size()
&& frame[firstNonPreamble] == kCivPreamble) {
++firstNonPreamble;
}
if (firstNonPreamble > 2) {
frame = frame.subspan(firstNonPreamble - 2);
}
// FE FE <to> <from> <cmd> ... <FD> — the shortest legal frame is 6 bytes
// (an FB/FA acknowledgement with no payload).
if (frame.size() < 6)
Expand Down
7 changes: 7 additions & 0 deletions src/core/backends/icom/CivCodec.h
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,13 @@ inline constexpr std::uint8_t kTuneOffset = 0x21;
inline constexpr std::uint8_t kVfoMode = 0x26;
} // namespace cmd

// Wake a transceiver from standby. `extraPreambleBytes` names model/baud-
// specific FE synchronization fill before the standard two-byte preamble.
[[nodiscard]] std::vector<std::uint8_t> cmdPowerOff(std::uint8_t to);
[[nodiscard]] std::vector<std::uint8_t> cmdPowerOn(std::uint8_t to,
std::size_t extraPreambleBytes = 0,
std::uint8_t from = kControllerAddress);

[[nodiscard]] std::vector<std::uint8_t> cmdSendCwMessage(
std::uint8_t to, std::string_view ascii);
[[nodiscard]] std::vector<std::uint8_t> cmdAbortCwMessage(std::uint8_t to);
Expand Down
Loading
Loading