diff --git a/bin/Release/assets/config/EMU对外通信点表最终修改1版_v9.xlsx b/bin/Release/assets/config/EMU对外通信点表最终修改1版_v9.xlsx index 75a46df..097244b 100644 Binary files a/bin/Release/assets/config/EMU对外通信点表最终修改1版_v9.xlsx and b/bin/Release/assets/config/EMU对外通信点表最终修改1版_v9.xlsx differ diff --git a/bin/Release/assets/config/app.json b/bin/Release/assets/config/app.json index 5f6843e..9096e24 100644 --- a/bin/Release/assets/config/app.json +++ b/bin/Release/assets/config/app.json @@ -6,7 +6,7 @@ "exportpath": "D:/Programs/openresty-1.27.1.1-win64/zdownload", "database": {"host": "localhost", "port": 3306, "user": "root", "passwd": "123456", "dbname": "ess"}, "http": {"token":0, "port": 19801, "encryption":0, "encryptKey":""}, - "mqtt": {"host":"mqtt://43.136.119.46:6203","username":"jsyhsec","password":"123456"}, + "mqtt": {"host":"mqtt://118.195.161.113:8883","username":"admin","password":"jsyh@2025", "interval": 120}, "topic": { "EMS_YX": {"deviceType":101, "polling":0, "enabled": 1}, "EMS_YC": {"deviceType":101, "polling":0, "enabled": 1}, @@ -32,5 +32,5 @@ "video": { "1":{"host":"", "port":9000, "user":"", "passwd":""} }, - "statistics": {"enabled": 1, "interval": 30} + "statistics": {"enabled": 1, "interval": 120} } \ No newline at end of file diff --git a/bin/Release/assets/config/regaddrs.json b/bin/Release/assets/config/regaddrs.json index 5247cdb..375213f 100644 --- a/bin/Release/assets/config/regaddrs.json +++ b/bin/Release/assets/config/regaddrs.json @@ -547,7 +547,7 @@ {"key": "0x0041", "datatype": "uint16", "alert": 0, "name":"单体最小SOc簇号", "remark": ""}, {"key": "0x0042", "datatype": "uint16", "alert": 0, "name":"单体最小SOc节号", "remark": ""}, {"key": "0x0043", "datatype": "uint16", "alert": 0, "name":"单体最小SOc", "remark": "(0.001)"}, - {"key": "0x0043", "datatype": "uint32", "alert": 0, "name":"系统剩余最大可充电功率", "remark": "(1KW)"}, + {"key": "0x0044", "datatype": "uint32", "alert": 0, "name":"系统剩余最大可充电功率", "remark": "(1KW)"}, {"key": "0x0045", "datatype": "uint32", "alert": 0, "name":"系统剩余最大可放电功率", "remark": "(1KW)"}, {"key": "0x0047", "datatype": "uint16", "alert": 0, "name":"可充电状态", "remark": ""}, {"key": "0x0048", "datatype": "uint16", "alert": 0, "name":"可放电状态", "remark": ""}, diff --git a/bin/Release/yhicon.ico b/bin/Release/yhicon.ico deleted file mode 100644 index c7a07f1..0000000 Binary files a/bin/Release/yhicon.ico and /dev/null differ diff --git a/src/app/AppData.cpp b/src/app/AppData.cpp index 67a853e..509d23d 100644 --- a/src/app/AppData.cpp +++ b/src/app/AppData.cpp @@ -69,9 +69,9 @@ bool AppData::initFromDB() std::string name = fields.value(DMDefWorkMode::NAME); this->mapping.policyType.push_back({std::to_string(policyTypeId), name}); this->mapPolicyType[policyTypeId] = name; - str += ("策略类型: {" + std::to_string(policyTypeId) + ":" + name + "},"); + //str += ("策略类型: {" + std::to_string(policyTypeId) + ":" + name + "},"); } - spdlog::info(str); + //spdlog::info(str); } { // 数据库读取设备类型定义 str = "", result.clear(); @@ -87,9 +87,9 @@ bool AppData::initFromDB() item->fieldsAttr.parseJson(item->attr); mapDeviceType[item->typeId] = item; mapping.deviceType.push_back({std::to_string(item->typeId), item->name}); - str += ("设备类型: {" + std::to_string(item->typeId) + ":" + item->name + "},"); + //str += ("设备类型: {" + std::to_string(item->typeId) + ":" + item->name + "},"); } - spdlog::info(str); + //spdlog::info(str); } { // 数据库读取角色定义 str = "", result.clear(); @@ -103,9 +103,9 @@ bool AppData::initFromDB() item->isOpen = fields.get(DMRole::IS_OPEN); mapRole[item->roleId] = item; mapping.role.push_back({std::to_string(item->roleId), item->name}); - str += ("角色: {" + std::to_string(item->roleId) + ":" + item->name + "},"); + //str += ("角色: {" + std::to_string(item->roleId) + ":" + item->name + "},"); } - spdlog::info(str); + //spdlog::info(str); } { // 数据库读取场站信息 str = "", result.clear(); @@ -118,9 +118,8 @@ bool AppData::initFromDB() station->setFields(fields); this->mapStation[station->stationId] = station; this->mapping.stationName.push_back({std::to_string(station->stationId), station->name}); - str += ("场站: {" + std::to_string(station->stationId) + ":" + station->name + "},"); + spdlog::info("场站: {}:{}, {}", station->stationId, station->name, station->status>0 ? "启用" : "未启用"); } - spdlog::info(str); } { // 数据库读取设备信息 str = "", result.clear(); @@ -158,32 +157,32 @@ bool AppData::initFromDB() } } { // 数据库读取电价分段信息 - result.clear(); - vecElectPeriods.resize(12); - DAO::exec(dao, "SELECT * FROM configure;", result); - - Fields info; - for (auto& fields: result) - { - auto k = fields.value("key"); - auto v = fields.value("val"); - info.set(k, v); - } + //result.clear(); + //vecElectPeriods.resize(12); + //DAO::exec(dao, "SELECT * FROM configure;", result); + // + //Fields info; + //for (auto& fields: result) + //{ + // auto k = fields.value("key"); + // auto v = fields.value("val"); + // info.set(k, v); + //} - for (int month = 1; month<=12; month++) - { - if (month-1 < vecElectPeriods.size()) - { - auto& vecItems = vecElectPeriods[month-1]; - std::string str = info.value("period_" + std::to_string(month)); - std::vector vec; - Utils::split(str, ",", vecItems); - } - } - electPriceSuperPeak = info.get("price_super_peak"); - electPricePeak = info.get("price_peak"); - electPriceShoulder = info.get("price_shoulder"); - electPriceOffPeak = info.get("price_off_peak"); + //for (int month = 1; month<=12; month++) + //{ + // if (month-1 < vecElectPeriods.size()) + // { + // auto& vecItems = vecElectPeriods[month-1]; + // std::string str = info.value("period_" + std::to_string(month)); + // std::vector vec; + // Utils::split(str, ",", vecItems); + // } + //} + //electPriceSuperPeak = info.get("price_super_peak"); + //electPricePeak = info.get("price_peak"); + //electPriceShoulder = info.get("price_shoulder"); + //electPriceOffPeak = info.get("price_off_peak"); } { // 数据库读取统计数据 vector result; @@ -457,32 +456,32 @@ int AppData::getPolicyTypeId(std::string name) } -std::vector AppData::getElectPreiodVals(int month) -{ - if (month > 0 && month-1 < vecElectPeriods.size()) - { - return vecElectPeriods[month-1]; - } - return {}; -} - -std::string AppData::getElectPreiodVal(int month, int hour) -{ - if (month > 0 && month-1 < vecElectPeriods.size()) - { - auto& vec = vecElectPeriods[month-1]; - if (hour > 0 && hour-1 < vec.size()) - { - auto& val = vec[hour-1]; - if (val == "尖") return "尖峰"; - if (val == "峰") return "高峰"; - if (val == "平") return "平段"; - if (val == "谷") return "低谷"; - return val; - } - } - return ""; -} +//std::vector AppData::getElectPreiodVals(int month) +//{ +// if (month > 0 && month-1 < vecElectPeriods.size()) +// { +// return vecElectPeriods[month-1]; +// } +// return {}; +//} +// +//std::string AppData::getElectPreiodVal(int month, int hour) +//{ +// if (month > 0 && month-1 < vecElectPeriods.size()) +// { +// auto& vec = vecElectPeriods[month-1]; +// if (hour > 0 && hour-1 < vec.size()) +// { +// auto& val = vec[hour-1]; +// if (val == "尖") return "尖峰"; +// if (val == "峰") return "高峰"; +// if (val == "平") return "平段"; +// if (val == "谷") return "低谷"; +// return val; +// } +// } +// return ""; +//} void AppData::storeRuntimeDB() { diff --git a/src/app/AppData.h b/src/app/AppData.h index 07a24f2..36691d6 100644 --- a/src/app/AppData.h +++ b/src/app/AppData.h @@ -99,9 +99,8 @@ public: // 根据策略类型名称获取策略类型ID int getPolicyTypeId(std::string name); - std::vector getElectPreiodVals(int month); - - std::string getElectPreiodVal(int month, int hour); + //std::vector getElectPreiodVals(int month); + //std::string getElectPreiodVal(int month, int hour); void storeRuntimeDB(); @@ -134,10 +133,12 @@ public: VecPairSS stationName; } mapping; - double electPriceSuperPeak {}; - double electPricePeak {}; - double electPriceShoulder {}; - double electPriceOffPeak {}; + //double electPriceSuperPeak {}; + //double electPricePeak {}; + //double electPriceShoulder {}; + //double electPriceOffPeak {}; + // 电力峰谷分段 (12个月,每个月按小时分成24个时段) + //std::vector> vecElectPeriods; // 场站信息 std::unordered_map> mapStation; @@ -157,8 +158,7 @@ public: // 策略信息 std::unordered_map> mapPolicy; - // 电力峰谷分段 (12个月,每个月按小时分成24个时段) - std::vector> vecElectPeriods; + std::map mapDataDay; diff --git a/src/app/Application.cpp b/src/app/Application.cpp index 16acd66..6c055a9 100644 --- a/src/app/Application.cpp +++ b/src/app/Application.cpp @@ -73,6 +73,8 @@ void Application::runThreadDevice() void Application::runThreadMain() { + std::this_thread::sleep_for(std::chrono::seconds(10)); // 延迟10秒执行 + while (!isQuit) { if (!this->isInit) // 初始化失败 @@ -85,27 +87,29 @@ void Application::runThreadMain() static TimeTick ttMqtt; // 检查 场站的 MQTT 连接 if (ttMqtt.elapse(30)) { - auto& optionMqtt = Config::option.mqtt; - if (!optionMqtt.host.empty()) + for (auto& item : appdata.mapStation) { - for (auto& item : appdata.mapStation) + auto& station = item.second; + // 检查MQTT的连接状态 + if (!Config::option.mqtt.host.empty()) { station->initMqtt(); } + // 检查设备的在线状态 + station->checkDevice(); + } + } + + static TimeTick ttMqttPolling; // 召测 + if (!Config::option.mqtt.host.empty() && ttMqttPolling.elapse(10)) + { + for (auto& item : appdata.mapStation) + { + auto& station = item.second; + if (Utils::time() - station->getPollingTS() > Config::option.mqtt.interval) { - auto& station = item.second; - if (station) - { - if (station->isOpen) - { - // 该函数检查连接状态,若已经连接,则无操作;若未连接,则进行连接操作 - item.second->initMqtt(); - // 召测 - item.second->polling(); - } - // 检查设备的在线状态 - station->checkDevice(); - } + item.second->polling(); } } } + std::this_thread::sleep_for(std::chrono::seconds(1)); } } diff --git a/src/app/Config.cpp b/src/app/Config.cpp index 7ed4d4c..312a5ac 100644 --- a/src/app/Config.cpp +++ b/src/app/Config.cpp @@ -47,8 +47,7 @@ bool Config::init(std::string filename) JSON::read(json, "token", option.http.useToken); JSON::read(json, "port", option.http.port); JSON::read(json, "encryption", option.http.encryption); - JSON::read(json, "encryptKey", option.http.encryptKey); - } + JSON::read(json, "encryptKey", option.http.encryptKey); } else { spdlog::error("[config] parse http failed: not found."); @@ -60,6 +59,7 @@ bool Config::init(std::string filename) JSON::read(json, "host", option.mqtt.host); JSON::read(json, "username", option.mqtt.username); JSON::read(json, "password", option.mqtt.password); + JSON::read(json, "interval", option.mqtt.interval); } else { diff --git a/src/app/Config.h b/src/app/Config.h index a0d0442..88e8445 100644 --- a/src/app/Config.h +++ b/src/app/Config.h @@ -29,6 +29,7 @@ struct AppOption std::string host; std::string username; std::string password; + int interval {60}; } mqtt; struct { diff --git a/src/app/Device.cpp b/src/app/Device.cpp index 0428df5..f06c62a 100644 --- a/src/app/Device.cpp +++ b/src/app/Device.cpp @@ -317,7 +317,10 @@ void Device::setParam(std::string k, int v) } else if (type == int(EDeviceType::BMS)) // 104 BMS { - if (k == "0x0049") { err = (v==1); } //运行状态 R uint16 0 运行状态 0-正常 1-告警 2-保护 0x0049 + if (k == "0x0049") + { + err = (v==1); + } //运行状态 R uint16 0 运行状态 0-正常 1-告警 2-保护 0x0049 else if (k == "0x004A") { running = (v==1 || v==2); } //充放电状态 R uint16 0 0-待机 1-充电 2-放电 0x004A } else if (type == int(EDeviceType::BCU)) // BCU diff --git a/src/app/Station.cpp b/src/app/Station.cpp index 038d18c..4fc9d39 100644 --- a/src/app/Station.cpp +++ b/src/app/Station.cpp @@ -24,8 +24,8 @@ void Station::setFields(Fields& fields) this->workMode = fields.get(DMStation::WORK_MODE); this->code = fields.value(DMStation::CODE); this->status = fields.get(DMStation::STATUS); + this->operationDate = fields.value(DMStation::OPERATION_DATE); - this->isOpen = fields.get(DMStation::STATUS); this->launchDate = fields.value("operation_date"); this->policy.setFields(fields); } @@ -156,12 +156,25 @@ void Station::initMqtt() void Station::polling() { - if (mqttCli) + if (status > 0 && mqttCli) { - mqttCli->polling(); + if (mqttCli->isConnected) + { + mqttCli->polling(); + } + //else + //{ + // // 该函数检查连接状态,若已经连接,则无操作;若未连接,则进行连接操作 + // this->initMqtt(); + //} } } +int64_t Station::getPollingTS() +{ + return (mqttCli != nullptr) ? mqttCli->tsPolling : 0; +} + void Station::setGarewayWorkMode() { if (!mqttCli) diff --git a/src/app/Station.h b/src/app/Station.h index c5b0e32..df192fb 100644 --- a/src/app/Station.h +++ b/src/app/Station.h @@ -111,6 +111,7 @@ public: void initMqtt(); void polling(); + int64_t getPollingTS(); void setGarewayWorkMode(); void checkDevice(); @@ -129,7 +130,6 @@ public: int stationId {}; std::string name; std::string code; - bool isOpen {false}; int status {0}; std::string operationDate; SysPolicy policy; @@ -236,6 +236,28 @@ public: float dayFeeOut {0.0}; // 日放电费用 R uint32 1RMB 0 0x1114 float dayIncome {0.0}; // 日收益 R int32 1RMB 0 0x1116 + //日正向尖有功电能 R uint32 1kWh 0x0039 + //日正向峰有功电能 R uint32 1kWh 0x003B + //日正向平有功电能 R uint32 1kWh 0x003D + //日正向谷有功电能 R uint32 1kWh 0x003F + //日正向总有功电能 R uint32 1kWh 0x0041 + //日反向尖有功电能 R uint32 1kWh 0x0043 + //日反向峰有功电能 R uint32 1kWh 0x0045 + //日反向平有功电能 R uint32 1kWh 0x0047 + //日反向谷有功电能 R uint32 1kWh 0x0049 + //日反向总有功电能 R uint32 1kWh 0x004B + + //总正向尖有功电能 R uint32 1kWh 0x0057 + //总正向峰有功电能 R uint32 1kWh 0x0059 + //总正向平有功电能 R uint32 1kWh 0x005B + //总正向谷有功电能 R uint32 1kWh 0x005D + //总正向总有功电能 R uint32 1kWh 0x005F + //总反向尖有功电能 R uint32 1kWh 0x0061 + //总反向峰有功电能 R uint32 1kWh 0x0063 + //总反向平有功电能 R uint32 1kWh 0x0065 + //总反向谷有功电能 R uint32 1kWh 0x0067 + //总反向总有功电能 R uint32 1kWh 0x0069 + } statData; /////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/common/Spdlogger.cpp b/src/common/Spdlogger.cpp index 61c674b..18a7632 100644 --- a/src/common/Spdlogger.cpp +++ b/src/common/Spdlogger.cpp @@ -1,7 +1,8 @@ #include "Spdlogger.h" #include "spdlog/sinks/stdout_color_sinks.h" -#include +#include "spdlog/sinks/daily_file_sink.h" + //std::shared_ptr Spdlogger::logger_ = spdlog::stdout_color_mt("sys"); //std::shared_ptr Spdlogger::logger = spdlog::daily_logger_mt("daily_logger", "ees.log", 0, 0); @@ -12,31 +13,36 @@ void Spdlogger::init(spdlog::level::level_enum log_level, std::string filename) //logger = spdlog::daily_logger_mt("daily_logger", filename, 0, 0); } - //logger_ = spdlog::stdout_color_mt("sys"); - //logger_ = spdlog::daily_logger_mt("daily_logger", "log/pvs.log", 0, 0); - // set log level : spdlog::level::debug - //logger->set_level(log_level); - //logger->flush_on(log_level); - + std::vector mySinks; + bool consoleEnabled = false; // 创建控制台接收器 - auto consoleSink = std::make_shared(); - //consoleSink->set_level(log_level); // 设置控制台日志等级 - //consoleSink->set_pattern("[%T] [%^%l%$] %v"); // 设置日志格式 + if (consoleEnabled) + { + auto consoleSink = std::make_shared(); + //consoleSink->set_level(log_level); // 设置控制台日志等级 + //consoleSink->set_pattern("[%T] [%^%l%$] %v"); // 设置日志格式 + mySinks.push_back(consoleSink); + } - // 创建文件接收器 - //auto fileSink = std::make_shared("logs/mcs.log", true); - //fileSink->set_level(spdlog::level::debug); // 设置文件日志等级 - //fileSink->set_pattern("[%T] [%l] %v"); // 设置日志格式 + bool fileEnabled = true; + if (fileEnabled) + { + // 创建文件接收器 + //auto fileSink = std::make_shared("logs/mcs.log", true); + //fileSink->set_level(spdlog::level::debug); // 设置文件日志等级 + //fileSink->set_pattern("[%T] [%l] %v"); // 设置日志格式 - // 每日文件sink(可选,每天生成新文件) - auto dailySink = std::make_shared("logs/ess.log", 0, 0); - //dailySink->set_level(log_level); - //dailySink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] %v"); + // 每日文件sink(可选,每天生成新文件) + auto dailySink = std::make_shared("logs/ess.log", 0, 0); + //dailySink->set_level(log_level); + //dailySink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] %v"); + + mySinks.push_back(dailySink); + } // 创建一个多重接收器的 logger - std::vector sinks {consoleSink, dailySink}; - auto logger = std::make_shared("", sinks.begin(), sinks.end()); + auto logger = std::make_shared("", mySinks.begin(), mySinks.end()); // 设置全局 logger spdlog::set_default_logger(logger); diff --git a/src/common/Spdlogger.h b/src/common/Spdlogger.h index 345ac75..5924d2e 100644 --- a/src/common/Spdlogger.h +++ b/src/common/Spdlogger.h @@ -3,6 +3,7 @@ #include "spdlog/spdlog.h" #include // 彩色控制台日志 #include // 文件日志 +#include "spdlog/sinks/qt_sinks.h" #include class Spdlogger diff --git a/src/database/Dao.cpp b/src/database/Dao.cpp index 4b27ba0..7e2fff7 100644 --- a/src/database/Dao.cpp +++ b/src/database/Dao.cpp @@ -4,6 +4,7 @@ #include "common/JsonN.h" #include "app/Application.h" #include "app/AppData.h" +#include "common/Crypto.h" std::string DAO::sqlPageLimit(int index, int size) { @@ -169,8 +170,6 @@ Errcode DAO::login(std::shared_ptr dao, std::string account, std::str if (!dao) { dao = std::make_shared(""); } if (!dao->isConnected()) { - - //DAO1::writeSystemLog(dao, 2, "", account, "用户登录失败:" + err); return Errcode::ERR_DB_CONN; } std::string t = Utils::timeStr(); @@ -181,12 +180,10 @@ Errcode DAO::login(std::shared_ptr dao, std::string account, std::str int ret = dao->exec(sql, result); if (ret != 0) { - //DAO1::writeSystemLog(dao, 2, "", account, "用户登录失败:" + err); return Errcode(ret); } if (result.size() <=0) { - //DAO1::writeSystemLog(dao, 2, "", account, "用户登录失败:" + err); return Errcode::ERR_LOGIN_USER_NOTEXIST; } fields = result[0]; @@ -194,9 +191,9 @@ Errcode DAO::login(std::shared_ptr dao, std::string account, std::str int loginCount = fields.get("login_count"); // 判断密码 - if (passwd != fields.remove("passwd")) + + if (passwd != Crypto::sm3(fields.remove("passwd"))) { - //DAO1::writeSystemLog(dao, 2, userId, account, "用户登录失败:" + err); return Errcode::ERR_LOGIN_PASSWD; } @@ -571,8 +568,8 @@ Errcode DAO::queryStatStationGroup(std::shared_ptr dao, string statio } if (!category.empty() && category != "0") { - if (!sqlCondition.empty()) sqlCondition += " AND "; - sqlCondition += "category='" + category + "'";; + //if (!sqlCondition.empty()) sqlCondition += " AND "; + //sqlCondition += "category='" + category + "'";; } if (!sqlCondition.empty()) { sqlCondition = " WHERE " + sqlCondition; } @@ -615,8 +612,8 @@ Errcode DAO::queryStatStationList(PageInfo& pageInfo, Fields& params, vectorstatus == 1) + if (station) { std::vector> vecDevice; station->getDeviceByCategory(category, vecDevice); @@ -1158,8 +1159,8 @@ static std::string VerifyStatSqlCondition(Fields& params) } if (!category.empty() && category != "0") { - if (!sqlCondition.empty()) sqlCondition += " AND "; - sqlCondition += "ss.category='" + category + "'";; + //if (!sqlCondition.empty()) sqlCondition += " AND "; + //sqlCondition += "ss.category='" + category + "'";; } if (!sqlCondition.empty()) { sqlCondition = " WHERE " + sqlCondition; } return sqlCondition; @@ -1399,8 +1400,8 @@ Errcode HttpEntity::exportStatReport(const httplib::Request& req, njson& json, s std::string endDate = params.value("end_date"); std::string sql = "SELECT s.name station_name, sd.* FROM stat_day sd LEFT JOIN station s ON sd.station_id=s.station_id"; - " WHERE station_id = '" + stationId + "' AND category = '" + category + "'" - " AND dt BETWEEN '" + startDate + "' AND '" + endDate + "2025-09-19' AND sd.category = '1'"; + sql += " WHERE sd.station_id = '" + stationId + "' AND category = '" + category + "'"; + sql += " AND dt BETWEEN '" + startDate + "' AND '" + endDate + "2025-09-19' AND sd.category = '1'"; std::string filename; std::vector result; diff --git a/src/protocol/MqttEntity.cpp b/src/protocol/MqttEntity.cpp index 86cb99e..94154d9 100644 --- a/src/protocol/MqttEntity.cpp +++ b/src/protocol/MqttEntity.cpp @@ -218,6 +218,7 @@ int MqttClient::polling() } } } + tsPolling = Utils::time(); return 0; } @@ -256,6 +257,7 @@ void MqttClient::onConnectSuccess( MQTTAsync_successData* resp) spdlog::info("[mqtt] connect to {} success, clientId={}.", addr, clientId); this->isConnected = true; this->subscribe(); + this->polling(); } void MqttClient::onConnectFaiure(MQTTAsync_failureData* resp) @@ -302,9 +304,9 @@ int MqttClient::onMessageArrived(char* topic, int topicLen, MQTTAsync_message* m std::string key = item.key(); auto& val = item.value(); if (key == "40001") { station->readGatewayMode(val.get()); } - else if (key == "40002") { spdlog::info("[mqtt] read register addr: [{}]={}, {}", key, val.dump(), "峰谷时间段"); } - else if (key == "40021") { spdlog::info("[mqtt] read register addr: [{}]={}, {}", key, val.dump(), "自定时间段"); } - else if (key == "40038") { spdlog::info("[mqtt] read register addr: [{}]={}, {}", key, val.dump(), "其他参数"); } + //else if (key == "40002") { spdlog::info("[mqtt] read register addr: [{}]={}, {}", key, val.dump(), "峰谷时间段"); } + //else if (key == "40021") { spdlog::info("[mqtt] read register addr: [{}]={}, {}", key, val.dump(), "自定时间段"); } + //else if (key == "40038") { spdlog::info("[mqtt] read register addr: [{}]={}, {}", key, val.dump(), "其他参数"); } } } else if (command == "Gateway_YX") @@ -388,13 +390,13 @@ void MqttClient::ParseArrivedMessage(njson& json, string command, std::shared_pt { auto addr = iter->first; auto& regUnit = iter->second; + spdlog::debug("[mqtt] read [{}]={}, {}{}", addr, val, regUnit.name, regUnit.remark); + if (regUnit.alert && val > 0) { station->readAlert(device, addr, val, "[" + command + "]" + regUnit.name + "(" + addr + ")"); } - device->setParam(addr, val); - spdlog::debug("[mqtt] read [{}]={}, {}{}", addr, val, regUnit.name, regUnit.remark); if (command == "MEM_YC") { station->readRuntimeData(deviceNo, addr, val); } else if (command == "Fire40_YX") { station->readFire40Data(deviceNo, addr, val); } diff --git a/src/protocol/MqttEntity.h b/src/protocol/MqttEntity.h index 96d10c4..af0d5a8 100644 --- a/src/protocol/MqttEntity.h +++ b/src/protocol/MqttEntity.h @@ -54,7 +54,7 @@ public: int qos {0}; bool isConnected {false}; bool isSubscribed {false}; - + int64_t tsPolling {0}; static std::map s_mapTopicInfo; }; diff --git a/src/qt/MainApp.cpp b/src/qt/MainApp.cpp index 03483ed..c793363 100644 --- a/src/qt/MainApp.cpp +++ b/src/qt/MainApp.cpp @@ -1,4 +1,5 @@ #include "MainApp.h" +#include "common/Spdlogger.h" #include #include @@ -10,19 +11,25 @@ #include #include #include +#include +#include + +#include "app/Config.h" +#include "common/Utils.h" +#include "QUI.h" +#include "app/Application.h" +#include "app/AppData.h" +#include "app/Station.h" +#include "protocol/MqttEntity.h" static const std::string QSS_BTN_MENU = -"QPushButton{background:rgba(50,128,218,200);color:white;border-radius:5px;border:2px solid rgb(10,120,215);font:bold 18px;}" -"QPushButton:hover{background-color:rgb(32,164,128);}" -"QPushButton:pressed{border-width:3px 0 0 3px;background-color:rgb(1,32,54);border-style:inset;}" -"QPushButton:disabled{color:rgb(150,150,150);}"; +"QPushButton {background:rgba(50,128,218,200);color:white;border-radius:5px;border:2px solid rgb(10,120,215);font:bold 18px;}" +"QPushButton:hover {background-color:rgb(32,164,128);}" +"QPushButton:pressed {border-width:3px 0 0 3px;background-color:rgb(1,32,54);border-style:inset;}" +"QPushButton:disabled {color:rgb(150,150,150);}"; static const std::string QSS_LINE = -"QLineEdit { background-color: rgb(14, 49, 66); color: #ffffff; border: 1px solid rgb(18, 251, 255); border-radius: 5px; font: normal 13px; }"; - -static const std::string QSS_GROUP = -"QGroupBox { border: 1px solid gray; margin-top: 8px; border-radius: 5px;}" -"QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; left:10px; margin-left: 0px; padding:0 1px; }"; +"QLineEdit { background-color: rgb(14, 49, 66); color: #ffffff; border: 1px solid gray; border-radius: 5px; font: bold 13px; }"; MainApp::MainApp() { @@ -45,8 +52,14 @@ MainApp::MainApp() //ui.weburl->setValue("http://www.baidu.com"); this->setMyLayout(); + + timer = std::make_shared(this); + connect(timer.get(), &QTimer::timeout, this, &MainApp::onTimer); + timer->start(1000); // 每隔1000毫秒更新一次 } + + class MyMenu : public QWidget { public: @@ -61,7 +74,7 @@ public: layout.setContentsMargins(2, 2, 2, 2); this->addMenuItem("系统总览"); - this->addMenuItem("运行监控"); + //this->addMenuItem("运行监控"); } void addMenuItem(std::string name) @@ -95,8 +108,9 @@ void PairLine(QWidget* parent, int x, int y, string k, string v) key->setGeometry(x, y, 80, 26); auto value = new QLineEdit(parent); value->setText(v.c_str()); - value->setGeometry(x+80, y, 180, 26); + value->setGeometry(x+80, y, 260, 26); value->setStyleSheet(QSS_LINE.c_str()); + value->setReadOnly(true); } @@ -110,36 +124,144 @@ public: int x=10, y=10; { - QGroupBox* groupBox = new QGroupBox("HTTP", this); - groupBox->setGeometry(x, y, 300, 120); - groupBox->setStyleSheet(QSS_GROUP.c_str()); - - PairLine(groupBox, 20, 20, "服务类型: ", "服务端"); - PairLine(groupBox, 20, 50, "服务地址: ", "92.168.0.13:17900"); - PairLine(groupBox, 20, 80, "服务状态: ", "---"); + this->groupSys = UI::GroupBox(this, x, y, 1190, 120, "系统"); + auto pw = groupSys.get(); } { - x += 320; - QGroupBox* groupBox = new QGroupBox("MQTT", this); - groupBox->setGeometry(x, y, 300, 120); - groupBox->setStyleSheet(QSS_GROUP.c_str()); - - PairLine(groupBox, 20, 20, "服务类型: ", "客户端"); - PairLine(groupBox, 20, 50, "服务地址: ", "92.168.0.13:17800"); - PairLine(groupBox, 20, 80, "服务状态: ", "---"); + x = 10, y += 130; + this->groupHttp = UI::GroupBox(this, x, y, 390, 120, "HTTP"); + auto pw = groupHttp.get(); + PairLine(pw, 20, 20, "服务类型: ", "服务端"); + PairLine(pw, 20, 50, "服务端口: ", Utils::toStr(Config::option.http.port)); + PairLine(pw, 20, 80, "服务状态: ", "运行"); } { - x += 320; - QGroupBox* groupBox = new QGroupBox("数据库", this); - groupBox->setGeometry(x, y, 300, 120); - groupBox->setStyleSheet(QSS_GROUP.c_str()); + x += 400; + this->groupMqtt = UI::GroupBox(this, x, y, 390, 120, "MQTT"); + auto pw = groupMqtt.get(); + PairLine(pw, 20, 20, "服务类型: ", "客户端"); + PairLine(pw, 20, 50, "服务地址: ", Config::option.mqtt.host); + PairLine(pw, 20, 80, "服务状态: ", "---"); + } + { + x += 400; + this->groupDB = UI::GroupBox(this, x, y, 390, 120, "数据库"); + auto pw = groupDB.get(); + PairLine(pw, 20, 20, "数据库名: ", Config::option.database.dbname); + PairLine(pw, 20, 50, "主机地址: ", Config::option.database.host); + PairLine(pw, 20, 80, "用 户 名: ", Config::option.database.user); - PairLine(groupBox, 20, 20, "数据库名: ", "ees"); - PairLine(groupBox, 20, 50, "主机地址: ", "92.168.0.13:17800"); - PairLine(groupBox, 20, 80, "用 户 名: ", "root"); + + const std::string QSS_TABLE = // 表格整体样式 + "QTableWidget {" + " background-color: transparent;" // 背景色 + " gridline-color: #C0C0C0;" // 网格线颜色 + " border: 1px solid gray;" // 边框 + " color: white;" // 文字颜色 + "}" + // 表头样式 + "QHeaderView::section {" + " background-color: #404040;" // 表头背景 + " padding: 4px;" // 内边距 + " border: 1px solid #505050;" // 边框 + " min-height: 25px;" // 最小高度 + "}" + // 单元格样式 + "QTableWidget::item {" + " padding-left: 5px;" + " border-bottom: 1px solid gray;" // 底部边框 + "}" + // 选中状态 + "QTableWidget::item:selected {" + " background-color: #B8D6FF;" // 选中背景色 + " color: black;" // 选中文字颜色 + "}"; + + table = std::make_shared(this); + table->setGeometry(10, y += 130, 1190, 300); + table->setStyleSheet(QSS_TABLE.c_str()); + table->horizontalHeader()->setStretchLastSection(true); // 最后一列占满 + table->verticalHeader()->setVisible(false); // 不显示垂直表头 + table->setEditTriggers(QAbstractItemView::NoEditTriggers); // 单元格不可编辑 + table->setSelectionMode(QAbstractItemView::SingleSelection); // 设置为单选模式 + table->setSelectionBehavior(QAbstractItemView::SelectRows); // 设置为整行选中 + + + QTableWidgetItem* headerItem; + QStringList headerText_Row, headerText_Col; + headerText_Row << "站ID" << "站名称" << "站编号" << "站状态" << "MQTT状态" << "召测(秒)" << "说明"; + + // 设置为水平表头 + table->setColumnCount(headerText_Row.size()); + table->setHorizontalHeaderLabels(headerText_Row); + } + + textLog = std::make_shared(this); + textLog->setGeometry(10, y += 310, 1190, 280); + textLog->setStyleSheet("background-color: transparent; border: 1px solid gray; font-weight: 400;"); + textLog->setReadOnly(true); + + { + // 第二个参数是方法函数名称,即调用 QTextEdit的appeng函数; + auto qtSink = std::make_shared(textLog.get(), "append"); + spdlog::default_logger()->sinks().push_back(qtSink); + } + { + //qtSink = std::make_shared(textLog.get()); + //connect(qtSink.get(), &QtTextSink::appendText, textLog.get(), &QTextEdit::append); } } + void setTableCell(int row, int col, std::string text, std::string style="") + { + auto item = table->item(row, col); + if (!item) + { + item = new QTableWidgetItem(); + table->setItem(row, col, item); + } + item->setText(text.c_str()); + if (style == "OK") { item->setForeground(QBrush(Qt::green)); } + else if (style == "ERR") { item->setForeground(QBrush(Qt::red)); } + } + + void onTimer() + { + auto& appdata = Application().data(); + int rowNo = 0; + int tsNow = Utils::time(); + for (auto& item : appdata.mapStation) + { + auto& station = item.second; + if (rowNo >= table->rowCount()) + { + table->insertRow(rowNo); + } + bool isOpen = station->status > 0; + bool isConnected = station->mqttCli->isConnected; + + setTableCell(rowNo, 0, std::to_string(station->stationId)); + setTableCell(rowNo, 0, std::to_string(station->stationId)); + setTableCell(rowNo, 1, station->name); + setTableCell(rowNo, 2, station->code); + setTableCell(rowNo, 3, isOpen ? "启用" : "未启用", isOpen ? "OK" : "ERR"); + setTableCell(rowNo, 4, isConnected ? "连接成功" : "未连接", isConnected ? "OK" : "ERR"); + + int tsPolling = station->getPollingTS(); + setTableCell(rowNo, 5, tsPolling > 0 ? std::to_string(tsNow - tsPolling) + "/" + std::to_string(Config::option.mqtt.interval) : "--"); + rowNo++; + } + } + + // 创建自定义sink + std::shared_ptr qtSink; + + std::shared_ptr groupSys; + std::shared_ptr groupHttp; + std::shared_ptr groupMqtt; + std::shared_ptr groupDB; + std::shared_ptr table {}; + std::shared_ptr textLog; }; void MainApp::setMyLayout() @@ -162,9 +284,9 @@ void MainApp::setMyLayout() menu->setSizePolicy(sizePolicy); layout.main->addWidget(menu, 0, 0, 1, 1); - MyWorkspace* workspace = new MyWorkspace(this); - workspace->setSizePolicy(sizePolicy); - layout.main->addWidget(workspace, 0, 1, 1, 1); + ui.workspace = std::make_shared(this); + ui.workspace->setSizePolicy(sizePolicy); + layout.main->addWidget(ui.workspace.get(), 0, 1, 1, 1); // 设置列宽和行高 layout.main->setColumnMinimumWidth(0, 200); // 设置第0列的最小宽度为100像素 @@ -175,4 +297,11 @@ void MainApp::setMyLayout() layout.main->setColumnStretch(1, 2); // 设置第1列的伸缩因子为2,使其更宽 //gridLayout->setRowStretch(0, 1); // 设置第0行的伸缩因子为1 +} + + + +void MainApp::onTimer() +{ + ui.workspace->onTimer(); } \ No newline at end of file diff --git a/src/qt/MainApp.h b/src/qt/MainApp.h index b157694..35b15d7 100644 --- a/src/qt/MainApp.h +++ b/src/qt/MainApp.h @@ -2,9 +2,54 @@ #include #include #include +#include +#include +#include #include using namespace std; +#include +#include +#include +#include +#include + +class QtTextSink : public QObject, public spdlog::sinks::base_sink +{ + Q_OBJECT +public: + // 构造函数接收一个 QTextEdit*,但用 QPointer 来包装它 + explicit QtTextSink(QTextEdit* textEdit, QObject* parent = nullptr) + : QObject(parent), textEdit(textEdit) { + } + +signals: + // 定义一个信号,用于在主线程中追加文本 + void appendText(const QString& message); + +protected: + // 重写 sink_it_ 方法,处理每条日志 + void sink_it_(const spdlog::details::log_msg& msg) override { + if (textEdit.isNull()) { + // 如果 QTextEdit 已经被删除,则忽略这条日志 + return; + } + spdlog::memory_buf_t formatted; + formatter_->format(msg, formatted); + QString logMessage = QString::fromStdString(fmt::to_string(formatted)); + // 发射信号,通过Qt的事件循环在主线程中安全地更新UI + emit appendText(logMessage); + } + + void flush_() override {} + +private: + QPointer textEdit; // 关键:使用 QPointer +}; + + + + class LabelPair { public: @@ -34,6 +79,9 @@ public: QLabel value; }; + +class MyWorkspace; + class MainApp : public QWidget { Q_OBJECT @@ -42,12 +90,19 @@ public: void setMyLayout(); + +private slots: + void onTimer(); + +public: struct { std::shared_ptr weburl {}; + std::shared_ptr workspace; } ui; struct { std::shared_ptr main; } layout; + std::shared_ptr timer; }; \ No newline at end of file diff --git a/src/qt/QUI.cpp b/src/qt/QUI.cpp new file mode 100644 index 0000000..8584f30 --- /dev/null +++ b/src/qt/QUI.cpp @@ -0,0 +1,14 @@ +#include "QUI.h" + +static const std::string QSS_GROUP = +"QGroupBox { border: 1px solid gray; margin-top: 8px; border-radius: 5px;}" +"QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; left:10px; margin-left: 0px; padding:0 1px; }"; + + +std::shared_ptr UI::GroupBox(QWidget* parent, int x, int y, int w, int h, std::string title) +{ + auto groupBox = std::make_shared(title.c_str(), parent); + groupBox->setGeometry(x, y, w, h); + groupBox->setStyleSheet(QSS_GROUP.c_str()); + return groupBox; +} \ No newline at end of file diff --git a/src/qt/QUI.h b/src/qt/QUI.h new file mode 100644 index 0000000..27024f1 --- /dev/null +++ b/src/qt/QUI.h @@ -0,0 +1,10 @@ +#pragma once + +#include + + +class UI +{ +public: + static std::shared_ptr GroupBox(QWidget* parent, int x, int y, int w, int h, std::string title); +}; \ No newline at end of file diff --git a/src/resource.rc b/src/resource.rc index 4e1b7c8..8f18745 100644 Binary files a/src/resource.rc and b/src/resource.rc differ diff --git a/src/yhicon.ico b/src/yhicon.ico deleted file mode 100644 index c7a07f1..0000000 Binary files a/src/yhicon.ico and /dev/null differ