2026-01-22 17:55:11 +08:00
|
|
|
|
#include <pybind11/pybind11.h>
|
|
|
|
|
|
#include <pybind11/stl.h> // 支持 std::vector, std::map 等
|
|
|
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
|
|
#include <cstring>
|
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
#include <string>
|
|
|
|
|
|
#include "native_logger.hpp"
|
|
|
|
|
|
|
|
|
|
|
|
namespace py = pybind11;
|
|
|
|
|
|
using json = nlohmann::json;
|
|
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
|
// 配置项
|
|
|
|
|
|
const std::string _cfg_server_ip = "www.shelingxingqiu.com";
|
|
|
|
|
|
const int _cfg_server_port = 50005;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 定义获取配置的函数
|
|
|
|
|
|
py::dict get_config() {
|
|
|
|
|
|
py::dict config;
|
|
|
|
|
|
config["SERVER_IP"] = _cfg_server_ip;
|
|
|
|
|
|
config["SERVER_PORT"] = _cfg_server_port;
|
|
|
|
|
|
return config;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 辅助函数:将 py::dict 转为 nlohmann::json
|
|
|
|
|
|
json py_dict_to_json(py::dict d) {
|
|
|
|
|
|
json j;
|
|
|
|
|
|
for (auto item : d) {
|
|
|
|
|
|
std::string key = py::str(item.first);
|
|
|
|
|
|
py::object val = py::reinterpret_borrow<py::object>(item.second);
|
|
|
|
|
|
|
|
|
|
|
|
if (py::isinstance<py::dict>(val)) {
|
|
|
|
|
|
j[key] = py_dict_to_json(py::cast<py::dict>(val));
|
|
|
|
|
|
} else if (py::isinstance<py::list>(val)) {
|
|
|
|
|
|
py::list py_list = py::cast<py::list>(val);
|
|
|
|
|
|
json arr = json::array();
|
|
|
|
|
|
for (auto elem : py_list) {
|
|
|
|
|
|
py::object elem_obj = py::reinterpret_borrow<py::object>(elem);
|
|
|
|
|
|
if (py::isinstance<py::dict>(elem_obj)) {
|
|
|
|
|
|
arr.push_back(py_dict_to_json(py::cast<py::dict>(elem_obj)));
|
|
|
|
|
|
} else if (py::isinstance<py::int_>(elem_obj)) {
|
|
|
|
|
|
arr.push_back(py::cast<int64_t>(elem_obj));
|
|
|
|
|
|
} else if (py::isinstance<py::float_>(elem_obj)) {
|
|
|
|
|
|
arr.push_back(py::cast<double>(elem_obj));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
arr.push_back(py::str(elem_obj));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
j[key] = arr;
|
|
|
|
|
|
} else if (py::isinstance<py::int_>(val)) {
|
|
|
|
|
|
j[key] = py::cast<int64_t>(val);
|
|
|
|
|
|
} else if (py::isinstance<py::float_>(val)) {
|
|
|
|
|
|
j[key] = py::cast<double>(val);
|
|
|
|
|
|
} else if (py::isinstance<py::bool_>(val)) {
|
|
|
|
|
|
j[key] = py::cast<bool>(val);
|
|
|
|
|
|
} else if (val.is_none()) {
|
|
|
|
|
|
j[key] = nullptr;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
j[key] = py::str(val);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return j;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 辅助函数:将 nlohmann::json 转为 py::dict
|
|
|
|
|
|
py::dict json_to_py_dict(const json& j) {
|
|
|
|
|
|
py::dict d;
|
|
|
|
|
|
if (j.is_object()) {
|
|
|
|
|
|
for (auto& item : j.items()) {
|
|
|
|
|
|
std::string key = item.key();
|
|
|
|
|
|
json val = item.value();
|
|
|
|
|
|
|
|
|
|
|
|
if (val.is_object()) {
|
|
|
|
|
|
d[py::str(key)] = json_to_py_dict(val);
|
|
|
|
|
|
} else if (val.is_array()) {
|
|
|
|
|
|
py::list py_list;
|
|
|
|
|
|
for (auto& elem : val) {
|
|
|
|
|
|
if (elem.is_object()) {
|
|
|
|
|
|
py_list.append(json_to_py_dict(elem));
|
|
|
|
|
|
} else if (elem.is_number_integer()) {
|
|
|
|
|
|
py_list.append(py::int_(elem.get<int64_t>()));
|
|
|
|
|
|
} else if (elem.is_number_float()) {
|
|
|
|
|
|
py_list.append(py::float_(elem.get<double>()));
|
|
|
|
|
|
} else if (elem.is_boolean()) {
|
|
|
|
|
|
py_list.append(py::bool_(elem.get<bool>()));
|
|
|
|
|
|
} else if (elem.is_null()) {
|
|
|
|
|
|
py_list.append(py::none());
|
|
|
|
|
|
} else {
|
|
|
|
|
|
py_list.append(py::str(elem.get<std::string>()));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
d[py::str(key)] = py_list;
|
|
|
|
|
|
} else if (val.is_number_integer()) {
|
|
|
|
|
|
d[py::str(key)] = py::int_(val.get<int64_t>());
|
|
|
|
|
|
} else if (val.is_number_float()) {
|
|
|
|
|
|
d[py::str(key)] = py::float_(val.get<double>());
|
|
|
|
|
|
} else if (val.is_boolean()) {
|
|
|
|
|
|
d[py::str(key)] = py::bool_(val.get<bool>());
|
|
|
|
|
|
} else if (val.is_null()) {
|
|
|
|
|
|
d[py::str(key)] = py::none();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
d[py::str(key)] = py::str(val.get<std::string>());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return d;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 打包 TCP 数据包
|
|
|
|
|
|
py::bytes make_packet(int msg_type, py::dict body_dict) {
|
|
|
|
|
|
netcore::log_debug(std::string("make_packet msg_type=") + std::to_string(msg_type));
|
|
|
|
|
|
// 1) 将 py::dict 转为 JSON 字符串
|
|
|
|
|
|
json j = py_dict_to_json(body_dict);
|
|
|
|
|
|
std::string body_str = j.dump();
|
|
|
|
|
|
|
|
|
|
|
|
// 2) 计算 body_len 和 checksum
|
|
|
|
|
|
uint32_t body_len = body_str.size();
|
|
|
|
|
|
uint32_t checksum = body_len + msg_type;
|
|
|
|
|
|
|
|
|
|
|
|
// 3) 打包头部(大端序)
|
|
|
|
|
|
std::vector<uint8_t> packet;
|
|
|
|
|
|
packet.reserve(12 + body_len);
|
|
|
|
|
|
|
|
|
|
|
|
// body_len (big-endian, 4 bytes)
|
|
|
|
|
|
packet.push_back((body_len >> 24) & 0xFF);
|
|
|
|
|
|
packet.push_back((body_len >> 16) & 0xFF);
|
|
|
|
|
|
packet.push_back((body_len >> 8) & 0xFF);
|
|
|
|
|
|
packet.push_back(body_len & 0xFF);
|
|
|
|
|
|
|
|
|
|
|
|
// msg_type (big-endian, 4 bytes)
|
|
|
|
|
|
packet.push_back((msg_type >> 24) & 0xFF);
|
|
|
|
|
|
packet.push_back((msg_type >> 16) & 0xFF);
|
|
|
|
|
|
packet.push_back((msg_type >> 8) & 0xFF);
|
|
|
|
|
|
packet.push_back(msg_type & 0xFF);
|
|
|
|
|
|
|
|
|
|
|
|
// checksum (big-endian, 4 bytes)
|
|
|
|
|
|
packet.push_back((checksum >> 24) & 0xFF);
|
|
|
|
|
|
packet.push_back((checksum >> 16) & 0xFF);
|
|
|
|
|
|
packet.push_back((checksum >> 8) & 0xFF);
|
|
|
|
|
|
packet.push_back(checksum & 0xFF);
|
|
|
|
|
|
|
|
|
|
|
|
// 4) 追加 body
|
|
|
|
|
|
packet.insert(packet.end(), body_str.begin(), body_str.end());
|
|
|
|
|
|
|
|
|
|
|
|
netcore::log_debug(std::string("make_packet done bytes=") + std::to_string(packet.size()));
|
|
|
|
|
|
return py::bytes(reinterpret_cast<const char*>(packet.data()), packet.size());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 解析 TCP 数据包
|
|
|
|
|
|
py::tuple parse_packet(py::bytes data) {
|
|
|
|
|
|
// 1) 转换为 bytes view
|
|
|
|
|
|
py::buffer_info buf = py::buffer(data).request();
|
|
|
|
|
|
if (buf.size < 12) {
|
|
|
|
|
|
netcore::log_error(std::string("parse_packet too_short len=") + std::to_string(buf.size));
|
|
|
|
|
|
return py::make_tuple(py::none(), py::none());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const uint8_t* ptr = static_cast<const uint8_t*>(buf.ptr);
|
|
|
|
|
|
|
|
|
|
|
|
// 2) 解析头部(大端序)
|
|
|
|
|
|
uint32_t body_len = (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3];
|
|
|
|
|
|
uint32_t msg_type = (ptr[4] << 24) | (ptr[5] << 16) | (ptr[6] << 8) | ptr[7];
|
|
|
|
|
|
uint32_t checksum = (ptr[8] << 24) | (ptr[9] << 16) | (ptr[10] << 8) | ptr[11];
|
|
|
|
|
|
|
|
|
|
|
|
// 3) 校验 checksum(可选,你现有代码不强制校验)
|
|
|
|
|
|
// if (checksum != (body_len + msg_type)) {
|
|
|
|
|
|
// return py::make_tuple(py::none(), py::none());
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// 4) 检查长度
|
|
|
|
|
|
uint32_t expected_len = 12 + body_len;
|
|
|
|
|
|
if (buf.size < expected_len) {
|
|
|
|
|
|
// 半包
|
|
|
|
|
|
netcore::log_warn(std::string("parse_packet incomplete got=") + std::to_string(buf.size) +
|
|
|
|
|
|
" expected=" + std::to_string(expected_len));
|
|
|
|
|
|
return py::make_tuple(py::none(), py::none());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5) 防御性检查:如果 data 比预期长,说明可能有粘包
|
|
|
|
|
|
// (只解析第一个包,忽略多余数据)
|
|
|
|
|
|
if (buf.size > expected_len) {
|
|
|
|
|
|
netcore::log_warn(std::string("parse_packet concat got=") + std::to_string(buf.size) +
|
|
|
|
|
|
" expected=" + std::to_string(expected_len) +
|
|
|
|
|
|
" body_len=" + std::to_string(body_len) +
|
|
|
|
|
|
" msg_type=" + std::to_string(msg_type));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 6) 提取 body 并解析 JSON
|
|
|
|
|
|
std::string body_str(reinterpret_cast<const char*>(ptr + 12), body_len);
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
json j = json::parse(body_str);
|
|
|
|
|
|
py::dict body_dict = json_to_py_dict(j);
|
|
|
|
|
|
return py::make_tuple(py::int_(msg_type), body_dict);
|
|
|
|
|
|
} catch (const json::parse_error& e) {
|
|
|
|
|
|
// JSON 解析失败,返回 raw(兼容你现有的逻辑)
|
|
|
|
|
|
netcore::log_error(std::string("parse_packet json_parse_error: ") + e.what());
|
|
|
|
|
|
py::dict raw_dict;
|
|
|
|
|
|
raw_dict["raw"] = body_str;
|
|
|
|
|
|
return py::make_tuple(py::int_(msg_type), raw_dict);
|
|
|
|
|
|
} catch (const std::exception& e) {
|
|
|
|
|
|
netcore::log_error(std::string("parse_packet json_parse_error: ") + e.what());
|
|
|
|
|
|
py::dict raw_dict;
|
|
|
|
|
|
raw_dict["raw"] = body_str;
|
|
|
|
|
|
return py::make_tuple(py::int_(msg_type), raw_dict);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
PYBIND11_MODULE(archery_netcore, m) {
|
|
|
|
|
|
m.doc() = "Archery net core (native, pybind11).";
|
|
|
|
|
|
|
|
|
|
|
|
// Optional: configure native logger from Python.
|
|
|
|
|
|
// Default log file: /maixapp/apps/t11/netcore.log
|
|
|
|
|
|
m.def("set_log_file", [](const std::string& path) { netcore::set_log_file(path); }, py::arg("path"));
|
|
|
|
|
|
m.def("set_log_level", [](int level) {
|
|
|
|
|
|
if (level < 0) level = 0;
|
|
|
|
|
|
if (level > 3) level = 3;
|
|
|
|
|
|
netcore::set_log_level(static_cast<netcore::LogLevel>(level));
|
|
|
|
|
|
}, py::arg("level"));
|
|
|
|
|
|
m.def("log_test", [](const std::string& msg) {
|
|
|
|
|
|
netcore::log_info(std::string("log_test: ") + msg);
|
|
|
|
|
|
}, py::arg("msg"));
|
|
|
|
|
|
|
|
|
|
|
|
m.def("make_packet", &make_packet,
|
|
|
|
|
|
"Pack TCP packet: header (len+type+checksum) + JSON body",
|
|
|
|
|
|
py::arg("msg_type"), py::arg("body_dict"));
|
|
|
|
|
|
|
|
|
|
|
|
m.def("parse_packet", &parse_packet,
|
|
|
|
|
|
"Parse TCP packet, return (msg_type, body_dict)");
|
|
|
|
|
|
|
|
|
|
|
|
m.def("get_config", &get_config, "Get system configuration");
|
|
|
|
|
|
|
|
|
|
|
|
// Minimal demo: return actions for inner_cmd=41 (manual trigger + ack)
|
|
|
|
|
|
m.def("actions_for_inner_cmd", [](int inner_cmd) {
|
|
|
|
|
|
py::list actions;
|
|
|
|
|
|
|
|
|
|
|
|
if (inner_cmd == 41) {
|
|
|
|
|
|
// 1) set manual trigger flag
|
|
|
|
|
|
{
|
|
|
|
|
|
py::dict a;
|
|
|
|
|
|
a["type"] = "SET_FLAG";
|
|
|
|
|
|
py::dict args;
|
|
|
|
|
|
args["name"] = "manual_trigger_flag";
|
|
|
|
|
|
args["value"] = true;
|
|
|
|
|
|
a["args"] = args;
|
|
|
|
|
|
actions.append(a);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2) enqueue trigger_ack
|
|
|
|
|
|
{
|
|
|
|
|
|
py::dict a;
|
|
|
|
|
|
a["type"] = "ENQUEUE";
|
|
|
|
|
|
py::dict args;
|
|
|
|
|
|
args["msg_type"] = 2;
|
|
|
|
|
|
args["high"] = false;
|
|
|
|
|
|
py::dict body;
|
|
|
|
|
|
body["result"] = "trigger_ack";
|
|
|
|
|
|
args["body"] = body;
|
|
|
|
|
|
a["args"] = args;
|
|
|
|
|
|
actions.append(a);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return actions;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|