新增http、mqtt运行库,实现mqtt功能, 新增spdlog

This commit is contained in:
lixiaoyuan
2025-09-01 20:08:40 +08:00
parent e0b64a20c4
commit 94e467b65e
245 changed files with 54182 additions and 117 deletions

View File

@@ -67,6 +67,8 @@ include_directories(
${THIRDPARTY_PATH}/mysql/include
${THIRDPARTY_PATH}/nlohmann_json-3.11.2
${THIRDPARTY_PATH}/cpp-httplib-0.25.0
${THIRDPARTY_PATH}/paho_mqtt/include
${THIRDPARTY_PATH}/spdlog-1.13.0/include
${PVLIBS_PATH}/include/pvserver
${PVLIBS_PATH}/include/rllib
)
@@ -98,7 +100,9 @@ target_link_libraries(${PROJECT_NAME}
target_link_libraries(${PROJECT_NAME}
ws2_32 iphlpapi
${THIRDPARTY_PATH}/mysql/lib/x64/libmysql.lib
${THIRDPARTY_PATH}/paho_mqtt/lib/paho-mqtt3a.lib
${THIRDPARTY_PATH}/paho_mqtt/lib/paho-mqtt3c.lib
${PVLIBS_PATH}/x64/serverlib.lib
${PVLIBS_PATH}/x64/rllib.lib
)
)

View File

@@ -6,6 +6,7 @@
#include "database/Dao.h"
#include "common/JsonN.h"
#include "common/Snowflake.h"
#include "common/Spdlogger.h"
void ElectPeriod::parse(std::string jsonstr)
@@ -39,7 +40,7 @@ void AppData::initFromDB()
auto dao = DaoEntity::create("");
if (!dao->isConnected())
{
XLOGE() << "Database connected error.";
spdlog::info("Database connected error.");
return;
}
@@ -57,7 +58,7 @@ void AppData::initFromDB()
this->mapWorkMode[workModeId] = name;
str += ("工作模式: {" + std::to_string(workModeId)+":" + name + "},");
}
XLOGD() << str;
spdlog::info(str);
}
{ // 数据库读取策略类型定义
str = "", result.clear();
@@ -70,7 +71,7 @@ void AppData::initFromDB()
this->mapPolicyType[policyTypeId] = name;
str += ("策略类型: {" + std::to_string(policyTypeId) + ":" + name + "},");
}
XLOGD() << str;
spdlog::info(str);
}
{ // 数据库读取设备类型定义
str = "", result.clear();
@@ -87,7 +88,7 @@ void AppData::initFromDB()
mapping.deviceType.push_back({std::to_string(item->typeId), item->name});
str += ("设备类型: {" + std::to_string(item->typeId) + ":" + item->name + "},");
}
XLOGD() << str;
spdlog::info(str);
}
{ // 数据库读取角色定义
str = "", result.clear();
@@ -103,7 +104,7 @@ void AppData::initFromDB()
mapping.role.push_back({std::to_string(item->roleId), item->name});
str += ("角色: {" + std::to_string(item->roleId) + ":" + item->name + "},");
}
XLOGD() << str;
spdlog::info(str);
}
{ // 数据库读取场站信息
str = "", result.clear();
@@ -116,7 +117,7 @@ void AppData::initFromDB()
mapping.stationName.push_back({std::to_string(station->id), station->name});
str += ("场站: {" + std::to_string(station->id) + ":" + station->name + "},");
}
XLOGD() << str;
spdlog::info(str);
}
{ // 数据库读取设备信息
str = "", result.clear();
@@ -135,7 +136,7 @@ void AppData::initFromDB()
}
else
{
XLOGE() << "init device error: unknown station_id:[" << stationId << "] device_id=" << deviceId;
spdlog::error("init device error: unknown station_id:, device_id=", stationId, deviceId);
}
}
}
@@ -207,7 +208,7 @@ void AppData::initFromDB()
}
else
{
XLOGE() << "init staticis data error: unknown station_id:[" << stationId << "] dt=" << dt;
spdlog::error("init staticis data error: unknown station_id:{}, dt={}", stationId, dt);
}
}
}
@@ -227,6 +228,10 @@ std::shared_ptr<Station> AppData::getStation(int stationId)
}
return nullptr;
}
int AppData::getStationCount()
{
return mapStation.size();
}
std::shared_ptr<Station> AppData::getStationByName(std::string name)
{

View File

@@ -7,6 +7,7 @@
#include <unordered_map>
#include "common/Fields.h"
#include "app/Config.h"
#include "common/Spdlogger.h"
class Station;
class Device;
@@ -66,6 +67,7 @@ public:
User getUser(std::string token);
std::shared_ptr<Station> getStation(int stationId);
int getStationCount();
std::shared_ptr<Station> getStationByName(std::string name);

View File

@@ -7,8 +7,9 @@
#include "database/Dao.h"
#include "app/Station.h"
#include "app/Device.h"
#include "protocol/HttpEntity.h"
#include "common/Spdlogger.h"
#include "protocol/MqttEntity.h"
void Application::init()
{
@@ -21,41 +22,77 @@ void Application::init()
Config::option.database.user,
Config::option.database.passwd,
Config::option.database.dbname);
XLOGI() << "[APP] set database option: host=" << Config::option.database.host
<< ", port=" << Config::option.database.port
<< ", user=" << Config::option.database.user
<< ", dbname=" << Config::option.database.dbname;
spdlog::info("[app] set database option: host={}, port={}, user={}, dbname={}",
Config::option.database.host,
Config::option.database.port,
Config::option.database.user,
Config::option.database.dbname);
// 连接数据库,读取基础信息
// 初始化系统基础数据
appdata_.init();
appdata.init();
// 创建设备处理线程
std::thread([=]() { runThreadDevice(); }).detach();
// 创建HTTP服务线程
std::thread([=]() {
while (!isQuit) {
MqttClient mqttCli;
mqttCli.init("tcp://localhost:1883", "AAAAAAAAA", "", "", {"topic/test"}); // 不阻塞
HttpEntity http;
http.listen("0.0.0.0", Config::option.http.port); // 阻塞
}
}).detach();
// 创建主业务循环线程
std::thread([=]() { runThreadMain(); }).detach();
}
void Application::runThreadMain()
void Application::runThreadDevice()
{
while (!isQuit())
while (!isQuit)
{
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
void Application::runThreadDevice()
void Application::runThreadMain()
{
while (!isQuit())
{
std::string addr = "tcp://localhost:1883";
mqttCli = std::make_shared<MqttClient>();
mqttCli->init(addr, "ESS", "", "", {});
while (!isQuit)
{
// 连接场站
static TimeTick ttStation;
if (ttStation.elapse(10000))
{
if (!mqttCli->isConnected)
{
}
else
{
for (auto& item: appdata.mapStation)
{
auto station = item.second;
if (station && !station->isConnected)
{
std::vector<std::string> vecTopics = {"topic/test" + std::to_string(station->id)};
mqttCli->subscribe(vecTopics, [=](int id)
{
station->isConnected = (id == 0);
});
}
break;
}
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));

View File

@@ -2,10 +2,11 @@
#include <thread>
#include "common/Logger.h"
#include "Operator.h"
#include "app/AppData.h"
class MqttClient;
class Application
{
public:
@@ -17,24 +18,25 @@ public:
static AppData& data()
{
return Application::instance().appdata_;
return Application::instance().appdata;
}
void init();
bool isQuit() { return isQuit_; }
Operator& getOperator() { return op_; }
Operator& getOperator() { return op; }
void runThreadMain();
void runThreadDevice();
public:
bool isQuit_ = false;
bool isQuit = false;
// 登录的管理员信息
Operator op_;
Operator op;
// 应用数据
AppData appdata_;
AppData appdata;
std::shared_ptr<MqttClient> mqttCli;
};

View File

@@ -3,7 +3,7 @@
#include <fstream>
#include "common/JsonN.h"
#include "Logger.h"
#include "common/Spdlogger.h"
#include "AppData.h"
AppOption Config::option;
@@ -14,10 +14,10 @@ bool Config::init(std::string filename)
bool ret = NJson::load(filename, jsonroot);
if (!ret)
{
XLOGE() << "[APP] load config failed, filename=" << filename;
spdlog::error("[config] load config file failed, filename={}", filename);
return false;
}
XLOGI() << "[APP] load config success, filename=" << filename;
spdlog::info("[config] load config file success, filename={}", filename);
if (jsonroot.contains("database"))
{
@@ -28,17 +28,20 @@ bool Config::init(std::string filename)
option.database.passwd = json.contains("passwd") ? json.at("passwd") : "";
option.database.dbname = json.contains("dbname") ? json.at("dbname") : "";
XLOGI() << "[APP] load database config end. host=" << option.database.host;
spdlog::info("[config] parse database success. host={}", option.database.host);
}
else
{
XLOGI() << "[APP] load database config error: not found. host=" << option.database.host;
spdlog::info("[config] parse database failed: not found. host={}", option.database.host);
}
if (jsonroot.contains("token"))
if (jsonroot.contains("http"))
{
std::string token = jsonroot["token"];
option.useToken = !token.empty();
NJsonNode json = jsonroot.at("http");
std:string token;
NJson::read(json, "token", token);
option.http.useToken = !token.empty();
NJson::read(json, "port", option.http.port);
}
return true;

View File

@@ -14,7 +14,12 @@ struct DatabaseOption
struct AppOption
{
DatabaseOption database;
bool useToken {true};
struct {
bool useToken {true};
int port {0};
} http;
};
class Config

View File

@@ -3,6 +3,7 @@
#include "database/SQL.h"
#include "common/fields.h"
#include "app/Device.h"
#include "common/Spdlogger.h"
Station::Station() : id(0)
@@ -71,7 +72,7 @@ void Station::setWorkMode(int modeId)
Errcode err = DAO::exec(NULL, sql);
if (err != Errcode::OK)
{
XLOGE() << "set station work mode failed.";
spdlog::error("set station work mode failed.");
}
}
@@ -83,6 +84,6 @@ void Station::setPolicy(int policyId)
Errcode err = DAO::exec(NULL, sql);
if (err != Errcode::OK)
{
XLOGE() << "set station policy failed.";
spdlog::error("set station policy failed.");
}
}

View File

@@ -28,6 +28,7 @@ public:
public:
int id {};
std::string name;
bool isConnected {false};
int workModeId {}; // 运行模式
int runPolicyId {}; // 运行策略

50
src/common/Spdlogger.cpp Normal file
View File

@@ -0,0 +1,50 @@
#include "Spdlogger.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include <spdlog/sinks/daily_file_sink.h>
//std::shared_ptr<spdlog::logger> Spdlogger::logger_ = spdlog::stdout_color_mt("sys");
//std::shared_ptr<spdlog::logger> Spdlogger::logger = spdlog::daily_logger_mt("daily_logger", "ees.log", 0, 0);
void Spdlogger::init(spdlog::level::level_enum log_level, std::string filename)
{
if (!filename.empty()) {
//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);
// 创建控制台接收器
auto consoleSink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
consoleSink->set_level(spdlog::level::info); // 设置控制台日志等级
//consoleSink->set_pattern("[%T] [%^%l%$] %v"); // 设置日志格式
// 创建文件接收器
//auto fileSink = std::make_shared<spdlog::sinks::basic_file_sink_mt>("logs/mcs.log", true);
//fileSink->set_level(spdlog::level::debug); // 设置文件日志等级
//fileSink->set_pattern("[%T] [%l] %v"); // 设置日志格式
// 每日文件sink可选每天生成新文件
auto dailySink = std::make_shared<spdlog::sinks::daily_file_sink_mt>("logs/mcs.log", 0, 0);
dailySink->set_level(spdlog::level::debug);
//dailySink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] %v");
// 创建一个多重接收器的 logger
std::vector<spdlog::sink_ptr> sinks {consoleSink, dailySink};
auto logger = std::make_shared<spdlog::logger>("", sinks.begin(), sinks.end());
// 设置全局 logger
spdlog::set_default_logger(logger);
spdlog::set_level(spdlog::level::debug); // 设置全局日志等级为 debug
spdlog::flush_on(spdlog::level::info); // 开启日志刷新
}
void Spdlogger::drop()
{
spdlog::drop_all();
}

36
src/common/Spdlogger.h Normal file
View File

@@ -0,0 +1,36 @@
#pragma once
#include "spdlog/spdlog.h"
#include <spdlog/sinks/stdout_color_sinks.h> // 彩色控制台日志
#include <spdlog/sinks/basic_file_sink.h> // 文件日志
#include <memory>
class Spdlogger
{
public:
static void init(spdlog::level::level_enum log_level, std::string filename);
static void drop();
template<typename... Args>
static void debug(spdlog::format_string_t<Args...> fmt, Args &&...args) {
//if (logger) logger->debug(fmt, args...);
}
template<typename... Args>
static void info(spdlog::format_string_t<Args...> fmt, Args &&...args) {
//if (logger) logger->info(fmt, args...);
}
template<typename... Args>
static void warn(spdlog::format_string_t<Args...> fmt, Args &&...args) {
//if (logger) logger->warn(fmt, args...);
}
template<typename... Args>
static void error(spdlog::format_string_t<Args...> fmt, Args &&...args) {
//if (logger) logger->error(fmt, args...);
}
private:
//static std::shared_ptr<spdlog::logger> logger;
};

View File

@@ -90,9 +90,9 @@ class TimeTick
public:
int64_t tickMS_ = 0;
TimeTick()
TimeTick(int t=0)
{
tickMS_ = Utils::time();
if (t !=0) { tickMS_ = Utils::time(); }
}
bool elapse(int64_t ms, bool reset = true)

View File

@@ -67,12 +67,12 @@ Errcode DAO::update(std::shared_ptr<DaoEntity> dao, std::string tableName, Field
std::string primaryVal = params.remove(primaryKey);
if (primaryVal.empty())
{
XLOGE() << "DAO update [" + tableName + "] failed, " << primaryKey << "=NULL.";
spdlog::error("DAO update [{}] failed,{} is NULL.", tableName, primaryKey);
return Errcode::ERR_PARAM;
}
if (params.size() == 0)
{
XLOGE() << "DAO update [" + tableName + "] failed, params size=0.";
spdlog::error("DAO update [{}] failed, params size=0.", tableName);
return Errcode::ERR_PARAM_NUL;
}
std::string condition = "WHERE " + primaryKey + "='" + primaryVal + "'";

View File

@@ -1,7 +1,6 @@
#pragma once
#include "DaoEntity.h"
#include "DataModelDef.h"
#include "common/Logger.h"
#include "errcode.h"
class DAO

View File

@@ -19,45 +19,27 @@
#include "pv/PvUser.h"
#include "rlsocket.h"
#include "protocol/HttpEntity.h"
#include "common/Spdlogger.h"
enum EAA
{
A = 1,
B = 2
};
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define wsa rlwsa
int main(int argc, char** argv)
{
EAA aa = EAA(100);
std::cout << aa;
std::thread([]() {
while (1) {
HttpEntity http;
http.listen("0.0.0.0", 19800);
}
}).detach();
// 设置控制台输出为 UTF-8 编码
SetConsoleOutputCP(CP_UTF8);
// 设置控制台输入为 UTF-8 编码(如果需要输入中文)
SetConsoleCP(CP_UTF8);
{
NJsonNode jsonroot;
NJson::parse(R"({"name": "Alice", "age": 25, "data":[["1","1","1"],["1","1","1"],["1","1","1"]]})", jsonroot);
std::cout << (jsonroot.is_null() ? "ERROR" : "OK") << std::endl;
std::vector<std::vector<std::string>> v1;
NJson::read<std::vector<std::vector<std::string>>>(jsonroot, "data", v1);
std::vector<std::vector<std::string>> vec = {
{"1", "1", "1", "1", "1", "1", "1", "1"},
{"1", "1", "1", "1", "1", "1", "1", "1"},
{"1", "1", "1", "1", "1", "1", "1", "1"},
{"1", "1", "1", "1", "1", "1", "1", "1"},
{"1", "1", "1", "1", "1", "1", "1", "1"}
@@ -70,14 +52,23 @@ int main(int argc, char** argv)
jsonroot1["price_off_peak"] = 0.53;
jsonroot1["periods"] = vec;
std::cout << jsonroot1.dump();
std::cout << jsonroot1.dump() << std::endl;
}
std::map<int, bool> mapT;
bool ff = mapT[1];
// 设置控制台输出为 UTF-8 编码
SetConsoleOutputCP(CP_UTF8);
// 设置控制台输入为 UTF-8 编码(如果需要输入中文)
SetConsoleCP(CP_UTF8);
rlwsa();
rlSocket socket("127.0.0.1", 19801, 1);
Spdlogger::init(spdlog::level::debug, "");
spdlog::info("[main] start ... ====================================================================================================");
spdlog::info("spd info");
spdlog::debug("spd debug");
spdlog::error("spd error");
//rlwsa();
//rlSocket socket("127.0.0.1", 19801, 1);
//int ret = socket.connect();
//std::string s1 = "helloworld";
//socket.write(s1.c_str(), s1.size());
@@ -91,7 +82,6 @@ int main(int argc, char** argv)
// std::cout << "===>>> " << std::string(buf.begin(), buf.end());
// }
//}
std::cout << "===>>> main start ... " << std::endl;
////std::cout << Snowflake::instance().getId() << std::endl;
//for (int i = 0; i<=10; ++i) {

View File

@@ -5,6 +5,7 @@
#include "common/Snowflake.h"
#include "app/Application.h"
#include "app/AppData.h"
#include "app/Config.h"
static NJsonNode FieldsToJsonArray(std::vector<Fields> vecFields)
{
@@ -116,65 +117,72 @@ static std::map<std::string, HandlerOptions> g_mapHttpHandler =
{"/queryPredictionDetail", HandlerOptions(&HttpEntity::queryPredictionDetail, {"token"})},
{"/queryStatSystem", HandlerOptions(&HttpEntity::queryStatSystem, {"token"})},
{"/queryStatTotal", HandlerOptions(&HttpEntity::queryStatTotal, {"token"})},
{"/queryStatDayList", HandlerOptions(&HttpEntity::queryStatDayList, {"token"})},
//{"/insert", HandlerOptions(&HttpEntity::insert, {})},
//{"/update", HandlerOptions(&HttpEntity::update, {})},
//{"/delete", HandlerOptions(&HttpEntity::delete, {})},
};
void HttpEntity::listen(std::string addr, int port)
HttpEntity::HttpEntity()
{
bool useToken = Config::option.http.useToken;
for (auto& item : g_mapHttpHandler)
{
std::string name = item.first;
HandlerOptions& handler = item.second;
this->httpsvr.Get(name, [=, &handler](const httplib::Request& req, httplib::Response& resp)
{
NJsonNode json;
Errcode errcode = Errcode::OK;
if (name != "/login" && Config::option.useToken)
this->httpsvr.Get(name, [=, &handler](const httplib::Request& req, httplib::Response& resp)
{
// 验证token
std::string token = req.get_param_value("token");
if (token.empty())
spdlog::info("[http] request: {}", name);
NJsonNode json;
Errcode errcode = Errcode::OK;
if (name != "/login" && useToken)
{
errcode = Errcode::ERR_TOKEN;
}
else
{
User user = Application::data().getUser(token);
if (user.userId.empty())
// 验证token
std::string token = req.get_param_value("token");
if (token.empty())
{
errcode = Errcode::ERR_TOKEN;
}
else
{
User user = Application::data().getUser(token);
if (user.userId.empty())
{
errcode = Errcode::ERR_TOKEN;
}
}
}
}
std::string errmsg;
if (errcode == Errcode::OK)
{
if (!HttpHelper::CheckRequestParam(req, resp, handler.requiredKeys, errmsg))
std::string errmsg;
if (errcode == Errcode::OK)
{
errcode = Errcode::ERR_PARAM;
if (!HttpHelper::CheckRequestParam(req, resp, handler.requiredKeys, errmsg))
{
errcode = Errcode::ERR_PARAM;
}
else
{
errcode = (this->*(handler.func))(req, resp, json);
}
}
else
{
errcode = (this->*(handler.func))(req, resp, json);
}
}
json["errcode"] = errcode;
json["errmsg"] = ErrcodeStr(errcode) + (errmsg.empty() ? "" : (":"+errmsg));
resp.set_content(json.dump(), "text/plain; charset=utf-8");
resp.status = 200;
});
json["errcode"] = errcode;
json["errmsg"] = ErrcodeStr(errcode) + (errmsg.empty() ? "" : (":"+errmsg));
resp.set_content(json.dump(), "text/plain; charset=utf-8");
resp.status = 200;
});
}
}
void HttpEntity::listen(std::string addr, int port)
{
if (addr.empty()) addr = "0.0.0.0";
httpsvr.listen(addr, port);
spdlog::info("[http] start listen: addr={}:{},token={}", addr, port, Config::option.http.useToken);
httpsvr.listen(addr, port); // 阻塞
}
void HttpEntity::registGet(std::string name, void (HttpEntity::* func)(const httplib::Request& req, httplib::Response& resp))
@@ -498,5 +506,60 @@ Errcode HttpEntity::queryPredictionDetail(const httplib::Request& req, httplib::
jsonData.push_back(jnode);
}
json["data"] = jsonData;
return Errcode::OK;
}
Errcode HttpEntity::queryStatSystem(const httplib::Request& req, httplib::Response& resp, NJsonNode& json)
{
auto& appdata = Application::data();
json["launch_date"] = "2025-01-01"; //: 系统上线启用日期格式yyyy-mm-dd
json["income_total"] = "0.00"; // : 累计收益精度0.01
json["station_num"] = Utils::toStr(appdata.getStationCount()); // : 能源站数量
json["storage_device_num "] = Utils::toStr(appdata.getStationCount()); //: 储能设备数量
json["solar_device_num"] = "0"; // : 光伏设备数量
json["capacity_total"] = "0.000"; // : 储能总容量kWh精度0.001
json["elect_gen"] = "0.000"; // : 发电总电量kWh精度0.001
json["elect_grid"] = "0.000"; // : 入网种电量kWh精度0.001
json["storage_elect_in"] = "0.000"; // : 储能充电总电量kWh精度0.001
json["storage_elect_out"] = "0.000"; // : 储能放电总电量kWh精度0.001
return Errcode::OK;
}
Errcode HttpEntity::queryStatTotal(const httplib::Request& req, httplib::Response& resp, NJsonNode& json)
{
std::string station_id = req.get_param_value("station_id");
std::string category = req.get_param_value("category");
json["dt"] = "2025-01-01"; //日期
json["storage_elect_in"] = "123.123"; //储能充电电量kWh精度0.001
json["storage_elect_out"] = "123.123"; //储能放电电量kWh精度0.001
json["storage_num_in"] = "1"; //储能设备充电次数
json["storage_num_out"] = "1"; //储能设备放电次数
json["storage_num_err"] = "1"; //储能设备故障次数
json["solar_elect_gen"] = "123.123"; //光伏发电电量kWh精度0.001
json["solar_elect_grid"] = "123.123"; //光伏入网电量kWh精度0.001
json["solar_num_err"] = "1"; //光伏设备故障次数
json["charge_elect"] = "123.123"; //充电设备充电电量kWh精度0.001
json["charge_num"] = "1"; //充电设备充电次数
json["charge_num_err"] = "1"; //充电设备故障次数
json["income_elect"] = ""; //发电收益精度0.01
json["income_charge"] = ""; //充电收益精度0.01
json["usage"] = "";
return Errcode::OK;
}
Errcode HttpEntity::queryStatDayList(const httplib::Request& req, httplib::Response& resp, NJsonNode& json)
{
std::string station_id = req.get_param_value("station_id");
std::string category = req.get_param_value("category");
std::string dt_start = req.get_param_value("dt_start");
std::string dt_end = req.get_param_value("dt_end");
if (!dt_start.empty() && dt_end.empty())
{
}
return Errcode::OK;
}

View File

@@ -6,6 +6,7 @@ class HttpEntity
{
public:
httplib::Server httpsvr;
HttpEntity();
void listen(std::string addr, int port);
void registGet(std::string name, void (HttpEntity::* func)(const httplib::Request& req, httplib::Response& resp));
@@ -54,5 +55,8 @@ public:
Errcode updateAlertLog(const httplib::Request& req, httplib::Response& resp, NJsonNode& json);
Errcode queryPredictionDetail(const httplib::Request& req, httplib::Response& resp, NJsonNode& json);
Errcode queryStatSystem(const httplib::Request& req, httplib::Response& resp, NJsonNode& json);
Errcode queryStatTotal(const httplib::Request& req, httplib::Response& resp, NJsonNode& json);
Errcode queryStatDayList(const httplib::Request& req, httplib::Response& resp, NJsonNode& json);
};

265
src/protocol/MqttEntity.cpp Normal file
View File

@@ -0,0 +1,265 @@
#include "MqttEntity.h"
#include "common/Spdlogger.h"
#include "common/JsonN.h"
#define TIMEOUT 10000L
int MqttClient::init(string addr, string client_id, string username, string password, std::vector<std::string> vecTopic)
{
this->addr = addr;
this->vecTopic = vecTopic;
MQTTAsync_connectOptions option = MQTTAsync_connectOptions_initializer;
MQTTAsync_message pubmsg = MQTTAsync_message_initializer;
int rc {0};
// "tcp://localhost:1883"
rc = MQTTAsync_create(&client, addr.c_str(), client_id.c_str(), MQTTCLIENT_PERSISTENCE_NONE, NULL);
if (rc != MQTTASYNC_SUCCESS)
{
spdlog::error("[mqtt] MQTTAsync_create error: {}", rc);
return rc;
}
MQTTAsync_connectionLost* onConnectionLost =
[](void* context, char* cause)
{
static_cast<MqttClient*>(context)->onConnectionLost(cause);
};
MQTTAsync_messageArrived* onMessageArrived =
[](void* context, char* topicName, int topicLen, MQTTAsync_message* message)->int
{
return static_cast<MqttClient*>(context)->onMessageArrived(topicName, topicLen, message);
};
MQTTAsync_deliveryComplete* onDeliveryComplete =
[](void* context, MQTTAsync_token token)
{
};
//设置连接丢失、接受消息的回调函数
rc = MQTTAsync_setCallbacks(client, this, onConnectionLost, onMessageArrived, onDeliveryComplete);
if (rc != MQTTASYNC_SUCCESS)
{
spdlog::error("[mqtt] MQTTAsync_setCallbacks error");
return rc;
}
option.keepAliveInterval = 20;
option.cleansession = 1;
option.onSuccess = [](void* context, MQTTAsync_successData* resp) { static_cast<MqttClient*>(context)->onConnectSuccess(resp); };;
option.onFailure = [](void* context, MQTTAsync_failureData* resp) { static_cast<MqttClient*>(context)->onConnectFaiure(resp); };
option.context = this;
option.username = username.c_str();
option.password = password.c_str();
//断开重连设置
option.automaticReconnect = 1;//设置非零,断开自动重连
option.minRetryInterval = 5; //单位秒,重连间隔次数,每次重新连接失败时,重试间隔都会加倍,直到最大间隔
option.maxRetryInterval = 60;//单位秒,最大重连尝试间隔
rc = MQTTAsync_connect(client, &option);
if (rc != MQTTASYNC_SUCCESS)
{
spdlog::error("[mqtt] MQTTAsync_connect error");
return rc;
}
return 0;
//MQTTAsync_disconnect(client, NULL);
//MQTTAsync_destroy(&client);
}
struct SubscribInfo
{
std::function<void(int id)> callback;
};
void MqttClient::subscribe(std::vector<std::string> vecTopics, std::function<void(int)> callback)
{
SubscribInfo* info = new SubscribInfo();
info->callback = callback;
MQTTAsync_responseOptions options = MQTTAsync_responseOptions_initializer;
options.context = info;
options.onSuccess = [](void* context, MQTTAsync_successData* response)
{
spdlog::info("[mqtt] subscribe success.");
SubscribInfo* info = (SubscribInfo*)context;
info->callback(0);
delete info;
};
options.onFailure = [](void* context, MQTTAsync_failureData* response)
{
spdlog::error("[mqtt] subscribe failed.");
SubscribInfo* info = (SubscribInfo*)context;
info->callback(-1);
delete info;
};
int count = 3;
char* topicsTmp[] = {
"topic/aa",
"topic/bb",
"topic/cc"
};
std::vector<int> qosTmp(count, 1); // 为每个主题指定 QoS
if (count > 0)
{
int rc = MQTTAsync_subscribeMany(client, count, topicsTmp, qosTmp.data(), &options);
if (rc != MQTTASYNC_SUCCESS)
{
spdlog::error("[mqtt] subscribe failed, err={}", rc);
}
}
else
{
delete info;
}
}
int MqttClient::publish(string topic, string text)
{
spdlog::info("MQTT publish: topic={}, text={}", topic, text);
MQTTAsync_responseOptions options = MQTTAsync_responseOptions_initializer;
//options.onSuccess = onSend;
//options.onFailure = onSendFailure;
options.context = this;
MQTTAsync_message msg = MQTTAsync_message_initializer;
msg.qos = 1;
msg.payload = text.data();
msg.payloadlen = text.size();
msg.retained = 0;
int rc = MQTTAsync_sendMessage(client, topic.c_str(), &msg, &options);
if (rc == MQTTASYNC_SUCCESS)
{
spdlog::info("MQTT send message success, topic={}, text={}", topic, text);
}
else
{
spdlog::error("MQTT send message error, topic={}, text={}", topic, text);
}
return rc;
}
void MqttClient::onConnectionLost(char* cause)
{
this->isConnected = false;
spdlog::error("MQTT connection lost, cause={}", cause);
}
int MqttClient::onMessageArrived(char* topic, int topicLen, MQTTAsync_message* msg)
{
int len = msg->payloadlen;
char* payload = (char*)msg->payload;
spdlog::info("MQTT message arrived: topic=[{},{}], payload len={}, payload msg={}", topic, msg->qos, len, payload);
// 必须释放消息内存!
MQTTAsync_freeMessage(&msg);
MQTTAsync_free(topic);
return 1; // 1表示消息已经处理
}
// 交付完成回调(可选)
void MqttClient::onDeliveryComplete(MQTTAsync_token token)
{
//spdlog::info("MQTT delivery complete, token={}", token);
}
void MqttClient::onConnectSuccess( MQTTAsync_successData* resp)
{
this->isConnected = true;
//spdlog::info("[mqtt] connect success: {}", addr);
//MQTTAsync_responseOptions options = MQTTAsync_responseOptions_initializer;
//options.context = this;
//options.onSuccess = [](void* context, MQTTAsync_successData* response)
// {
// spdlog::info("[mqtt] subscribe success.");
// };
//options.onFailure = [](void* context, MQTTAsync_failureData* response)
// {
// spdlog::info("[mqtt] subscribe failed.");
// };
//
//for (auto& topic: vecTopic)
//{
// int rc = MQTTAsync_subscribe(client, topic.c_str(), qos, &options);
// if (rc != MQTTASYNC_SUCCESS)
// {
// spdlog::error("[mqtt] subscribe [{},{}] failed, err={}", topic, qos, rc);
// }
// else
// {
// spdlog::info("[mqtt] subscribe [{},{}] success", topic, qos);
// }
//}
}
void MqttClient::onConnectFaiure(MQTTAsync_failureData* resp)
{
this->isConnected = false;
}
string MQTT::packEquipmentInfo(mqtt::EquipmentInfo& info)
{
NJsonNode jsonroot;
jsonroot["EquipmentID"] = info.EquipmentID.c_str();
jsonroot["ManufacturerID"] = info.ManufacturerID.c_str();
jsonroot["EquipmentModel"] = info.EquipmentModel.c_str();
jsonroot["ProductionDate"] = info.ProductionDate.c_str();
jsonroot["OpenForBusinessDate"] = info.OpenForBusinessDate.c_str();
jsonroot["EquipmentType"] = info.EquipmentType;
return jsonroot.dump();
}
string MQTT::packSwapEquipmentStatusInfo(string node_id)
{
NJsonNode jsonroot;
return jsonroot.dump();
}
string MQTT::packNotifyStationInfo()
{
NJsonNode jsonroot;
return jsonroot.dump();
}
string MQTT::packNotifyAlarm()
{
NJsonNode jsonroot;
return jsonroot.dump();
}
string MQTT::packNotifyChargeStatus()
{
NJsonNode jsonroot;
return jsonroot.dump();
}
string MQTT::packNotifySwapStatus()
{
NJsonNode jsonroot;
return jsonroot.dump();
}
string MQTT::packNotifyChargeOrder()
{
NJsonNode jsonroot;
return jsonroot.dump();
}
string MQTT::packNotifySwapOrder()
{
NJsonNode jsonroot;
return jsonroot.dump();
}

368
src/protocol/MqttEntity.h Normal file
View File

@@ -0,0 +1,368 @@
#pragma once
#include <string>
#include <vector>
#include <functional>
#include "MQTTAsync.h"
using namespace std;
class MqttClient
{
public:
int init(string addr, string client_id, string username, string password, std::vector<std::string> vecTopic);
void subscribe(std::vector<std::string> topics, std::function<void(int)> callback);
int publish(string topic, string text);
void onConnectionLost(char* cause);
int onMessageArrived(char* topic, int len, MQTTAsync_message* msg);
void onDeliveryComplete(MQTTAsync_token token);
void onConnectSuccess(MQTTAsync_successData* resp);
void onConnectFaiure(MQTTAsync_failureData* resp);
public:
MQTTAsync client = nullptr;
std::vector<std::string> vecTopic;
std::string addr; // "tcp://localhost:1883"
int qos {1};
std::string clientId;
bool isConnected {false};
bool isSubscribed {false};
};
// <数据方向>/<数据格式>/<厂家ID>/<指令>/<设备标识,上行可选>
// Topic 字段说明
// 数据方向 :
//·down:下行,用于管理平台向厂家平台发送指令
// up :上行,用于厂家平台向管理平台推送设备应答或主报数据数据格式:基础格式为json其他格式均为对json字符串进行二次编码
// json : body内的数据采用json编码
// base64 : body内数据先采用ison编码然后再使用base64编码的字符串
// rsa : body内数据先采用json编码然后采用 rsa 算法加密的base64字符串(rsa 密钥由管理平台提供)
// sm2 : body内数据先采用json编码然后采用 sm2 算法加密的base64字符串(sm2 密钥由管理平台提供)
//·厂家ID : 由管理平台定义
//·命令ID : 用于指定读取/设置/控制的具体内容,如读取数据,读取时钟等等,具体参考 交互类指令协议设备标识 : 用于标识设备的唯一id具体定义见3.2中的 context.dev_id 说明,上行时可不提供。
// <数据方向>/<数据格式>/<厂家ID>/<指令>/<设备标识,上行可选>
#define TOPIC_PCS_YC "up/json/预制舱01/PCS_YC"
#define TOPIC_PCS_YC "up/json/预制舱01/PCS_YC"
#define MQTT_TOPIC_NOTIFY_STATION "notification_stationInfo" // 充(换)电站信息变化推送
#define MQTT_TOPIC_QUERY_STATION "query_stations_info" // 查询充(换)电站信息
#define MQTT_TOPIC_NOTIFY_ALARM "notification_alarmInfo" // 告警信息推送
#define MQTT_TOPIC_NOTIFY_CHARGE_STATUS "notification_connectorStatus" //充电设备状态变化推送
#define MQTT_TOPIC_NOTIFY_SWAP_STATUS "notification_swapStatus" //换电设备状态变化推送
#define MQTT_TOPIC_QUERY_STATUS "query_station_status" //查询站内设备接口状态
#define MQTT_TOPIC_NOTIFY_CHARGE_ORDER "notification_orderInfo" //充电电量信息推送
#define MQTT_TOPIC_QUERY_ORDER "query_order_info" //查询充电电量信息
#define MQTT_TOPIC_NOTIFY_SWAP_ORDER "notification_swapInfo" //换电记录信息推送
#define MQTT_TOPIC_QUERY_SWAP_ORDER "query_swap_info" //查询换电电量信息
namespace mqtt
{
// 充(换)电运营商信息
struct OperatorInfo
{
string OperatorID; // 运营商ID 组织机构代码 是 字符串 9 字符
string OperatorName; // 运营商名称 机构全称 是 字符串 <= 64 字符
string OperatorTel1; // 运营商电话1 运营商客服电话 1 是 字符串 <= 32 字符
string OperatorTel2; // 运营商电话2 运营商客服电话 2 否 字符串 <= 32 字符
string OperatorRegAddress; // 运营商注册地址 运营商注册地址 否 字符串 <= 64 字符
string OperatorNote; // 备注 备注信息 否 字符串 <= 255 字符
};
// 充(换)电站信息
struct StationInfo
{
string StationID; // 充(换)电站 ID 运营商自定义的唯一编码 是 字符串 <= 20 字 符
string OperatorID; // 运营商 ID 电动汽车充(换)电服务平台的运营商 ID 是 字符串 9 字符
string EquipmentOwnerID; // 设备所属方 ID 设备所属方组织机构代码,所属方为个人时可不填 否 字符串 9 字符
string StationName; // 充(换)电站名称 充(换)电站名称的描述 是 字符串 <= 50 字
string CountryCode; // 充(换)电站国家代码 比如 CN 是 字符串 2 字符
string AreaCode; // 充(换)电站省市辖区编码 填写内容为参照 GB / T2260以民政部发布最新数据为准 是 字符串 <= 20 字符
string Address; // 详细地址 是 字符串<= 100字符
string StationTel; // 站点电话 能够联系场站工作人员进行协助的联系电话 否 字符串<= 30 字符
string ServiceTel; // 服务电话 平台服务电话,例如 400 电话 是 字符串<= 30字符
string ServiceType; // *服务类型 1充电 2换电 3充换电 255其他 是 整型
string StationType; // 站点类型 1公共充电站 2专用充电站 3居民充电站 255其他 是 整型
string StationStatus; // 站点状态 0未知 1建设中 5关闭下线 6维护中 50正常使用 是 整型
string ParkNums; // 车位数量 可停放进行充电的车位总数, 默认0 未知 是 整型
float StationLng; // 经度 GCJ - 02 坐标系 是 浮点型 保留小数点后6 位
float StationLat; // 纬度 GCJ - 02 坐标系 是 浮点型保留小数点后6 位
string SiteGuide; // 站点引导描述性文字,用于引导车主找到充电车位 否 字符串<= 255字符
int Construction; // 建设场所
//101公共服务场所
//102公共停车场
//103城市交通节点
//104加油站
//105具备停车条件的充电区域
//106高速服务区
//201政府机关
//202公共机构
//203企业事业单位
//204公交
//205环卫
//206物流
//207出租车
//208港口码头
//209重卡换电场所
//210矿卡换电场所
//301居民
//255其他
// 是 整型
string Pictures; // 站点照片 充(换)电设备照片、充(换)电车位照片、停车场入口照片 是 字符串数组 无照片时可传空数组
string MatchCars; // 服务车型描述 描述该站点可充(换)电服务 的车辆类型:如大巴、物流车、私家乘用车、出租车、重卡型卡车等 否 字符串<= 255字符
string ParkInfo; // 车位楼层及数量描述车位楼层以及数量信息 否 字符串<= 100字符
int OpenAllDay; // *全天开放 0否 1是 是 整型
string OpenForBusinessDate; // *投运日期 站点投运日期 yyyy - MM - dd 格式 是 字符串
string BusineHours; // 营业时间 营业时间描述 否 字符串<=255字符
string ElectricityFee; // *电费费率 示例 [{"StartTime":"000000","Price":"1.0000"},{"StartTime":"120000","Price":"1.2000"}] 否 字符串 <= 2000 字符
string ServiceFee; // *服务费率 示例[{"StartTime":"000000","Price":"1.0000"},{"StartTime":"120000","Price":"1.2000"}] 否 字符串 <= 2000 字符
string ParkOwner; // *停车场产权方 停车场产权人 否 字符串
string ParkManager; // *停车场管理方 停车场管理人XX 物业) 否 字符串
int ParkType; // 停车费类型 0免费 1不免费 2限时免费停车 3充电限时减免 255参考场地实际收费标准 否 整型
string ParkFee; // 停车费描述 停车费率描述 否 字符串 <= 255字符
string Payment; // 支付方式 支付方式: 刷卡、线上、现金其中电子钱包类卡为刷卡,身份鉴权卡、微信 / 支付宝等在线支付、APP 支付为线上否 字符串<= 20 字符
int SupportOrder; // 是否支持预约 0不支持预约 1支持预约。不填默认为 0 否 整型
string Remark; // 备注 其他备注信息 否 字符串<= 100字符
string EquipmentInfos; // 充电设备信息列表 该充(换)电站所有充电设备 信息对象数组 是 EquipmentInfos[],参照 4.4
string SwapEquipmentInfos; // *换电设备信息列表 该充(换)电站所有换电设备。换电站以及充换电站提供此数据,充电站默认空数组。 是 SwapEquipmentInfo[],参照 4.6 换电工位信息
int BatteryNo; // *备用电池数量换电站内可提供更换最大电池数量。换电站以及充换电站提供此数据充电站默认填0。 是 整型
};
// 充电设备信息
struct EquipmentInfo
{
string EquipmentID;
string ManufacturerID;
string EquipmentModel;
string ProductionDate;
string OpenForBusinessDate;
int EquipmentType;
int EquipmentStatus;
vector<int> ConnectorInfos;
float EquipmentLng;
float EquipmentLat;
float Power;
string EquipmentName;
};
// 充电设备接口信息
struct ConnectorInfo
{
string ConnectorID; // 充电设备接口编码 充电设备接口编码,同一运营商内唯一 是 字符串 <= 64 字符
string ConnectorName; // 充电设备接口名称 否 字符串 <= 30 字符
int ConnectorType; // 充电设备接口类型
//1家用插座模式 2
//2交流接口插座模式 3连接方式 B
//3交流接口插头带枪线模式 3连接方式 C
//4直流接口枪头带枪线模式 4
//5无线充电座
//6其他
//7对换电站电池箱的接口
//是 整型
int VoltageUpperLimits; // 额定电压上限 单位V 是 整型
int VoltageLowerLimits; // 额定电压下限 单位V 交流可与额定电压上限相同 是 整型
int ConstantVoltageUpperLimits; // *恒功率电压上限 单位V 否 整型
int ConstantVoltageLowerLimits; // *恒功率电压下限 单位V 否 整型
int Current; // 额定电流 单位A 是 整型
float Power; // 额定功率 单位kW 是 浮点型 保留小数点后一位
string ParkNo; // 车位号 停车场车位编号,或充电架编号 否 字符串 <= 10 字符
int NationalStandard; // 国家标准 1:2011 2 : 2015 3 : 兼容 2011 和 2015 是 整型
};
// 换电设备信息(SwapEquipmentInfo)
class SwapEquipmentInfo
{
string EquipmentID; // 设备编码 换电设备唯一编码,同一运营商下唯一 是 字符串 <= 64 字符
string ManufacturerID; // 设备生产商组织机构代码 设备生产商组织机构代码 否 字符串 9 字符
string EquipmentModel; // 设备型号 由设备生厂商定义的设备型号 否 字符串 <= 20 字符
string ProductionDate; // 设备生产日期 YYYY - MM - DD 否 字符串 10 字符
string OpenForBusinessDate; // *投运日期 充电桩投运日期 yyyy - MM - dd 格式 是 字符串
string OpreateStatus; // 运营状态 0未知 1建设中 5关闭下线 6维护中 50正常使用 是 整型
int EquipmentType; // 换电设备类型 填写内容为参照GB29317 - 2021 4.3节中的描述 1侧向换电 2底部换电 3顶部换电 4端部换电 5中置换电 255其他 是 整型
string MatchCars; // 服务车型描述 描述该设备可服务的车辆类型以及 型号等 否 字符串<= 1000 字符
string SupplyBattery; // 提供电池描述 描述该设备提供的电池类型以及型号等 否 字符串<= 100 字符
};
//电池箱信息
struct BatteryInfo
{
string BatteryNo; // 电池箱编号 运营商自定义唯一编码 是 字符串 <= 32 字
string BatteryOwnerID; // 电池所属方ID 设备所属方组织机构代码,所属方为个人时可不填 否 字符串 9 字符
string ManufacturerID; // 设备生产商组织机构代码 设备生产商组织机构代码 否 字符串 9 字符
string BatteryModel; // 电池型号 由设备生厂商定义的设备型号 否 字符串 <= 20 字符
string ProductionDate; // 设备生产日期 YYYY - MM - DD 否 字符串 10 字符
string OpenForBusinessDate; // 投运日期 电池投运日期 yyyy - MM - dd 格式 是 字符串
int CellNum; // 电池箱所含单体电池个数 电池箱所含单体个数 是 整型
int SeriesNum; // 单体电池串联总数 串联总数 否 整型
int ParallelNum; // 单体电池并联总数 并联总数 否 整型
int BatteryType; // 电池类型
//1磷酸铁锂电池
//2锰酸锂电池
//3钴酸锂电池
//4三元材料电池
//5聚合物锂离子电池
//6钛酸锂电池
//7燃料电池
//255其它
//是 整型
float RatedCapacity; // 电池箱额定容量 单位Ah小数点后 1 位 是 浮点型
float RatedVoltage; // 电池箱额定电压 单位V小数点后 1 位 是 浮点型
};
// 充电设备接口状态
struct ConnectorStatusInfo
{
string ConnectorID; // 充电设备接口编码 充电设备接口编码,同一运营商内唯一 是 字符串<= 64 字符
string UpdateTime; // 状态更新时间 本次状态变化的时间格式“yyyy -MM - dd HH : mm:ss” 是 字符串 <= 20 字符
int Status; // 接口状态 0离线 1空闲 2占用未充电 3占用充电中 4占用预约锁定 255故障 是 整型
int ParkStatus; // 车位状态 0未知 10空闲 50占用 否 整型
int LockStatus; // 地锁状态 0未知 10已解锁 50已上锁 否 整型
int CurrentA; // A 相电流 单位A默认0 含直流(输出) 是 整型
int CurrentB; // B 相电流 单位A默认0 否 整型
int CurrentC; // C 相电流 单位A默认0 否 整型
int VoltageA; // A 相电压 单位V默认0 含直流(输出) 是 整型
int VoltageB; // B 相电压 单位V默认0 否 整型
int VoltageC; // C 相电压 单位V默认0 否 整型
float SOC; // *剩余电量 默认0 交流充电桩采集不到SOC 值的填 0 是 浮点型
string Begin_time; // *开始充电时间 格式 为 yyyy-MM-dd HH:mm:ss 是 字符串
float Current_kwh; // *本次已充电量 单位kWh 是 浮点型
float Current_meter; // *当前电表读数 单位kWh 否 浮点型
string Vin; // *车架号 否 字符串 <= 20 字符
//BatteryStatusInfo //*电池状态信息 充电设备有电池情况下需上报 是 BatteryStatusInfo参照4.9
};
// 电池箱状态
struct BatteryStatusInfo
{
string BatteryNo; // 电池箱编号 (充电设备有电池情况下需上报) 是 字符串<= 32 字符
string UpdateTime; // 状态更新时间 本次状态变化的时间格式“yyyy -MM - dd HH : mm:ss” 是 字符串 <= 20 字符
float Voltage; // 当前电压 单位V小数点后 2 位 (充电设备有电池情况下需上报) 是 浮点型
float Current; // 当前电流 单位V小数点后 2 位 (充电设备有电池情况下需上报) 是 浮点型
float SOC; // 当前 Soc 当前电池电量百分比范围0100小数点后 1 位 (充电设备有电池情况下需上报) 是 浮点型
float SOH; // 当前 Soh 当前电池健康度范围0100小数点后 1 位(充电设备有电池情况下需上报)是 浮点型
int BatteryIsFault; // 电池箱是否故障 0未知 1是 2否 (充电设备有电池情况下需上报) 是 整型
int MaxVoltageBatteryNo; // 最高电压单体电池编号 充电设备有电池情况下需上报 否 整型
float MaxVoltage; // 最高电压单体电池电压值单位: V小数点后3位 充电设备有电池情况下需上报 否 浮点型
int MinVoltageBatteryNo; // 最低电压单体电池编号 充电设备有电池情况下需上报 否 整型
float MinVoltage; // 最低电压单体电池电压值 单位: V小数点后 3 位 充电设备有电池情况下需上报 否 浮点型
float MaxTempBatteryNo; // 最高温度测温点编号 充电设备有电池情况下需上报 否 整型
int MaxTemp; // 最高温度测温点温度值 单位:℃ 充电设备有电池情况下需上报 否 整型
int MinTempBatteryNo; // 最低温度测温点编号 充电设备有电池情况下需上报 否 整型
int MinTemp; // 最低温度测温点温度值 单位:℃ 充电设备有电池情况下需上报 否 整型
};
// 换电设备状态SwapEquipmentStatusInfo
struct SwapEquipmentStatusInfo
{
string EquipmentID; // 换电设备编码 换电设备编码,同一运营商内唯一 是 字符串 <= 64 字符
string UpdateTime; // 状态更新时间 本次状态变化的时间格式“yyyy - MM - dd HH : mm:ss” 是 字符串 <= 20 字符
int Status; // 换电设备状态 0离线 1空闲 2工作 255故障 是 整型
int SwapMode; // 换电模式 0手动模式 1半自动模式 2全自动模式 3检修模式 否 整型
};
// 充电站状态StationStatusInfo
struct StationStatusInfo
{
string StationID; // 充(换)电站 ID 运营商自定义的唯一编码 是 字符串 <= 20 字符
string ConnectorStatusInfos; // 充电设备接口状态列表 充(换)电站下所有充电设备接口的状态对象数组 是 ConnectorStatusInfos[] 参照 5.6
string SwapEquipmentStatusInfo; // 换电设备状态列 所有充电设备接口的是 SwapEquipmentStatusInfo[]表 状态 参照4.10
};
//充电电量信息OrderInfo
struct OrderInfo
{
string OperatorID; // 运营商 ID 统一社会信用代码 是 字符串 9 字符
string ConnectorID; // 充电设备接口编码 充电设备接口编码,同一充(换)电运营平台内唯一 是 字符串 <= 26 字符
string StartChargeSeq; // 充电业务编号 运营商充电业务编号 是 字符串 <= 32 字符
int UserChargeType; // 用户发起充电类型 1:充(换)电运营平台注册用户 2 : 监管平台注册用户 3 : 其他 否 整型
string MobileNumber; // 用户手机号 若用户发起充电类型为APP用户手机号必填否 字符串
float Money; // 本次充电消费总金额 单位:元 若通过苏e充APP启动此字段为必填项。 否 浮点型
float ElectMoney; // 本次充电电费总金额 单位:元 若通过苏e充APP启动此字段为必填项。 否 浮点型
float ServiceMoney; // 本次充电服务费金额 单位:元 若通过苏e充APP启动此字段为必填项。 否 浮点型
float Elect; // 本次充电电量 单位 kWh精度0.001,如果不设置峰谷电价,平电量等于本次充电电量,其他分电量为零。 是 浮点型
float CuspElect; // *尖阶段电量 单位 kWh精度0.001 是 浮点型
float PeakElect; // *峰阶段电量 单位 kWh精度0.001 是 浮点型
float FlatElect; // *平阶段电量 单位 kWh精度0.001 是 浮点型
float ValleyElect; // *谷阶段电量 单位 kWh精度0.001 是 浮点型
float StartTime; // 本次充电开始时间 格式“yyyy - MM - ddHH : mm:ss” 是 字符串
float EndTime; // 本次充电结束时间 格式“yyyy - MM - ddHH : mm: ss” 是 字符串
float PaymentAmount; // 支付金额 支付金额 若通过苏e充APP启动此字段为必填项。 否 浮点型 保留小数点后 2 位
float MeterValueStart; // *电表总起值 单位 kWh精度0.001 是 浮点型 保留小数点后三位
float MeterValueEnd; // *电表总止值 单位 kWh精度0.001 是 浮点型 保留小数点后三位
float Vin; // *本次充电车架号 充电设备有车辆VIN码需上报 否 字符串 <= 64 字符
float BatteryNo; // *本次充电电池编号 充电设备有电池情况下需上报 否 字符串 <= 64 字符
float ExchangeChargeSeq;// *换电记录编号 格式“运营商 ID + 唯一编号”27 字符,如果有对应的换电记录需要填写是 字符串 <= 40 字符
};
// 换电记录信息SwapInfo
struct SwapInfo
{
string OperatorID; //运营商 ID 统一社会信用代码 是 字符串 9 字符
string EquipmentID; // 换电设备编码 换电设备接口编码,同一充(换)电运营平台内唯一 是 字符串 <= 40 字符
string ExchangeChargeSeq; // 换电记录编号 格式“运营商 ID + 唯一编号”27 字 符 是 字符串
string SwapMode; // 换电模式 0手动模式 1半自动模式 2全自动模式 3检修模式 否 整型
string CarNo; // 车牌号 否 字符串 <= 16 字符
string Vin; // 车辆VIN码 车辆识别码见GB - T - 27930 - 2015国标PGN512 BMS 和车辆辨识报文BRM约定 否 字符串
string RepDownBatteryNo;// 换下电池箱编号 运营商自定义唯一 编码, 是 字符串 <= 32 字符
string RepDownBatterySoc;// 换下电池箱SOC 电池电量百分比范围0100 是 整型
string RepOnBatteryNo;//换上电池箱编号 运营商自定义唯一编码,是 字符串 <= 32 字符
int RepOnBatterySoc; //换上电池箱SOC 电池电量百分比范围0100 是 整型
float TotalPower; //换上电池箱总充入电量 单位:度(kWh) 是 浮点型 保留小数点后两位
string StartTime; //换电开始时间 格式“yyyy - MM - dd HH : mm:ss” 是 字符串
string EndTime; //换电结束时间 格式“yyyy - MM - dd HH : mm: ss” 是 字符串
};
// 充电设备告警信息AlarmInfo
struct AlarmInfo
{
string EquipmentID; // 设备编码 充电接口唯一编码,对同一运营商,保证唯一 是 字符串 23 字符
int EquipmentType; //设备类型 1充电设备 2换电设备 整型
string Alert_time; //告警时间 格 式 为 yyyy - MMdd HH : mm:ss 是 字符串
int Alert_code; //告警代码 告警代码 是 整型
string Describe; //描述 文字描述,最大长度 256字符。是 字符串 256 字符
int Status; //状态 告警发生0告警 恢复: 1默认为 0。是 整型
};
}
class MQTT
{
public:
static string packEquipmentInfo(mqtt::EquipmentInfo& info);
static string packSwapEquipmentStatusInfo(string node_name);
// 充(换)电站信息变化推送
static string packNotifyStationInfo();
// 告警信息推送
static string packNotifyAlarm();
// 充电设备状态变化推送, 充电启停或者离线状态改变时推送,充电过程中每分钟一次推送
static string packNotifyChargeStatus();
// 换电设备状态变化推送
static string packNotifySwapStatus();
// 充电电量信息推送chon 充电结束后5分钟内推送
static string packNotifyChargeOrder();
// 换电记录信息推送, 换电结束后5分钟内推送
static string packNotifySwapOrder();
};