mirror of
https://gitee.com/js-yhsec/energy_storage.git
synced 2026-05-27 18:59:26 +08:00
1.新增服务端UI界面,显示服务基础信息。2.修改设备显示配置。3.监控页面储能系统显示储能模式,模式设置新增'手动'
This commit is contained in:
@@ -87,6 +87,7 @@ ADD_SOURCE_GROUP(protocol)
|
||||
#ADD_SOURCE_GROUP(pv)
|
||||
#ADD_SOURCE_GROUP(pv/pages)
|
||||
ADD_SOURCE_GROUP(qt)
|
||||
ADD_SOURCE_GROUP(qt/widgets)
|
||||
|
||||
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/../bin)
|
||||
add_executable(${PROJECT_NAME} ${SOURCE_FILE} "resource.rc")
|
||||
|
||||
@@ -195,19 +195,17 @@ bool AppData::initFromDB()
|
||||
auto station = this->getStation(stationId);
|
||||
if (station)
|
||||
{
|
||||
station->storageIn = fields.get<double>(DMStatStation::STORAGE_ELECT_IN);
|
||||
station->storageOut = fields.get<double>(DMStatStation::STORAGE_ELECT_OUT);
|
||||
//station->storageIn = fields.get<double>(DMStatStation::STORAGE_ELECT_IN);
|
||||
//station->storageOut = fields.get<double>(DMStatStation::STORAGE_ELECT_OUT);
|
||||
//station->storageNumIn = fields.getFloat(DMStatStation::STORAGE_NUM);
|
||||
//station->storageNumOut = fields.getFloat(DMStatStation::STORAGE_NUM);
|
||||
station->storageNumErr = fields.get<int>(DMStatStation::STORAGE_NUM_ERR);
|
||||
|
||||
station->solarGen = fields.get<double>(DMStatStation::SOLAR_ELECT_GEN);
|
||||
station->solarGrid = fields.get<double>(DMStatStation::SOLAR_ELECT_GRID);
|
||||
station->solarNumErr = fields.get<int>(DMStatStation::SOLAR_NUM_ERR);
|
||||
|
||||
station->chargeElect = fields.get<double>(DMStatStation::CHARGE_ELECT);
|
||||
station->chargeNum = fields.get<int>(DMStatStation::CHARGE_NUM);
|
||||
station->chargeNumErr = fields.get<int>(DMStatStation::CHARGE_NUM_ERR);
|
||||
//station->storageNumErr = fields.get<int>(DMStatStation::STORAGE_NUM_ERR);
|
||||
//station->solarGen = fields.get<double>(DMStatStation::SOLAR_ELECT_GEN);
|
||||
//station->solarGrid = fields.get<double>(DMStatStation::SOLAR_ELECT_GRID);
|
||||
//station->solarNumErr = fields.get<int>(DMStatStation::SOLAR_NUM_ERR);
|
||||
//station->chargeElect = fields.get<double>(DMStatStation::CHARGE_ELECT);
|
||||
//station->chargeNum = fields.get<int>(DMStatStation::CHARGE_NUM);
|
||||
//station->chargeNumErr = fields.get<int>(DMStatStation::CHARGE_NUM_ERR);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -216,30 +214,38 @@ bool AppData::initFromDB()
|
||||
}
|
||||
}
|
||||
|
||||
{ // 初始化场站设备的历史监测数据
|
||||
|
||||
{ // 初始化场站设备的历史监测数据(当天)
|
||||
vector<Fields> result;
|
||||
DAO::queryRuntimeData(dao, Utils::dateStr(), result);
|
||||
DAO::queryRuntimeDataHistory(dao, Utils::dateStr(), result);
|
||||
for (auto& item : result)
|
||||
{
|
||||
int stationId = item.get<int>("station_id");
|
||||
int deviceId = item.get<int>("device_id");
|
||||
int datatype = item.get<int>("datatype");
|
||||
auto device = this->getDevice(stationId, deviceId);
|
||||
if (device)
|
||||
{
|
||||
int datatype = item.get<int>("datatype");
|
||||
std::string value = item.value("value");
|
||||
|
||||
njson json;
|
||||
if (JSON::parse(value, json))
|
||||
{
|
||||
std::vector<double> vecVal(json.size());
|
||||
for (int i=0; i<json.size(); ++i)
|
||||
{
|
||||
vecVal[i] = JSON::get<double>(json[i]);
|
||||
}
|
||||
device->setCache(datatype, vecVal);
|
||||
}
|
||||
std::vector<float> vd;
|
||||
JSON::parseArray(value, vd);
|
||||
device->setCache(datatype, vd);
|
||||
}
|
||||
}
|
||||
}
|
||||
{ // 初始化预测数据源的历史数据(当天)
|
||||
vector<Fields> result;
|
||||
DAO::queryPredictHistory(dao, Utils::dateStr(), result);
|
||||
for (auto& item : result)
|
||||
{
|
||||
int stationId = item.get<int>("station_id");
|
||||
int datatype = item.get<int>("datatype");
|
||||
auto station = this->getStation(stationId);
|
||||
if (station)
|
||||
{
|
||||
std::string value = item.value("value");
|
||||
std::vector<float> vd;
|
||||
JSON::parseArray(value, vd);
|
||||
station->setCache(datatype, vd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,12 +98,13 @@ void Application::runThreadMain()
|
||||
}
|
||||
|
||||
static TimeTick ttMqttPolling; // 召测
|
||||
if (!Config::option.mqtt.host.empty() && ttMqttPolling.elapse(10))
|
||||
int mqttInterval = Config::option.mqtt.interval;
|
||||
if (!Config::option.mqtt.host.empty() && mqttInterval > 0 && ttMqttPolling.elapse(10))
|
||||
{
|
||||
for (auto& item : appdata.mapStation)
|
||||
{
|
||||
auto& station = item.second;
|
||||
if (Utils::time() - station->getPollingTS() > Config::option.mqtt.interval)
|
||||
if (Utils::time() - station->getPollingTS() > mqttInterval)
|
||||
{
|
||||
item.second->polling();
|
||||
}
|
||||
|
||||
@@ -187,22 +187,9 @@ void Device::getCachePower(std::vector<std::string>& vec)
|
||||
}
|
||||
}
|
||||
|
||||
int64_t GetCurrentTimePos(int step)
|
||||
void Device::setCache(int datatype, std::vector<float>& vd)
|
||||
{
|
||||
auto tp = chrono::system_clock::now();
|
||||
int64_t tTime = chrono::time_point_cast<chrono::seconds>(tp).time_since_epoch().count();
|
||||
std::time_t t = chrono::system_clock::to_time_t(tp);
|
||||
std::tm* tmlocal = localtime(&t);
|
||||
tmlocal->tm_hour = 0;
|
||||
tmlocal->tm_min = 0;
|
||||
tmlocal->tm_sec = 0;
|
||||
int64_t tDate = chrono::time_point_cast<chrono::seconds>(chrono::system_clock::from_time_t(mktime(tmlocal))).time_since_epoch().count();
|
||||
return (tTime - tDate) / step;
|
||||
}
|
||||
|
||||
void Device::setCache(int datatype, std::vector<double>& vec)
|
||||
{
|
||||
std::map<int, double>* mapptr = NULL;
|
||||
std::map<int, float>* mapptr = NULL;
|
||||
if (datatype == 1) { mapptr = &mapCacheVoltage; }
|
||||
else if (datatype == 2) { mapptr = &mapCacheCurrent; }
|
||||
else if (datatype == 3) { mapptr = &mapCachePower; }
|
||||
@@ -211,12 +198,12 @@ void Device::setCache(int datatype, std::vector<double>& vec)
|
||||
{
|
||||
const int step = 600;
|
||||
const int N = 86400/step;
|
||||
|
||||
int n = GetCurrentTimePos(step);
|
||||
int64_t tsSeconds = Utils::timeDaySeconds();
|
||||
int npos = tsSeconds / step;
|
||||
for (int i = 0; i<N; ++i)
|
||||
{
|
||||
if (i < vec.size()) { (*mapptr)[i] = vec[i]; }
|
||||
else if (i <= n) { (*mapptr)[i] = 0; }
|
||||
if (i < vd.size()) { (*mapptr)[i] = vd[i]; }
|
||||
else if (i <= npos) { (*mapptr)[i] = 0; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -402,10 +389,10 @@ void Device::getRuntimeParams(std::vector<std::pair<std::string, std::string>>&
|
||||
if (v == "0") v = "并网";
|
||||
else if (v == "1") v = "离网";
|
||||
}
|
||||
if (item.addr == "0x1008") // 模块状态 R uint16 1开机,0待机 0x1008
|
||||
if (item.addr == "0x1006") // 启停状态 R uint16 1开机,0关机 0x1008
|
||||
{
|
||||
if (v == "0") v = "开机";
|
||||
else if (v == "1") v = "待机";
|
||||
if (v == "1") v = "开机";
|
||||
else if (v == "0") v = "关机";
|
||||
}
|
||||
}
|
||||
else if (type == int(EDeviceType::PCS))
|
||||
@@ -466,7 +453,7 @@ void Device::setBCUUnit(std::string k, int pos, int v, int count)
|
||||
if (k == "0x0056") { bcuUnit[0] = float(v) * 0.1f; }
|
||||
else if (k == "0x043E") { bcuUnit[1] = float(v) * 0.1f; }
|
||||
else if (k == "0x0826") { bcuUnit[2] = float(v) * 0.001f; }
|
||||
else if (k == "0x0C0E") { bcuUnit[3] = float(v); } // * 0.01f
|
||||
else if (k == "0x0C0E") { bcuUnit[3] = float(v) * 0.01f; } // * 0.01f
|
||||
else if (k == "0x0FF6") { bcuUnit[4] = float(v); }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ public:
|
||||
void getCachePower(std::vector<std::string>& vec);
|
||||
|
||||
// int datatype: 1: 电压,2:电流,3:功率
|
||||
void setCache(int datatype, std::vector<double>& vec);
|
||||
void setCache(int datatype, std::vector<float>& vec);
|
||||
|
||||
bool cache(int npos);
|
||||
void storeDB(int npos);
|
||||
@@ -87,9 +87,9 @@ public:
|
||||
std::shared_ptr<CommEntity> commEntity;
|
||||
|
||||
int64_t tsDataDate {};
|
||||
std::map<int, double> mapCacheVoltage;
|
||||
std::map<int, double> mapCacheCurrent;
|
||||
std::map<int, double> mapCachePower;
|
||||
std::map<int, float> mapCacheVoltage;
|
||||
std::map<int, float> mapCacheCurrent;
|
||||
std::map<int, float> mapCachePower;
|
||||
std::map<std::string, std::string> mapParams;
|
||||
|
||||
std::map<std::string, DeviceParamAddr*> mapMyParams;
|
||||
|
||||
@@ -203,6 +203,19 @@ void Station::setGarewayWorkMode()
|
||||
mqttCli->publish("Gateway_YT", text);
|
||||
}
|
||||
|
||||
|
||||
string Station::getGatewayMode()
|
||||
{
|
||||
// 0:手动,1:峰谷套利,2:增网配容,3:应急供电,4:并网保电,5:自定时段
|
||||
if (workModeGateway == 0) { return "手动"; }
|
||||
else if (workModeGateway == 1) { return "峰谷套利"; }
|
||||
else if (workModeGateway == 2) { return "增网配容"; }
|
||||
else if (workModeGateway == 3) { return "应急供电"; }
|
||||
else if (workModeGateway == 4) { return "并网保电"; }
|
||||
else if (workModeGateway == 5) { return "自定时段"; }
|
||||
else { return "--"; };
|
||||
}
|
||||
|
||||
void Station::checkDevice()
|
||||
{
|
||||
for (auto& item: mapDevice)
|
||||
@@ -331,6 +344,7 @@ void Station::readCoolingData(int deviceNo, string addr, int val)
|
||||
|
||||
void Station::readGatewayMode(int mode)
|
||||
{
|
||||
this->workModeGateway = mode;
|
||||
if (mode != this->workMode)
|
||||
{
|
||||
//this->setGarewayWorkMode();
|
||||
@@ -363,20 +377,61 @@ void Station::readGatewayStatus(int cdzStatus, int emuStatus)
|
||||
}
|
||||
}
|
||||
|
||||
static std::string MapValueToJson(int npos, std::map<int, double>& mapV)
|
||||
static std::string MapValueToJson(int npos, std::map<int, float>& mapV)
|
||||
{
|
||||
njson jsonarray = njson::array();
|
||||
for (int i = 0; i<=npos; i++)
|
||||
{
|
||||
jsonarray.push_back(mapV[i]);
|
||||
jsonarray.push_back(int(mapV[i]));
|
||||
}
|
||||
return jsonarray.dump();
|
||||
}
|
||||
void Station::setCache(int datatype, std::vector<float>& vd)
|
||||
{
|
||||
std::map<int, float>* mapptr = NULL;
|
||||
if (datatype == 1) { mapptr = &mapCacheElectIn; }
|
||||
else if (datatype == 2) { mapptr = &mapCacheElectOut; }
|
||||
else if (datatype == 3) { mapptr = &mapCacheElectCharger; }
|
||||
if (mapptr)
|
||||
{
|
||||
const int step = 600;
|
||||
const int N = 86400/step;
|
||||
int64_t tsSeconds = Utils::timeDaySeconds();
|
||||
int npos = tsSeconds / step;
|
||||
for (int i = 0; i<N; ++i)
|
||||
{
|
||||
if (i < vd.size()) { (*mapptr)[i] = vd[i]; }
|
||||
else if (i <= npos) { (*mapptr)[i] = 0; }
|
||||
}
|
||||
}
|
||||
}
|
||||
void Station::cache()
|
||||
{
|
||||
int64_t tDaySeconds = Utils::timeDaySeconds();
|
||||
int npos = tDaySeconds / 600;
|
||||
int offset = tDaySeconds % 600;
|
||||
bool save = false;
|
||||
if (offset >= (600-180) && npos + 1 < 144)
|
||||
{
|
||||
npos += 1;
|
||||
save = true;
|
||||
}
|
||||
else if (offset <= 180 && posCache < npos)
|
||||
{
|
||||
save = true;
|
||||
posCache = npos;
|
||||
}
|
||||
if (save)
|
||||
{
|
||||
mapCacheElectIn[npos] = Utils::random(100, 800); // dayElectIn
|
||||
mapCacheElectOut[npos] = Utils::random(100, 800); // dayElectOut
|
||||
mapCacheElectCharger[npos] = Utils::random(100, 800); // 暂无数据源
|
||||
}
|
||||
}
|
||||
|
||||
void Station::writeStatistic()
|
||||
{
|
||||
auto dao = DaoEntity::create("history_day");
|
||||
|
||||
std::string dt = Utils::dateStr();
|
||||
int64_t tTime = Utils::time();
|
||||
int64_t tDate = Utils::date();
|
||||
@@ -386,7 +441,7 @@ void Station::writeStatistic()
|
||||
for (auto iter = mapDevice.begin(); iter!=mapDevice.end(); ++iter)
|
||||
{
|
||||
auto device = iter->second;
|
||||
if (device->cache(npos) && device->type == int(EDeviceType::BMS))
|
||||
if (device->cache(npos))
|
||||
{
|
||||
Fields fields;
|
||||
fields.set("dt", dt);
|
||||
@@ -407,7 +462,7 @@ void Station::writeStatistic()
|
||||
}
|
||||
}
|
||||
|
||||
if (statData.ts != 0)
|
||||
if (statData.ts > 0)
|
||||
{
|
||||
Fields fields;
|
||||
fields.set("dt", Utils::dateStr(statData.ts));
|
||||
@@ -437,15 +492,14 @@ void Station::writeStatistic()
|
||||
};
|
||||
dao->duplicateUpdate(fields, vecKeys);
|
||||
|
||||
{
|
||||
{ // stat_day
|
||||
Fields fields;
|
||||
fields.set("dt", Utils::dateStr(statData.ts));
|
||||
fields.set("station_id", this->stationId);
|
||||
fields.set("device_id", 0);
|
||||
fields.set("storage_elect_in", statData.dayElectIn);
|
||||
fields.set("storage_elect_out", statData.dayElectOut);
|
||||
fields.set("income_elect", statData.dayIncome);
|
||||
DAO::insertStatStation(dao, fields);
|
||||
DAO::insertStatDay(dao, fields);
|
||||
}
|
||||
{
|
||||
Fields fields;
|
||||
@@ -458,4 +512,25 @@ void Station::writeStatistic()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
// 预测数据源记录
|
||||
dao->setTableName("predict_day");
|
||||
|
||||
Fields fields;
|
||||
fields.set("dt", dt);
|
||||
fields.set("station_id", stationId);
|
||||
fields.set("datatype", 1); // 1:储能充电,2:储能放电,3:充电桩充电,4:发电
|
||||
fields.set("value", MapValueToJson(npos, mapCacheElectIn));
|
||||
dao->duplicateUpdate(fields, {"value"});
|
||||
|
||||
fields.set("datatype", 2); // 1:储能充电,2:储能放电,3:充电桩充电,4:发电
|
||||
fields.set("value", MapValueToJson(npos, mapCacheElectOut));
|
||||
dao->duplicateUpdate(fields, {"value"});
|
||||
|
||||
fields.set("datatype", 3); // 1:储能充电,2:储能放电,3:充电桩充电,4:发电
|
||||
fields.set("value", MapValueToJson(npos, mapCacheElectCharger));
|
||||
dao->duplicateUpdate(fields, {"value"});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -115,6 +115,8 @@ public:
|
||||
void setGarewayWorkMode();
|
||||
void checkDevice();
|
||||
|
||||
string getGatewayMode();
|
||||
|
||||
void readAlert(std::shared_ptr<Device> device, std::string addr, int v, std::string text);
|
||||
void readRuntimeData(int deviceNo, string addr, int val);
|
||||
void readTHData(int deviceNo, string addr, int val);
|
||||
@@ -123,6 +125,10 @@ public:
|
||||
void readGatewayMode(int mode);
|
||||
void readGatewayStatus(int cdzStatus, int emuStatus);
|
||||
|
||||
void setCache(int datatype, std::vector<float>& vd);
|
||||
void cache();
|
||||
int posCache {0};
|
||||
|
||||
void writeStatistic();
|
||||
int posDayStat {0};
|
||||
|
||||
@@ -138,6 +144,7 @@ public:
|
||||
bool isConnected {false};
|
||||
|
||||
int workMode {}; // 运行模式
|
||||
int workModeGateway { -1 }; // 运行模式
|
||||
int runPolicyId {}; // 运行策略
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -158,23 +165,19 @@ public:
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// === 日统计 ===
|
||||
double storageIn {}; // 储能充电电量
|
||||
double storageOut {}; // 储能放电电量
|
||||
|
||||
int storageNumIn {}; // 储能充电次数
|
||||
int storageNumOut {}; // 储能放电次数
|
||||
int storageNumErr {}; // 储能故障次数
|
||||
|
||||
double solarGen {}; // 光伏发电电量
|
||||
double solarGrid {}; // 光伏入网电量
|
||||
int solarNumErr {}; // 光伏故障次数
|
||||
|
||||
double chargeElect {}; // 充电设备充电电量
|
||||
int chargeNum {}; // 充电设备充电次数
|
||||
int chargeNumErr {}; // 充电设备故障次数
|
||||
|
||||
double incomeElect {}; // 发电收益金额
|
||||
double incomeCharge {}; // 充电收益金额
|
||||
//double storageIn {}; // 储能充电电量
|
||||
//double storageOut {}; // 储能放电电量
|
||||
//int storageNumIn {}; // 储能充电次数
|
||||
//int storageNumOut {}; // 储能放电次数
|
||||
//int storageNumErr {}; // 储能故障次数
|
||||
//double solarGen {}; // 光伏发电电量
|
||||
//double solarGrid {}; // 光伏入网电量
|
||||
//int solarNumErr {}; // 光伏故障次数
|
||||
//double chargeElect {}; // 充电设备充电电量
|
||||
//int chargeNum {}; // 充电设备充电次数
|
||||
//int chargeNumErr {}; // 充电设备故障次数
|
||||
//double incomeElect {}; // 发电收益金额
|
||||
//double incomeCharge {}; // 充电收益金额
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// === 环境 ===
|
||||
@@ -200,7 +203,6 @@ public:
|
||||
std::unordered_map<int, std::shared_ptr<Device>> mapDevice;
|
||||
std::map<int, std::map<std::string, std::shared_ptr<Device>>> mapDeviceGroup;
|
||||
|
||||
|
||||
// 温湿度信息
|
||||
std::map<int, TempHumUnit> mapTempHumUnit;
|
||||
// 消防4.0信息
|
||||
@@ -269,4 +271,14 @@ public:
|
||||
int emuStatus {-1};
|
||||
|
||||
std::map<std::string, int64_t> mapAlertCache;
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/// 说明:从电表中读取对应数据:每间隔600秒(10分钟)缓存一个点位,存储到数据库,用于绘制一天的电曲线
|
||||
// 储能充电量缓存,key:位置索引(0->144),val:电量
|
||||
std::map<int, float> mapCacheElectIn;
|
||||
// 储能放电量缓存,key:位置索引(0->144),val:电量
|
||||
std::map<int, float> mapCacheElectOut;
|
||||
// 充电桩充电量缓存,key:位置索引(0->144),val:电量
|
||||
std::map<int, float> mapCacheElectCharger;
|
||||
};
|
||||
@@ -64,15 +64,6 @@ std::string JSON::readStr(njson& json, std::string k)
|
||||
return v;
|
||||
}
|
||||
|
||||
void JSON::parse(std::string jsonstr, std::vector<std::string>& vd)
|
||||
{
|
||||
njson jsonroot;
|
||||
if (JSON::parse(jsonstr, jsonroot))
|
||||
{
|
||||
vd = jsonroot.get<std::vector<std::string>>();
|
||||
}
|
||||
}
|
||||
|
||||
std::string JSON::toStr(njson& json)
|
||||
{
|
||||
std::string v;
|
||||
@@ -92,4 +83,6 @@ std::string JSON::toStr(njson& json)
|
||||
break;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -90,10 +90,19 @@ public:
|
||||
}
|
||||
|
||||
static std::string readStr(njson& json, std::string k);
|
||||
|
||||
static void parse(std::string jsonstr, std::vector<std::string>& vd);
|
||||
|
||||
|
||||
static std::string toStr(njson& json);
|
||||
|
||||
template <typename T>
|
||||
static void parseArray(std::string jsonstr, std::vector<T>& vd)
|
||||
{
|
||||
njson json;
|
||||
if (JSON::parse(jsonstr, json))
|
||||
{
|
||||
try { vd = json.get<std::vector<T>>(); }
|
||||
catch (const nlohmann::detail::exception& e) { Spdlogger::info("JSON read error: err={}, json={}", e.what(), json.dump()); }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -400,6 +400,24 @@ string Utils::timeStrMS(std::string fmt /*= "%Y-%m-%dT%H:%M:%S"*/)
|
||||
// return true;
|
||||
//}
|
||||
|
||||
int64_t Utils::timeDaySeconds()
|
||||
{
|
||||
// 获取当前时间
|
||||
auto tpNow = chrono::system_clock::now();
|
||||
// 转换为time_t以便使用C库函数
|
||||
std::time_t tsNow = std::chrono::system_clock::to_time_t(tpNow);
|
||||
std::tm* tmLocal = std::localtime(&tsNow);
|
||||
|
||||
// 设置为今日0点
|
||||
tmLocal->tm_hour = 0;
|
||||
tmLocal->tm_min = 0;
|
||||
tmLocal->tm_sec = 0;
|
||||
|
||||
// 转换回时间戳
|
||||
std::time_t tsDay = std::mktime(tmLocal);
|
||||
return tsNow - tsDay;
|
||||
}
|
||||
|
||||
void Utils::sleep_ms(int ms)
|
||||
{
|
||||
// 计算时间间隔:
|
||||
|
||||
@@ -60,6 +60,8 @@ public:
|
||||
//static string time_to_string(int64_t dt, std::string fmt="%Y-%m-%d %H:%M:%S");
|
||||
//static bool time_string_to_tm(string dt, std::tm& t);
|
||||
|
||||
static int64_t timeDaySeconds();
|
||||
|
||||
std::string duration_str(int64_t t)
|
||||
{
|
||||
auto h = t / 3600;
|
||||
|
||||
@@ -440,8 +440,8 @@ Errcode DAO::queryDeviceList(std::shared_ptr<DaoEntity> dao, vector<Fields>& res
|
||||
// 分页查询设备信息列表
|
||||
Errcode DAO::queryDeviceList(PageInfo& pageInfo, vector<Fields>& result)
|
||||
{
|
||||
std::string sqlFrom = "FROM " + DMDevice::TABLENAME;
|
||||
return QueryPagination("*", sqlFrom, pageInfo, result);
|
||||
std::string sqlFrom = "FROM device d LEFT JOIN station s ON d.station_id =s.station_id";
|
||||
return QueryPagination("d.*, s.name station_name", sqlFrom, pageInfo, result);
|
||||
}
|
||||
|
||||
// 查询设备类型定义
|
||||
@@ -637,7 +637,7 @@ Errcode DAO::queryPolicyTypeDef(std::shared_ptr<DaoEntity> dao, vector<Fields>&
|
||||
return DAO::exec(dao, sql, result);
|
||||
}
|
||||
|
||||
Errcode DAO::insertStatStation(std::shared_ptr<DaoEntity> dao, Fields& fields)
|
||||
Errcode DAO::insertStatDay(std::shared_ptr<DaoEntity> dao, Fields& fields)
|
||||
{
|
||||
// 根据主键(dt、station_id、category),写入或更新数据
|
||||
if (!dao) { dao = DaoEntity::create("stat_day"); }
|
||||
@@ -686,10 +686,18 @@ Errcode DAO::queryRuntimeData(std::shared_ptr<DaoEntity> dao, std::string dt, in
|
||||
return Errcode(ret);
|
||||
}
|
||||
|
||||
Errcode DAO::queryRuntimeData(std::shared_ptr<DaoEntity> dao, std::string dt, vector<Fields>& result)
|
||||
Errcode DAO::queryRuntimeDataHistory(std::shared_ptr<DaoEntity> dao, std::string dt, vector<Fields>& result)
|
||||
{
|
||||
if (!dao) { dao = DaoEntity::create("history_day"); }
|
||||
if (!dao) { dao = DaoEntity::create(""); }
|
||||
std::string sql = "SELECT * FROM history_day WHERE dt='" + dt + "';";
|
||||
int ret = dao->exec(sql, result);
|
||||
return Errcode(ret);
|
||||
}
|
||||
|
||||
Errcode DAO::queryPredictHistory(std::shared_ptr<DaoEntity> dao, std::string dt, vector<Fields>& result)
|
||||
{
|
||||
if (!dao) { dao = DaoEntity::create(""); }
|
||||
std::string sql = "SELECT * FROM predict_day WHERE dt='" + dt + "';";
|
||||
int ret = dao->exec(sql, result);
|
||||
return Errcode(ret);
|
||||
}
|
||||
@@ -122,12 +122,14 @@ public:
|
||||
static Errcode queryPolicyTypeDef(std::shared_ptr<DaoEntity> dao, vector<Fields>& result);
|
||||
|
||||
|
||||
static Errcode insertStatStation(std::shared_ptr<DaoEntity> dao, Fields& fields);
|
||||
static Errcode insertStatDay(std::shared_ptr<DaoEntity> dao, Fields& fields);
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// === 设备历史监测数据管理 ===
|
||||
static Errcode insertRuntimeData(std::shared_ptr<DaoEntity> dao, Fields& fields);
|
||||
static Errcode queryRuntimeData(std::shared_ptr<DaoEntity> dao, std::string dt, int stationId, int deviceId, vector<Fields>& result);
|
||||
static Errcode queryRuntimeData(std::shared_ptr<DaoEntity> dao, std::string dt, vector<Fields>& result);
|
||||
static Errcode queryRuntimeDataHistory(std::shared_ptr<DaoEntity> dao, std::string dt, vector<Fields>& result);
|
||||
|
||||
static Errcode queryPredictHistory(std::shared_ptr<DaoEntity> dao, std::string dt, vector<Fields>& result);
|
||||
};
|
||||
@@ -101,6 +101,7 @@ int MysqlClient::exec(std::string sql)
|
||||
|
||||
int MysqlClient::exec(std::string sql, vector<Fields>& result)
|
||||
{
|
||||
if (!mysql_) { return 1; }
|
||||
result.clear();
|
||||
int err = MysqlQuery(mysql_, sql);
|
||||
if (err != 0)
|
||||
|
||||
@@ -657,6 +657,16 @@ Errcode HttpEntity::updateStation(const httplib::Request& req, njson& json, std:
|
||||
}
|
||||
}
|
||||
}
|
||||
if (params.contains("station_id") && params.contains("work_mode"))
|
||||
{
|
||||
int stationId = params.get<int>("station_id");
|
||||
int workmode = params.get<int>("work_mode");
|
||||
auto station = Application::data().getStation(stationId);
|
||||
if (station)
|
||||
{
|
||||
station->setGarewayWorkMode();
|
||||
}
|
||||
}
|
||||
return err;
|
||||
};
|
||||
|
||||
@@ -702,6 +712,9 @@ Errcode HttpEntity::queryStationOverview(const httplib::Request& req, njson& jso
|
||||
return Errcode(ret);
|
||||
}
|
||||
njson jsonStorage = njson::parse(R"({"category":1, "gateway":0, "count":0, "power":0.0})");
|
||||
auto station = Application::data().getStation(Utils::toInt(stationId));
|
||||
jsonStorage["workmode"] = station ? station->getGatewayMode() : "--";
|
||||
|
||||
njson jsonCharge = njson::parse(R"({"category":2, "count":0, "power":0.0})");
|
||||
njson jsonSolar = njson::parse(R"({"category":3, "count":0, "power":0.0})");
|
||||
njson jsonSecurity = njson::parse(R"({"category":4, "count":0, "power":0.0})");
|
||||
@@ -1252,6 +1265,13 @@ Errcode HttpEntity::queryStatDayList(const httplib::Request& req, njson& json, s
|
||||
|
||||
if (!startDate.empty() && !endDate.empty())
|
||||
{
|
||||
std::map<std::string, Fields*> mapTemp;
|
||||
for (auto& item: result)
|
||||
{
|
||||
auto& dt = item.value("dt");
|
||||
mapTemp[dt] = &item;
|
||||
}
|
||||
|
||||
int64_t t0 = Utils::time(startDate + " 00:00:00");
|
||||
int64_t t1 = Utils::time(endDate + " 00:00:00");
|
||||
int i = 0;
|
||||
@@ -1262,11 +1282,9 @@ Errcode HttpEntity::queryStatDayList(const httplib::Request& req, njson& json, s
|
||||
njson jsonrow;
|
||||
std::string dt = Utils::dateStr(t);
|
||||
Fields* fields = NULL;
|
||||
if (i<result.size() && result[i].value("dt") == dt)
|
||||
{
|
||||
fields = &(result[i]);
|
||||
i++;
|
||||
}
|
||||
auto iter = mapTemp.find(dt);
|
||||
if (iter != mapTemp.end()) { fields = iter->second; }
|
||||
|
||||
jsonrow["dt"] = dt.substr(5);
|
||||
jsonrow["storage_elect_in"] = fields ? fields->value("storage_elect_in") : "0";
|
||||
jsonrow["storage_elect_out"] = fields ? fields->value("storage_elect_out") : "0";
|
||||
|
||||
@@ -25,8 +25,7 @@ bool MqttClient::load(std::string filename)
|
||||
std::map<std::string, TopicInfo> MqttClient::s_mapTopicInfo;
|
||||
|
||||
int MqttClient::init(string addr, string clientId, string username, string password)
|
||||
{
|
||||
if (addr.empty())
|
||||
{ if (addr.empty())
|
||||
{
|
||||
return MQTTASYNC_FAILURE;
|
||||
}
|
||||
@@ -43,7 +42,7 @@ int MqttClient::init(string addr, string clientId, string username, string passw
|
||||
int rc {0};
|
||||
|
||||
// "tcp://localhost:1883"
|
||||
std::string str = "ESS-" + std::to_string(Utils::random(1000, 9999)) + "-" +clientId;
|
||||
std::string str = "ESS-" + clientId + "-" + std::to_string(Utils::time());
|
||||
rc = MQTTAsync_create(&client, addr.c_str(), str.c_str(), MQTTCLIENT_PERSISTENCE_NONE, NULL);
|
||||
if (rc != MQTTASYNC_SUCCESS)
|
||||
{
|
||||
@@ -303,7 +302,10 @@ 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<int>()); }
|
||||
if (key == "40001")
|
||||
{
|
||||
station->readGatewayMode(val.get<int>());
|
||||
}
|
||||
//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(), "其他参数"); }
|
||||
@@ -314,7 +316,7 @@ int MqttClient::onMessageArrived(char* topic, int topicLen, MQTTAsync_message* m
|
||||
int cdzStatus = -1;
|
||||
int emuStatus = -1;
|
||||
JSON::read(json, "cdz", cdzStatus);
|
||||
JSON::read(json, "emu", cdzStatus);
|
||||
JSON::read(json, "emu", emuStatus);
|
||||
station->readGatewayStatus(cdzStatus, emuStatus);
|
||||
}
|
||||
else
|
||||
@@ -391,7 +393,11 @@ 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 (command == "BCU_YC" || command == "BCU_YX")
|
||||
{
|
||||
//spdlog::info("[mqtt] read [{}]={}, {}{}", addr, val, regUnit.name, regUnit.remark);
|
||||
}
|
||||
|
||||
if (regUnit.alert && val > 0)
|
||||
{
|
||||
station->readAlert(device, addr, val, "[" + command + "]" + regUnit.name + "(" + addr + ")");
|
||||
|
||||
@@ -1,35 +1,16 @@
|
||||
#include "MainApp.h"
|
||||
#include "common/Spdlogger.h"
|
||||
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtWidgets/QComboBox>
|
||||
#include <QtWidgets/QGridLayout>
|
||||
#include <QtWidgets/QLabel>
|
||||
#include <QtWidgets/QLineEdit>
|
||||
#include <QtWidgets/QPushButton>
|
||||
#include <QtWidgets/QWidget>
|
||||
#include <QPalette>
|
||||
#include <QGroupBox>
|
||||
#include <QTableWidget>
|
||||
#include <QHeaderView>
|
||||
|
||||
#include "app/Config.h"
|
||||
#include "common/Utils.h"
|
||||
#include "QUI.h"
|
||||
#include "MyQUI.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);}";
|
||||
|
||||
static const std::string QSS_LINE =
|
||||
"QLineEdit { background-color: rgb(14, 49, 66); color: #ffffff; border: 1px solid gray; border-radius: 5px; font: bold 13px; }";
|
||||
|
||||
MainApp::MainApp()
|
||||
{
|
||||
@@ -101,168 +82,8 @@ public:
|
||||
QGridLayout layout;
|
||||
};
|
||||
|
||||
void PairLine(QWidget* parent, int x, int y, string k, string v)
|
||||
{
|
||||
auto key = new QLabel(parent);
|
||||
key->setText(k.c_str());
|
||||
key->setGeometry(x, y, 80, 26);
|
||||
auto value = new QLineEdit(parent);
|
||||
value->setText(v.c_str());
|
||||
value->setGeometry(x+80, y, 260, 26);
|
||||
value->setStyleSheet(QSS_LINE.c_str());
|
||||
value->setReadOnly(true);
|
||||
}
|
||||
|
||||
|
||||
class MyWorkspace : public QWidget
|
||||
{
|
||||
public:
|
||||
MyWorkspace(QWidget* parent) : QWidget(parent)
|
||||
{
|
||||
this->setObjectName("workspace");
|
||||
this->setStyleSheet("#workspace { background-color:rgba(100,100,100,50); }");
|
||||
|
||||
int x=10, y=10;
|
||||
{
|
||||
this->groupSys = UI::GroupBox(this, x, y, 1190, 120, "系统");
|
||||
auto pw = groupSys.get();
|
||||
}
|
||||
{
|
||||
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 += 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);
|
||||
|
||||
|
||||
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<QTableWidget>(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<QTextEdit>(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<spdlog::sinks::qt_sink_mt>(textLog.get(), "append");
|
||||
spdlog::default_logger()->sinks().push_back(qtSink);
|
||||
}
|
||||
{
|
||||
//qtSink = std::make_shared<QtTextSink>(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<QtTextSink> qtSink;
|
||||
|
||||
std::shared_ptr<QGroupBox> groupSys;
|
||||
std::shared_ptr<QGroupBox> groupHttp;
|
||||
std::shared_ptr<QGroupBox> groupMqtt;
|
||||
std::shared_ptr<QGroupBox> groupDB;
|
||||
std::shared_ptr<QTableWidget> table {};
|
||||
std::shared_ptr<QTextEdit> textLog;
|
||||
};
|
||||
|
||||
void MainApp::setMyLayout()
|
||||
{
|
||||
@@ -284,9 +105,9 @@ void MainApp::setMyLayout()
|
||||
menu->setSizePolicy(sizePolicy);
|
||||
layout.main->addWidget(menu, 0, 0, 1, 1);
|
||||
|
||||
ui.workspace = std::make_shared<MyWorkspace>(this);
|
||||
ui.workspace->setSizePolicy(sizePolicy);
|
||||
layout.main->addWidget(ui.workspace.get(), 0, 1, 1, 1);
|
||||
ui.wigetHome = std::make_shared<QWHome>(this);
|
||||
ui.wigetHome->setSizePolicy(sizePolicy);
|
||||
layout.main->addWidget(ui.wigetHome.get(), 0, 1, 1, 1);
|
||||
|
||||
// 设置列宽和行高
|
||||
layout.main->setColumnMinimumWidth(0, 200); // 设置第0列的最小宽度为100像素
|
||||
@@ -303,5 +124,5 @@ void MainApp::setMyLayout()
|
||||
|
||||
void MainApp::onTimer()
|
||||
{
|
||||
ui.workspace->onTimer();
|
||||
ui.wigetHome->onTimer();
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
#include <QMainWindow>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QHBoxLayout>
|
||||
#include <QTableWidget>
|
||||
|
||||
#include <QTimer>
|
||||
#include <QTextEdit>
|
||||
|
||||
#include <string>
|
||||
using namespace std;
|
||||
|
||||
@@ -14,73 +13,8 @@ using namespace std;
|
||||
#include <QTextEdit>
|
||||
#include <mutex>
|
||||
|
||||
class QtTextSink : public QObject, public spdlog::sinks::base_sink<std::mutex>
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
// 构造函数接收一个 QTextEdit*,但用 QPointer 来包装它
|
||||
explicit QtTextSink(QTextEdit* textEdit, QObject* parent = nullptr)
|
||||
: QObject(parent), textEdit(textEdit) {
|
||||
}
|
||||
#include "widgets/QWHome.h"
|
||||
|
||||
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<QTextEdit> textEdit; // 关键:使用 QPointer
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
class LabelPair
|
||||
{
|
||||
public:
|
||||
LabelPair(QWidget* parent, int x, int y, int w, int h)
|
||||
{
|
||||
title.setParent(parent);
|
||||
value.setParent(&title);
|
||||
|
||||
title.setGeometry(x, y, w, h);
|
||||
value.setGeometry(80, 0, w-80, h);
|
||||
|
||||
title.show();
|
||||
value.show();
|
||||
}
|
||||
|
||||
void setTitle(std::string text)
|
||||
{
|
||||
title.setText(text.c_str());
|
||||
}
|
||||
|
||||
void setValue(std::string text)
|
||||
{
|
||||
value.setText(text.c_str());
|
||||
}
|
||||
|
||||
QLabel title;
|
||||
QLabel value;
|
||||
};
|
||||
|
||||
|
||||
class MyWorkspace;
|
||||
|
||||
class MainApp : public QWidget
|
||||
{
|
||||
@@ -97,7 +31,7 @@ private slots:
|
||||
public:
|
||||
struct {
|
||||
std::shared_ptr<LabelPair> weburl {};
|
||||
std::shared_ptr<MyWorkspace> workspace;
|
||||
std::shared_ptr<MyWidget> wigetHome;
|
||||
} ui;
|
||||
|
||||
struct {
|
||||
|
||||
35
src/qt/MyQUI.cpp
Normal file
35
src/qt/MyQUI.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
#include "MyQUI.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; }";
|
||||
|
||||
|
||||
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);}";
|
||||
|
||||
static const std::string QSS_LINE =
|
||||
"QLineEdit { background-color: rgb(14, 49, 66); color: #ffffff; border: 1px solid gray; border-radius: 5px; font: bold 13px; }";
|
||||
|
||||
std::shared_ptr<QGroupBox> MyQUI::GroupBox(QWidget* parent, int x, int y, int w, int h, std::string title)
|
||||
{
|
||||
auto groupBox = std::make_shared<QGroupBox>(title.c_str(), parent);
|
||||
groupBox->setGeometry(x, y, w, h);
|
||||
groupBox->setStyleSheet(QSS_GROUP.c_str());
|
||||
return groupBox;
|
||||
}
|
||||
|
||||
void MyQUI::PairLine(QWidget* parent, int x, int y, string k, string v)
|
||||
{
|
||||
auto key = new QLabel(parent);
|
||||
key->setText(k.c_str());
|
||||
key->setGeometry(x, y, 80, 26);
|
||||
auto value = new QLineEdit(parent);
|
||||
value->setText(v.c_str());
|
||||
value->setGeometry(x+80, y, 260, 26);
|
||||
value->setStyleSheet(QSS_LINE.c_str());
|
||||
value->setReadOnly(true);
|
||||
}
|
||||
76
src/qt/MyQUI.h
Normal file
76
src/qt/MyQUI.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <QGroupBox>
|
||||
#include <QMainWindow>
|
||||
#include <QtWebEngineWidgets/QWebEngineView>
|
||||
#include <QSplashScreen>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QHBoxLayout>
|
||||
#include <QTextEdit>
|
||||
#include <QtCore/QVariant>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtWidgets/QComboBox>
|
||||
#include <QtWidgets/QGridLayout>
|
||||
#include <QtWidgets/QLabel>
|
||||
#include <QtWidgets/QLineEdit>
|
||||
#include <QtWidgets/QPushButton>
|
||||
#include <QtWidgets/QWidget>
|
||||
#include <QPalette>
|
||||
#include <QGroupBox>
|
||||
#include <QTableWidget>
|
||||
#include <QHeaderView>
|
||||
|
||||
|
||||
#include <string>
|
||||
using namespace std;
|
||||
|
||||
extern const std::string QSS_GROUP;
|
||||
extern const std::string QSS_BTN_MENU;
|
||||
extern const std::string QSS_LINE;
|
||||
|
||||
class LabelPair
|
||||
{
|
||||
public:
|
||||
LabelPair(QWidget* parent, int x, int y, int w, int h)
|
||||
{
|
||||
title.setParent(parent);
|
||||
value.setParent(&title);
|
||||
title.setGeometry(x, y, w, h);
|
||||
value.setGeometry(80, 0, w-80, h);
|
||||
title.show();
|
||||
value.show();
|
||||
}
|
||||
|
||||
void setTitle(std::string text)
|
||||
{
|
||||
title.setText(text.c_str());
|
||||
}
|
||||
|
||||
void setValue(std::string text)
|
||||
{
|
||||
value.setText(text.c_str());
|
||||
}
|
||||
|
||||
QLabel title;
|
||||
QLabel value;
|
||||
};
|
||||
|
||||
class MyWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
MyWidget(QWidget* parent) : QWidget(parent) {}
|
||||
|
||||
virtual void onTimer() {};
|
||||
};
|
||||
|
||||
|
||||
class MyQUI
|
||||
{
|
||||
public:
|
||||
static std::shared_ptr<QGroupBox> GroupBox(QWidget* parent, int x, int y, int w, int h, std::string title);
|
||||
|
||||
static void PairLine(QWidget* parent, int x, int y, string k, string v);
|
||||
|
||||
};
|
||||
@@ -1,14 +0,0 @@
|
||||
#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<QGroupBox> UI::GroupBox(QWidget* parent, int x, int y, int w, int h, std::string title)
|
||||
{
|
||||
auto groupBox = std::make_shared<QGroupBox>(title.c_str(), parent);
|
||||
groupBox->setGeometry(x, y, w, h);
|
||||
groupBox->setStyleSheet(QSS_GROUP.c_str());
|
||||
return groupBox;
|
||||
}
|
||||
10
src/qt/QUI.h
10
src/qt/QUI.h
@@ -1,10 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QGroupBox>
|
||||
|
||||
|
||||
class UI
|
||||
{
|
||||
public:
|
||||
static std::shared_ptr<QGroupBox> GroupBox(QWidget* parent, int x, int y, int w, int h, std::string title);
|
||||
};
|
||||
161
src/qt/widgets/QWHome.cpp
Normal file
161
src/qt/widgets/QWHome.cpp
Normal file
@@ -0,0 +1,161 @@
|
||||
#include "QWHome.h"
|
||||
#include "common/Spdlogger.h"
|
||||
#include "app/Config.h"
|
||||
#include "common/Utils.h"
|
||||
#include "app/Application.h"
|
||||
#include "app/AppData.h"
|
||||
#include "app/Station.h"
|
||||
#include "protocol/MqttEntity.h"
|
||||
|
||||
QWHome::QWHome(QWidget* parent) : MyWidget(parent)
|
||||
{
|
||||
this->setObjectName("workspace");
|
||||
this->setStyleSheet("#workspace { background-color:rgba(100,100,100,50); }");
|
||||
|
||||
int x = 10, y = 10;
|
||||
{
|
||||
this->groupSys = MyQUI::GroupBox(this, x, y, 1190, 120, "系统");
|
||||
auto pw = groupSys.get();
|
||||
}
|
||||
{
|
||||
x = 10, y += 130;
|
||||
this->groupHttp = MyQUI::GroupBox(this, x, y, 390, 120, "HTTP");
|
||||
auto pw = groupHttp.get();
|
||||
MyQUI::PairLine(pw, 20, 20, "服务类型: ", "服务端");
|
||||
MyQUI::PairLine(pw, 20, 50, "服务端口: ", Utils::toStr(Config::option.http.port));
|
||||
MyQUI::PairLine(pw, 20, 80, "服务状态: ", "运行");
|
||||
}
|
||||
{
|
||||
x += 400;
|
||||
this->groupMqtt = MyQUI::GroupBox(this, x, y, 390, 120, "MQTT");
|
||||
auto pw = groupMqtt.get();
|
||||
MyQUI::PairLine(pw, 20, 20, "服务类型: ", "客户端");
|
||||
MyQUI::PairLine(pw, 20, 50, "服务地址: ", Config::option.mqtt.host);
|
||||
MyQUI::PairLine(pw, 20, 80, "服务状态: ", "---");
|
||||
}
|
||||
{
|
||||
x += 400;
|
||||
this->groupDB = MyQUI::GroupBox(this, x, y, 390, 120, "数据库");
|
||||
auto pw = groupDB.get();
|
||||
MyQUI::PairLine(pw, 20, 20, "数据库名: ", Config::option.database.dbname);
|
||||
MyQUI::PairLine(pw, 20, 50, "主机地址: ", Config::option.database.host);
|
||||
MyQUI::PairLine(pw, 20, 80, "用 户 名: ", Config::option.database.user);
|
||||
|
||||
|
||||
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<QTableWidget>(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); // 设置为整行选中
|
||||
table->horizontalHeader()->setFixedHeight(50);
|
||||
table->horizontalHeader()->setDefaultSectionSize(60);
|
||||
|
||||
QTableWidgetItem* headerItem;
|
||||
QStringList headerText_Row, headerText_Col;
|
||||
headerText_Row << "ID" << "站名" << "编号" << "状态" << "MQTT状态" << "召测(秒)"
|
||||
<< "日充电\n电量" << "日放电\n电量" << "总充电\n电量" << "总放电\n电量"
|
||||
<< "日充电\n费用" << "日放电\n费用" << "总充电\n费用" << "总放电\n费用"
|
||||
<< "日收益" << "总收益" << "--";
|
||||
|
||||
// 设置为水平表头
|
||||
table->setColumnCount(headerText_Row.size());
|
||||
table->setHorizontalHeaderLabels(headerText_Row);
|
||||
table->setColumnWidth(0, 50);
|
||||
table->setColumnWidth(1, 120);
|
||||
table->setColumnWidth(2, 50);
|
||||
table->setColumnWidth(4, 80);
|
||||
|
||||
}
|
||||
|
||||
textLog = std::make_shared<QTextEdit>(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<spdlog::sinks::qt_sink_mt>(textLog.get(), "append");
|
||||
spdlog::default_logger()->sinks().push_back(qtSink);
|
||||
}
|
||||
}
|
||||
|
||||
void QWHome::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 QWHome::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, 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) : "--");
|
||||
|
||||
setTableCell(rowNo, 6, Utils::toStr(station->statData.dayElectIn, 0));
|
||||
setTableCell(rowNo, 7, Utils::toStr(station->statData.dayElectOut, 0));
|
||||
setTableCell(rowNo, 8, Utils::toStr(station->statData.totalElectIn, 0));
|
||||
setTableCell(rowNo, 9, Utils::toStr(station->statData.totalElectOut, 0));
|
||||
setTableCell(rowNo, 10, Utils::toStr(station->statData.dayFeeIn, 0));
|
||||
setTableCell(rowNo, 11, Utils::toStr(station->statData.dayFeeOut, 0));
|
||||
setTableCell(rowNo, 12, Utils::toStr(station->statData.totalFeeIn, 0));
|
||||
setTableCell(rowNo, 13, Utils::toStr(station->statData.totalFeeOut, 0));
|
||||
setTableCell(rowNo, 14, Utils::toStr(station->statData.dayIncome, 0));
|
||||
setTableCell(rowNo, 15, Utils::toStr(station->statData.totalIncome, 0));
|
||||
setTableCell(rowNo, 16, station->statData.ts > 0 ? Utils::timeStr(station->statData.ts) : "--");
|
||||
|
||||
rowNo++;
|
||||
}
|
||||
}
|
||||
28
src/qt/widgets/QWHome.h
Normal file
28
src/qt/widgets/QWHome.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "qt/MyQUI.h"
|
||||
#include <spdlog/sinks/base_sink.h>
|
||||
#include <QHBoxLayout>
|
||||
#include <QTableWidget>
|
||||
|
||||
|
||||
class QWHome : public MyWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
QWHome(QWidget* parent);
|
||||
|
||||
|
||||
// 创建自定义sink
|
||||
//std::shared_ptr<QtTextSink> qtSink;
|
||||
|
||||
std::shared_ptr<QGroupBox> groupSys;
|
||||
std::shared_ptr<QGroupBox> groupHttp;
|
||||
std::shared_ptr<QGroupBox> groupMqtt;
|
||||
std::shared_ptr<QGroupBox> groupDB;
|
||||
std::shared_ptr<QTableWidget> table {};
|
||||
std::shared_ptr<QTextEdit> textLog;
|
||||
|
||||
void onTimer() override;
|
||||
void setTableCell(int row, int col, std::string text, std::string style = "");
|
||||
};
|
||||
0
src/qt/widgets/QWMonitor.cpp
Normal file
0
src/qt/widgets/QWMonitor.cpp
Normal file
0
src/qt/widgets/QWMonitor.h
Normal file
0
src/qt/widgets/QWMonitor.h
Normal file
Reference in New Issue
Block a user