apart-controller/src/protocol/Concept1Protocol.h

133 lines
5 KiB
C++

#pragma once
#include <QByteArray>
#include <QString>
#include <optional>
// Low-level, stateless helpers for the Apart Concept 1 RS-232 ASCII protocol.
//
// Port settings (fixed by the device): 19200 baud, 8 data bits, no parity,
// 1 stop bit, no flow control. Every instruction ends with <CR> (0x0D).
namespace Concept1 {
constexpr int kBaudRate = 19200;
constexpr char kCr = '\r';
constexpr char kLf = '\n';
// Volume levels: -79..0 dB, with -80 meaning "OFF".
constexpr int kLevelOff = -80;
constexpr int kLevelMin = -79;
constexpr int kLevelMax = 0;
// Equalizer (bass/treble): -14..14 in steps of 2.
constexpr int kEqMin = -14;
constexpr int kEqMax = 14;
constexpr int kEqStep = 2;
// Input gain: -20..14 in steps of 2.
constexpr int kGainMin = -20;
constexpr int kGainMax = 14;
constexpr int kGainStep = 2;
enum class Command { Set, Get, Inc, Dec };
enum class Zone { None, Zone1, Zone2 };
enum class Source { A, B, C, D };
// All attributes understood by the Concept 1.
enum class Attribute {
MultiZone,
ZoneLink,
MusicLevel, // MSCLVL
MicLevel, // MICLVL
MaxMusicLevel, // MAXMSCLVL
MaxMicLevel, // MAXMICLVL
Select, // SELECT A/B/C/D
EqBass, // EQBASS
EqTreble, // EQTREB
Standby, // STANDBY
AutoLoudness, // AUTOLD
PagingActive, // PAGACT
InputGain, // IPGAIN (per source)
Echo, // ECHO
LineFeed, // LF
BackSpace, // BS
Header, // HEADER
ValueFeedback, // VALFB
Info, // INFO (GET only)
SourceName, // SOURCENAME (per source)
Serial, // SERIAL (GET only)
HwVersion, // HWVRSN (GET only)
SwVersion, // SWVRSN (GET only)
Restore, // RESTORE (SET only)
Unknown
};
// Classification of a line received from the device.
enum class ReplyKind {
Value, // an attribute value, e.g. "MSCLVL ZONE1 -16"
Info, // a single line of a GET INFO dump (also Value-shaped)
Paging, // "PAGING ZONEx ON/OFF" or "PAGING ON/OFF"
Acknowledge, // "Command Executed!" (also accepts the doc typo "Exicuted")
PleaseCycle, // "Please Cycle Power!"
PowerDown, // "POWER DOWN!"
Standby, // bare "STANDBY ON" sent while not ready
Error, // "ERROR: ...!"
NotImplemented, // "Not Yet Implemented!"
Echo, // an echoed instruction (when ECHO is on)
Unknown
};
struct ParsedReply {
ReplyKind kind = ReplyKind::Unknown;
Attribute attribute = Attribute::Unknown;
Zone zone = Zone::None;
std::optional<Source> source;
QString rawText; // full original line (trimmed)
QString textValue; // string value (source names, serial, versions, error text)
std::optional<int> intValue; // numeric value where applicable (dB / eq / gain)
bool boolValue = false; // for ON/OFF attributes
bool hasBool = false; // whether boolValue is meaningful
};
// --- String mapping helpers ---------------------------------------------
QString attributeToken(Attribute attr); // e.g. "MSCLVL"
Attribute attributeFromToken(const QString &token); // case-insensitive
QString commandToken(Command cmd);
QString zoneToken(Zone zone); // "ZONE1"/"ZONE2"/""
QString sourceToken(Source src); // "A".."D"
std::optional<Source> sourceFromToken(const QString &token);
std::optional<Zone> zoneFromToken(const QString &token);
// --- Value validation / normalisation -----------------------------------
int clampLevel(int value); // -80..0
int normalizeEq(int value); // snap to nearest valid -14..14/2
int normalizeGain(int value); // snap to nearest valid -20..14/2
int clampStep(int step); // 1..10
QString levelToDisplay(int level); // "OFF" or "-16 dB"
QString levelToken(int level); // "OFF" or "-16" (protocol wire form)
// --- Command building ----------------------------------------------------
// Builds a full instruction terminated with <CR>.
QByteArray buildCommand(Command cmd, Attribute attr,
Zone zone = Zone::None,
std::optional<Source> source = std::nullopt,
std::optional<QString> value = std::nullopt);
// Convenience builders.
QByteArray buildGet(Attribute attr, Zone zone = Zone::None,
std::optional<Source> source = std::nullopt);
QByteArray buildSetBool(Attribute attr, bool on, Zone zone = Zone::None);
QByteArray buildSetInt(Attribute attr, int value, Zone zone = Zone::None,
std::optional<Source> source = std::nullopt);
QByteArray buildSetString(Attribute attr, const QString &value,
std::optional<Source> source = std::nullopt);
QByteArray buildStep(Command incOrDec, Attribute attr, int step,
Zone zone = Zone::None);
// --- Reply parsing -------------------------------------------------------
// Parses a single line (without <CR>/<LF>) received from the device.
ParsedReply parseReply(const QString &line);
} // namespace Concept1