modbus rtu

This commit is contained in:
flower_linux
2026-04-03 16:23:56 +08:00
parent 1da5fe12c3
commit 28b5082742
32 changed files with 3231 additions and 1 deletions

View File

@@ -0,0 +1,133 @@
#include "core/metadata/MetadataRegistry.h"
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonParseError>
namespace softbus::core::metadata
{
namespace
{
using softbus::core::models::DataDomain;
using softbus::core::models::DataType;
DataDomain parseDomain(const QString& s)
{
const QString x = s.trimmed().toUpper();
if (x == QStringLiteral("PROCESS_PARAM")) {
return DataDomain::PROCESS_PARAM;
}
if (x == QStringLiteral("CONTROL_CMD")) {
return DataDomain::CONTROL_CMD;
}
if (x == QStringLiteral("MONITOR_DATA")) {
return DataDomain::MONITOR_DATA;
}
if (x == QStringLiteral("HEALTH_DIAG")) {
return DataDomain::HEALTH_DIAG;
}
if (x == QStringLiteral("CONFIG_PARAM")) {
return DataDomain::CONFIG_PARAM;
}
return DataDomain::UNKNOWN;
}
DataType parseDataType(const QString& s)
{
const QString x = s.trimmed().toUpper();
if (x == QStringLiteral("BOOL")) {
return DataType::BOOL;
}
if (x == QStringLiteral("INT32")) {
return DataType::INT32;
}
if (x == QStringLiteral("UINT32")) {
return DataType::UINT32;
}
if (x == QStringLiteral("INT64")) {
return DataType::INT64;
}
if (x == QStringLiteral("FLOAT32")) {
return DataType::FLOAT32;
}
if (x == QStringLiteral("FLOAT64")) {
return DataType::FLOAT64;
}
if (x == QStringLiteral("STRING")) {
return DataType::STRING;
}
if (x == QStringLiteral("BYTE_ARRAY")) {
return DataType::BYTE_ARRAY;
}
return DataType::FLOAT64;
}
} // namespace
bool MetadataRegistry::loadFromFile(const QString& filePath)
{
m_lastError.clear();
m_defsById.clear();
QFile f(filePath);
if (!f.open(QIODevice::ReadOnly)) {
m_lastError = QStringLiteral("failed to open metadata file: %1").arg(filePath);
return false;
}
QJsonParseError pe;
const auto doc = QJsonDocument::fromJson(f.readAll(), &pe);
if (pe.error != QJsonParseError::NoError || !doc.isObject()) {
m_lastError = QStringLiteral("invalid metadata json: %1").arg(pe.errorString());
return false;
}
const auto root = doc.object();
const auto arr = root.value(QStringLiteral("metadatas")).toArray();
for (const auto& item : arr) {
if (!item.isObject()) {
continue;
}
const auto one = parseOne(item.toObject());
if (!one.has_value() || one->metadataId.isEmpty()) {
continue;
}
m_defsById.insert(one->metadataId, *one);
}
if (m_defsById.isEmpty()) {
m_lastError = QStringLiteral("metadata file contains no valid entries");
return false;
}
return true;
}
const softbus::core::models::MetadataDef* MetadataRegistry::findById(const QString& metadataId) const
{
const auto it = m_defsById.constFind(metadataId);
if (it == m_defsById.constEnd()) {
return nullptr;
}
return &it.value();
}
std::optional<softbus::core::models::MetadataDef> MetadataRegistry::parseOne(const QJsonObject& o)
{
softbus::core::models::MetadataDef def;
def.metadataId = o.value(QStringLiteral("metadataId")).toString();
def.name = o.value(QStringLiteral("name")).toString();
def.description = o.value(QStringLiteral("description")).toString();
def.domain = parseDomain(o.value(QStringLiteral("domain")).toString());
def.semanticType = parseDataType(o.value(QStringLiteral("semanticType")).toString());
def.unit = o.value(QStringLiteral("unit")).toString();
def.minValue = o.value(QStringLiteral("minValue")).toDouble(0.0);
def.maxValue = o.value(QStringLiteral("maxValue")).toDouble(0.0);
def.deadband = o.value(QStringLiteral("deadband")).toDouble(0.0);
def.isWriteable = o.value(QStringLiteral("isWriteable")).toBool(false);
return def;
}
} // namespace softbus::core::metadata

View File

@@ -0,0 +1,31 @@
#pragma once
#include <optional>
#include <QHash>
#include <QJsonObject>
#include <QString>
#include "core/models/MetadataDef.h"
namespace softbus::core::metadata
{
class MetadataRegistry
{
public:
bool loadFromFile(const QString& filePath);
const softbus::core::models::MetadataDef* findById(const QString& metadataId) const;
bool empty() const { return m_defsById.isEmpty(); }
const QString& lastError() const { return m_lastError; }
private:
static std::optional<softbus::core::models::MetadataDef> parseOne(const QJsonObject& o);
private:
QHash<QString, softbus::core::models::MetadataDef> m_defsById;
QString m_lastError;
};
} // namespace softbus::core::metadata

View File

@@ -0,0 +1,160 @@
#include "core/metadata/ProfileRegistry.h"
#include <QFile>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonParseError>
namespace softbus::core::metadata
{
namespace
{
QString normalizedProtocol(const QString& protocol)
{
return protocol.trimmed().toLower();
}
} // namespace
bool ProfileRegistry::loadFromFile(const QString& filePath)
{
m_lastError.clear();
m_profilesByKey.clear();
QFile f(filePath);
if (!f.open(QIODevice::ReadOnly)) {
m_lastError = QStringLiteral("failed to open profile file: %1").arg(filePath);
return false;
}
QJsonParseError pe;
const auto doc = QJsonDocument::fromJson(f.readAll(), &pe);
if (pe.error != QJsonParseError::NoError || !doc.isObject()) {
m_lastError = QStringLiteral("invalid profile json: %1").arg(pe.errorString());
return false;
}
const auto root = doc.object();
const auto arr = root.value(QStringLiteral("profiles")).toArray();
for (const auto& item : arr) {
if (!item.isObject()) {
continue;
}
const auto obj = item.toObject();
softbus::core::models::DeviceDataProfile p;
p.deviceId = obj.value(QStringLiteral("deviceId")).toString();
p.protocol = normalizedProtocol(obj.value(QStringLiteral("protocol")).toString());
const auto mappings = obj.value(QStringLiteral("mappings")).toArray();
for (const auto& m : mappings) {
if (!m.isObject()) {
continue;
}
const auto one = parseMapping(m.toObject());
if (one.has_value()) {
p.mappings.append(*one);
}
}
if (p.deviceId.isEmpty() || p.protocol.isEmpty() || p.mappings.isEmpty()) {
continue;
}
m_profilesByKey.insert(makeKey(p.deviceId, p.protocol), std::move(p));
}
if (m_profilesByKey.isEmpty()) {
m_lastError = QStringLiteral("profile file contains no valid entries");
return false;
}
return true;
}
const softbus::core::models::DeviceDataProfile* ProfileRegistry::find(const QString& deviceId,
const QString& protocol) const
{
const auto protocolNorm = normalizedProtocol(protocol);
const auto exactIt = m_profilesByKey.constFind(makeKey(deviceId, protocolNorm));
if (exactIt != m_profilesByKey.constEnd()) {
return &exactIt.value();
}
const auto wildcard = m_profilesByKey.constFind(makeKey(QStringLiteral("*"), protocolNorm));
if (wildcard != m_profilesByKey.constEnd()) {
return &wildcard.value();
}
return nullptr;
}
QString ProfileRegistry::makeKey(const QString& deviceId, const QString& protocol)
{
return deviceId + QStringLiteral("::") + protocol;
}
std::optional<softbus::core::models::DataPointMapping> ProfileRegistry::parseMapping(
const QJsonObject& o)
{
softbus::core::models::DataPointMapping m;
m.pointId = o.value(QStringLiteral("pointId")).toString();
m.metadataId = o.value(QStringLiteral("metadataId")).toString();
m.registerAddress = static_cast<std::uint32_t>(
o.value(QStringLiteral("registerAddress")).toInt(static_cast<int>(m.registerAddress)));
m.byteOffset = static_cast<std::uint16_t>(o.value(QStringLiteral("byteOffset")).toInt(0));
m.byteLength = static_cast<std::uint16_t>(o.value(QStringLiteral("byteLength")).toInt(0));
m.bitOffset = static_cast<std::int8_t>(o.value(QStringLiteral("bitOffset")).toInt(-1));
m.endianness = parseEndianness(o.value(QStringLiteral("endianness")).toString());
m.rawType = parseDataType(o.value(QStringLiteral("rawType")).toString());
m.scaleFactor = o.value(QStringLiteral("scaleFactor")).toDouble(1.0);
m.offsetFactor = o.value(QStringLiteral("offsetFactor")).toDouble(0.0);
m.expression = o.value(QStringLiteral("expression")).toString();
if (m.pointId.isEmpty() || m.metadataId.isEmpty() || m.byteLength == 0) {
return std::nullopt;
}
return m;
}
softbus::core::models::DataType ProfileRegistry::parseDataType(const QString& s)
{
const QString x = s.trimmed().toUpper();
using softbus::core::models::DataType;
if (x == QStringLiteral("BOOL")) {
return DataType::BOOL;
}
if (x == QStringLiteral("INT32")) {
return DataType::INT32;
}
if (x == QStringLiteral("UINT32")) {
return DataType::UINT32;
}
if (x == QStringLiteral("INT64")) {
return DataType::INT64;
}
if (x == QStringLiteral("FLOAT32")) {
return DataType::FLOAT32;
}
if (x == QStringLiteral("FLOAT64")) {
return DataType::FLOAT64;
}
if (x == QStringLiteral("STRING")) {
return DataType::STRING;
}
if (x == QStringLiteral("BYTE_ARRAY")) {
return DataType::BYTE_ARRAY;
}
return DataType::INT32;
}
softbus::core::models::Endianness ProfileRegistry::parseEndianness(const QString& s)
{
const QString x = s.trimmed().toUpper();
using softbus::core::models::Endianness;
if (x == QStringLiteral("LITTLE_ENDIAN")) {
return Endianness::LittleEndian;
}
if (x == QStringLiteral("BIG_ENDIAN_BYTE_SWAP")) {
return Endianness::BigEndianByteSwap;
}
return Endianness::BigEndian;
}
} // namespace softbus::core::metadata

View File

@@ -0,0 +1,35 @@
#pragma once
#include <optional>
#include <QHash>
#include <QJsonObject>
#include <QString>
#include "core/models/DataMapping.h"
namespace softbus::core::metadata
{
class ProfileRegistry
{
public:
bool loadFromFile(const QString& filePath);
const softbus::core::models::DeviceDataProfile* find(const QString& deviceId,
const QString& protocol) const;
const QString& lastError() const { return m_lastError; }
bool empty() const { return m_profilesByKey.isEmpty(); }
private:
static QString makeKey(const QString& deviceId, const QString& protocol);
static std::optional<softbus::core::models::DataPointMapping> parseMapping(const QJsonObject& o);
static softbus::core::models::DataType parseDataType(const QString& s);
static softbus::core::models::Endianness parseEndianness(const QString& s);
private:
QHash<QString, softbus::core::models::DeviceDataProfile> m_profilesByKey;
QString m_lastError;
};
} // namespace softbus::core::metadata

View File

@@ -0,0 +1,39 @@
#pragma once
// 定义DOM消息
#include <cstdint>
#include <variant>
#include <QString>
#include "core/models/MetadataDef.h"
#include "core/models/Types.h"
namespace softbus::core::models
{
using DOMValue = std::variant<bool, std::int32_t, std::uint32_t, std::int64_t, float, double>;
class DOMMessage
{
public:
DOMMessage() = default;
QString messageId;
const MetadataDef* metaRef{nullptr};
std::int64_t timestamp{0};
DOMValue value{0.0};
DataQuality quality{DataQuality::BAD};
DataDomain domain() const { return metaRef ? metaRef->domain : DataDomain::UNKNOWN; }
template <typename T>
T getValueAs() const
{
if (const auto* p = std::get_if<T>(&value)) {
return *p;
}
return T{};
}
};
} // namespace softbus::core::models

View File

@@ -0,0 +1,42 @@
#pragma once
#include <cstdint>
#include <QString>
#include <QVector>
#include "core/models/Types.h"
namespace softbus::core::models
{
enum class Endianness : std::uint8_t
{
BigEndian = 0,
LittleEndian,
BigEndianByteSwap
};
struct DataPointMapping
{
QString pointId;
QString metadataId;
std::uint32_t registerAddress{0xFFFFFFFFu}; // 可选:用于按地址匹配
std::uint16_t byteOffset{0};
std::uint16_t byteLength{0};
std::int8_t bitOffset{-1};
Endianness endianness{Endianness::BigEndian};
DataType rawType{DataType::INT32};
double scaleFactor{1.0};
double offsetFactor{0.0};
QString expression;
};
struct DeviceDataProfile
{
QString deviceId;
QString protocol;
QVector<DataPointMapping> mappings;
};
} // namespace softbus::core::models

View File

@@ -0,0 +1,24 @@
#pragma once
// 定义元数据定义
#include <QString>
#include "core/models/Types.h"
namespace softbus::core::models
{
struct MetadataDef
{
QString metadataId;
QString name;
QString description;
DataDomain domain{DataDomain::UNKNOWN};
DataType semanticType{DataType::FLOAT64};
QString unit;
double minValue{0.0};
double maxValue{0.0};
double deadband{0.0};
bool isWriteable{false};
};
} // namespace softbus::core::models

38
src/core/models/Types.h Normal file
View File

@@ -0,0 +1,38 @@
#pragma once
// 定义数据域类型、数据质量类型、数据类型
#include <cstdint>
namespace softbus::core::models
{
enum class DataDomain : std::uint8_t
{
PROCESS_PARAM = 0, // 过程参数
CONTROL_CMD = 1, // 控制指令
MONITOR_DATA = 2,
HEALTH_DIAG = 3,
CONFIG_PARAM = 4,
UNKNOWN = 255
};
enum class DataQuality : std::uint8_t
{
GOOD = 0,
BAD = 1,
UNCERTAIN = 2,
STALE = 3
};
enum class DataType : std::uint8_t
{
BOOL = 0,
INT32,
UINT32,
INT64,
FLOAT32,
FLOAT64,
STRING,
BYTE_ARRAY
};
} // namespace softbus::core::models

View File

@@ -0,0 +1,79 @@
#pragma once
#include <chrono>
#include <condition_variable>
#include <cstddef>
#include <deque>
#include <mutex>
#include <atomic>
namespace softbus::core::threading
{
/// 多生产者多消费者安全队列(互斥 + 条件变量)。
template <typename T>
class MutexQueue
{
public:
explicit MutexQueue(std::size_t maxDepth = 0)
: m_maxDepth(maxDepth)
{
}
bool tryPush(T value)
{
std::lock_guard<std::mutex> lk(m_mutex);
if (m_maxDepth > 0 && m_queue.size() >= m_maxDepth) {
return false;
}
m_queue.push_back(std::move(value));
m_cv.notify_all();
return true;
}
void push(T value)
{
std::lock_guard<std::mutex> lk(m_mutex);
m_queue.push_back(std::move(value));
m_cv.notify_all();
}
/// `running=false` 且队列空时返回 false。
bool popWait(T& out, const std::atomic<bool>& running)
{
std::unique_lock<std::mutex> lk(m_mutex);
m_cv.wait(lk, [&] { return !m_queue.empty() || !running.load(std::memory_order_acquire); });
if (m_queue.empty()) {
return false;
}
out = std::move(m_queue.front());
m_queue.pop_front();
return true;
}
template <class Rep, class Period>
bool popWaitFor(T& out, const std::atomic<bool>& running, std::chrono::duration<Rep, Period> timeout)
{
std::unique_lock<std::mutex> lk(m_mutex);
if (!m_cv.wait_for(lk, timeout, [&] {
return !m_queue.empty() || !running.load(std::memory_order_acquire);
})) {
return false;
}
if (m_queue.empty()) {
return false;
}
out = std::move(m_queue.front());
m_queue.pop_front();
return true;
}
private:
std::mutex m_mutex;
std::condition_variable m_cv;
std::deque<T> m_queue;
std::size_t m_maxDepth;
};
} // namespace softbus::core::threading