modbus rtu
This commit is contained in:
133
src/core/metadata/MetadataRegistry.cpp
Normal file
133
src/core/metadata/MetadataRegistry.cpp
Normal 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
|
||||
31
src/core/metadata/MetadataRegistry.h
Normal file
31
src/core/metadata/MetadataRegistry.h
Normal 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
|
||||
160
src/core/metadata/ProfileRegistry.cpp
Normal file
160
src/core/metadata/ProfileRegistry.cpp
Normal 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
|
||||
35
src/core/metadata/ProfileRegistry.h
Normal file
35
src/core/metadata/ProfileRegistry.h
Normal 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
|
||||
39
src/core/models/DOMMessage.h
Normal file
39
src/core/models/DOMMessage.h
Normal 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
|
||||
42
src/core/models/DataMapping.h
Normal file
42
src/core/models/DataMapping.h
Normal 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
|
||||
24
src/core/models/MetadataDef.h
Normal file
24
src/core/models/MetadataDef.h
Normal 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
38
src/core/models/Types.h
Normal 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
|
||||
79
src/core/threading/MutexQueue.h
Normal file
79
src/core/threading/MutexQueue.h
Normal 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
|
||||
Reference in New Issue
Block a user