AntiClipSettings/DeviceConnection.cpp
2024-08-22 10:48:28 +08:00

650 lines
24 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "DeviceConnection.h"
#include "BoostLog.h"
#include "StringUtility.h"
#include <QPointF>
#include <QTimer>
#include <WinSock2.h>
#include <boost/json/object.hpp>
#include <boost/json/parse.hpp>
#include <boost/json/serialize.hpp>
#include <filesystem>
#include <fstream>
#include <mbedtls/md5.h>
DeviceConnection::DeviceConnection(QObject *parent) : QObject{parent} {
}
DeviceConnection::Infomation DeviceConnection::infomation() const {
return m_infomation;
}
bool DeviceConnection::isConnected() const {
bool ret = false;
if (m_commandSocket != nullptr) {
// LOG(info) << "DeviceConnection::isConnected " << m_commandSocket->isValid();
ret = m_commandSocket->isValid() && (m_commandSocket->state() == QTcpSocket::ConnectedState);
}
return ret;
}
void DeviceConnection::connect(const Infomation &infomation) {
m_infomation = infomation;
m_commandSocket = new QTcpSocket(this);
QObject::connect(m_commandSocket, &QTcpSocket::disconnected, this, &DeviceConnection::onDisconnected);
QObject::connect(m_commandSocket, &QTcpSocket::errorOccurred, this, &DeviceConnection::onErrorOccurred);
m_h264Socket = new QTcpSocket(this);
QObject::connect(m_commandSocket, &QTcpSocket::connected, this, &DeviceConnection::onConnected);
QObject::connect(m_h264Socket, &QTcpSocket::connected, this, &DeviceConnection::onConnected);
QObject::connect(m_h264Socket, &QTcpSocket::readyRead, this, &DeviceConnection::onH264ReadyRead);
QObject::connect(m_commandSocket, &QTcpSocket::readyRead, this, &DeviceConnection::onCommandReadyRead);
LOG(info) << "connect to " << infomation.ip.toStdString();
m_commandSocket->connectToHost(infomation.ip, 8000);
m_h264Socket->connectToHost(infomation.ip, 8000);
}
DeviceConnection::Area DeviceConnection::area() const {
return m_area;
}
NetworkInfomation DeviceConnection::networkInfomation() const {
return m_networkInfomation;
}
void DeviceConnection::setLiveStreamEnabled(bool enabled) {
boost::json::object request;
request["func"] = enabled ? "openlivestream_setdata" : "closelivestream_setdata";
request["deviceid"] = "0";
boost::json::object data;
data["value"] = "1";
request["data"] = std::move(data);
auto text = boost::json::serialize(request);
m_h264Socket->write(text.data(), text.size());
}
void DeviceConnection::requestOpenDoorArea() {
Task task;
task.command = "a03opendoor1_getdata";
task.task = [this]() {
boost::json::object request;
request["func"] = "a03opendoor1_getdata";
request["deviceid"] = "0";
boost::json::object data;
request["data"] = std::move(data);
auto text = boost::json::serialize(request);
m_commandSocket->write(text.data(), text.size());
};
if (m_requests.empty()) {
task.task();
}
m_requests.push(task);
}
QFuture<bool> DeviceConnection::updateOpenDoorAreaPoints(AreaWay way, const QList<QPointF> &points) {
Task task;
task.command = "a03opendoor1_setdata";
task.task = [this, way, points]() {
boost::json::object request;
request["func"] = "a03opendoor1_setdata";
request["deviceid"] = "0";
boost::json::object data;
const char *value = "0";
if (way == FullArea) {
value = "1";
} else if (way == Quadrangle) {
value = "2";
}
data["value"] = value;
boost::json::array pointArray;
for (auto &p : points) {
boost::json::object point;
point["x"] = p.x();
point["y"] = p.y();
pointArray.push_back(std::move(point));
}
data["points"] = std::move(pointArray);
request["data"] = std::move(data);
auto text = boost::json::serialize(request);
m_commandSocket->write(text.data(), text.size());
};
task.future = std::make_shared<QFutureInterface<bool>>();
if (m_requests.empty()) {
task.task();
}
auto ret = task.future->future();
m_requests.push(task);
return ret;
}
void DeviceConnection::requestShieldedArea() {
Task task;
task.command = "a03opendoor4_getdata";
task.task = [this]() {
boost::json::object request;
request["func"] = "a03opendoor4_getdata";
request["deviceid"] = "0";
boost::json::object data;
request["data"] = std::move(data);
auto text = boost::json::serialize(request);
m_commandSocket->write(text.data(), text.size());
};
if (m_requests.empty()) {
task.task();
}
m_requests.push(task);
}
void DeviceConnection::updateShieldedAreaPoints(bool enabled, const QList<QPointF> &points) {
Task task;
task.command = "a03opendoor4_setdata";
task.task = [this, enabled, points]() {
boost::json::object request;
request["func"] = "a03opendoor4_setdata";
request["deviceid"] = "0";
boost::json::object data;
data["value"] = enabled ? "1" : "0";
boost::json::array pointArray;
for (auto &p : points) {
boost::json::object point;
point["x"] = p.x();
point["y"] = p.y();
pointArray.push_back(std::move(point));
}
data["points"] = std::move(pointArray);
request["data"] = std::move(data);
auto text = boost::json::serialize(request);
m_commandSocket->write(text.data(), text.size());
};
if (m_requests.empty()) {
task.task();
}
m_requests.push(task);
}
void DeviceConnection::requestAntiClipArea() {
Task task;
task.command = "a03opendoor5_getdata";
task.task = [this]() {
boost::json::object request;
request["func"] = "a03opendoor5_getdata";
request["deviceid"] = "0";
boost::json::object data;
request["data"] = std::move(data);
auto text = boost::json::serialize(request);
m_commandSocket->write(text.data(), text.size());
};
if (m_requests.empty()) {
task.task();
}
m_requests.push(task);
}
void DeviceConnection::updateAntiClipAreaPoints(bool enabled, const QList<QPointF> &points) {
Task task;
task.command = "a03opendoor5_setdata";
task.task = [this, enabled, points]() {
boost::json::object request;
request["func"] = "a03opendoor5_setdata";
request["deviceid"] = "0";
boost::json::object data;
data["value"] = enabled ? "1" : "0";
boost::json::array pointArray;
for (auto &p : points) {
boost::json::object point;
point["x"] = p.x();
point["y"] = p.y();
pointArray.push_back(std::move(point));
}
data["points"] = std::move(pointArray);
request["data"] = std::move(data);
auto text = boost::json::serialize(request);
m_commandSocket->write(text.data(), text.size());
LOG(info) << "updateAntiClipAreaPoints";
};
if (m_requests.empty()) {
task.task();
}
m_requests.push(task);
}
void DeviceConnection::requestResolution(Resolution resolution) {
Task task;
task.command = "quality_setdata";
task.task = [this, resolution]() {
boost::json::object request;
request["func"] = "quality_setdata";
request["deviceid"] = "0";
boost::json::object data;
data["value"] = static_cast<int>(resolution);
request["data"] = std::move(data);
auto text = boost::json::serialize(request);
m_commandSocket->write(text.data(), text.size());
LOG(info) << "requestResolution";
};
if (m_requests.empty()) {
task.task();
}
m_requests.push(task);
}
void DeviceConnection::requestVersion() {
Task task;
task.command = "a15devicedetail_getdata";
task.task = [this]() {
boost::json::object request;
request["func"] = "a15devicedetail_getdata";
request["deviceid"] = "0";
boost::json::object data;
request["data"] = std::move(data);
auto text = boost::json::serialize(request);
m_commandSocket->write(text.data(), text.size());
LOG(info) << "requestVersion";
};
if (m_requests.empty()) {
task.task();
}
m_requests.push(task);
}
void DeviceConnection::requestNetworkInfomation() {
Task task;
task.command = "netconfig_getdata";
task.task = [this]() {
boost::json::object request;
request["func"] = "netconfig_getdata";
request["deviceid"] = "0";
boost::json::object data;
request["data"] = std::move(data);
auto text = boost::json::serialize(request);
m_commandSocket->write(text.data(), text.size());
LOG(info) << "requestNetworkInfomation";
};
if (m_requests.empty()) {
task.task();
}
m_requests.push(task);
}
QFuture<bool> DeviceConnection::updateNetworkInfomation(bool dhcp, const QString &ip, const QString &netmask,
const QString &gateway, const QString &dns) {
Task task;
task.command = "netconfig_setdata";
task.task = [this, dhcp, ip, netmask, gateway, dns]() {
boost::json::object request;
request["func"] = "netconfig_setdata";
request["deviceid"] = "0";
boost::json::object data;
data["type"] = dhcp ? "dhcp" : "static";
data["ip"] = ip.toStdString();
data["netmask"] = netmask.toStdString();
data["gateway"] = gateway.toStdString();
data["dns"] = dns.toStdString();
request["data"] = std::move(data);
auto text = boost::json::serialize(request);
m_commandSocket->write(text.data(), text.size());
LOG(info) << "requestUpdateNetworkInfomation";
};
task.future = std::make_shared<QFutureInterface<bool>>();
if (m_requests.empty()) {
task.task();
}
auto ret = task.future->future();
m_requests.push(task);
return ret;
}
void DeviceConnection::requestOta(const QString &file) {
m_otaProgress = 0;
emit otaProgressChanged(true, m_otaProgress, "正在向设备发起OTA请求......");
if (m_timerId > 0) {
killTimer(m_timerId);
m_timerId = -1;
}
Task task;
task.command = "a22devicefirmware_setdata";
task.task = [this, file]() {
std::ifstream ifs(Amass::StringUtility::UTF8ToGBK(file.toStdString()), std::ifstream::binary);
m_uploadBuffer = std::vector<uint8_t>((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
m_sendedSize = 0;
unsigned char md5[16];
mbedtls_md5_context context;
mbedtls_md5_init(&context);
mbedtls_md5_starts(&context);
mbedtls_md5_update(&context, m_uploadBuffer.data(), m_uploadBuffer.size());
mbedtls_md5_finish(&context, md5);
mbedtls_md5_free(&context);
std::stringstream oss;
oss << std::hex << std::setfill('0');
for (int i = 0; i < 16; i++) {
oss << std::setw(2) << static_cast<int>(md5[i]);
}
boost::json::object request;
request["func"] = "a22devicefirmware_setdata";
request["deviceid"] = "0";
boost::json::object data;
data["target_linux04_firmware"] = "RD_T009_V21R003B002";
data["datasize"] = std::filesystem::file_size(file.toStdString());
data["md5"] = oss.str();
request["data"] = std::move(data);
auto text = boost::json::serialize(request);
m_commandSocket->write(text.data(), text.size());
LOG(info) << "requestOta: " << text << ": " << text.size();
};
if (m_requests.empty()) {
task.task();
}
m_requests.push(task);
}
void DeviceConnection::transferBinContent() {
constexpr int SliceSize = 1024;
constexpr int WaitMd5CheckTime = 3000; // ms
if (m_sendedSize >= m_uploadBuffer.size()) return;
char buffer[1 + sizeof(int32_t) + 1024];
int sendSize = SliceSize;
if ((m_sendedSize + SliceSize) > m_uploadBuffer.size()) {
sendSize = m_uploadBuffer.size() - m_sendedSize;
}
memcpy(buffer + 1 + sizeof(int32_t), m_uploadBuffer.data() + m_sendedSize, sendSize);
buffer[0] = ':';
auto contentSize = reinterpret_cast<int32_t *>(&buffer[1]);
*contentSize = htonl(sendSize);
m_commandSocket->write(buffer, sendSize + 1 + sizeof(uint32_t));
m_sendedSize += sendSize;
auto fileProgress = static_cast<float>(m_sendedSize) / m_uploadBuffer.size();
m_otaProgress = 2 + 96 * fileProgress;
emit otaProgressChanged(true, m_otaProgress, "向设备发送OTA升级文件......");
if (m_sendedSize < m_uploadBuffer.size()) {
QTimer::singleShot(0, this, &DeviceConnection::transferBinContent);
} else if (m_sendedSize >= m_uploadBuffer.size()) {
LOG(info) << "transfer ota file finished, wait " << WaitMd5CheckTime
<< " ms for send check, total sended size: " << m_sendedSize;
QTimer::singleShot(WaitMd5CheckTime, this, [this]() {
boost::json::object request;
request["func"] = "a22devicefirmware_setdata";
request["deviceid"] = "0";
boost::json::object data;
data["target_linux04_firmware"] = "RD_T009_V21R003B001";
request["data"] = std::move(data);
auto text = boost::json::serialize(request);
m_commandSocket->write(text.data(), text.size());
LOG(info) << "request md5 check result: " << text;
});
}
}
QString DeviceConnection::handleCommand(const std::string_view &replyText, const Task *task) {
QString ret;
boost::system::error_code error;
auto replyValue = boost::json::parse(replyText, error);
if (error) {
LOG(error) << "prase [" << replyText << "] failed, message: " << error.message();
return ret;
}
auto &reply = replyValue.as_object();
auto &function = reply.at("func").as_string();
if (function == "a03opendoor1_getdata") {
auto &data = reply.at("data").as_object();
auto &value = data.at("value").as_string();
auto &pointArray = data.at("points").as_array();
QList<QPointF> points;
for (auto &p : pointArray) {
QPointF point;
auto &obj = p.as_object();
point.setX(obj.at("x").as_double());
point.setY(obj.at("y").as_double());
points.push_back(point);
}
AreaWay way = Diabled;
if (value == "1") {
way = FullArea;
} else if (value == "2") {
way = Quadrangle;
}
m_area.openDoorAreaWay = way;
m_area.openDoorArea = points;
emit openDoorAreaChanged(way, points);
} else if (function == "a03opendoor4_getdata") {
auto &data = reply.at("data").as_object();
auto &value = data.at("value").as_string();
auto &pointArray = data.at("points").as_array();
QList<QPointF> points;
for (auto &p : pointArray) {
QPointF point;
auto &obj = p.as_object();
point.setX(obj.at("x").as_double());
point.setY(obj.at("y").as_double());
points.push_back(point);
}
m_area.shieldedAreaEnabled = value == "1";
m_area.shieldedArea = points;
emit shieldedAreaChanged(value == "1", points);
} else if (function == "a03opendoor5_getdata") {
auto &data = reply.at("data").as_object();
auto &value = data.at("value").as_string();
auto &pointArray = data.at("points").as_array();
QList<QPointF> points;
for (auto &p : pointArray) {
QPointF point;
auto &obj = p.as_object();
point.setX(obj.at("x").as_double());
point.setY(obj.at("y").as_double());
points.push_back(point);
}
m_area.antiClipAreaEnabled = value == "1";
m_area.antiClipArea = points;
emit antiClipAreaChanged(value == "1", points);
} else if (function == "netconfig_getdata") {
auto &data = reply.at("data").as_object();
m_networkInfomation.dhcp = data.at("type").as_string() == "dhcp";
m_networkInfomation.ip = data.at("ip").as_string().c_str();
m_networkInfomation.gateway = data.at("gateway").as_string().c_str();
m_networkInfomation.netmask = data.at("netmask").as_string().c_str();
if (data.contains("dns")) {
m_networkInfomation.dns = data.at("dns").as_string().c_str();
}
emit networkInfomationChanged(m_networkInfomation);
LOG(info) << replyText;
} else if (function == "a15devicedetail_getdata") {
auto &data = reply.at("data").as_object();
auto firmware = QString::fromStdString(std::string(data.at("linux04_firmware").as_string()));
if (m_infomation.firmwareVersion != firmware) {
m_infomation.firmwareVersion = firmware;
emit firmwareChanged(m_infomation.firmwareVersion);
}
} else if (function == "a03opendoor5_setdata") {
requestAntiClipArea();
} else if (function == "a03opendoor4_setdata") {
requestShieldedArea();
} else if (function == "a03opendoor1_setdata") {
if ((task != nullptr) && (task->command.toStdString() == function)) {
if (task->timeoutTimer) {
task->timeoutTimer->stop();
}
bool status = true;
if (task->future) {
task->future->reportFinished(&status);
}
}
requestOpenDoorArea();
} else if (function == "netconfig_setdata") {
if ((task != nullptr) && (task->command.toStdString() == function)) {
if (task->timeoutTimer) {
task->timeoutTimer->stop();
}
bool status = true;
if (task->future) {
task->future->reportFinished(&status);
}
}
LOG(info) << replyText;
} else if (function == "a22devicefirmware_setdata") {
LOG(warning) << "ota reply: " << replyText;
auto &data = reply.at("data").as_object();
auto &value = data.at("value").as_string();
if (value == "1") {
m_otaProgress = 1;
emit otaProgressChanged(true, m_otaProgress, "设备已进入OTA升级状态......");
QTimer::singleShot(0, this, [this]() { transferBinContent(); });
} else if (value == "2") {
LOG(info) << "md5 check finished";
m_otaProgress = 99;
emit otaProgressChanged(true, m_otaProgress, "设备正在升级中,请稍后......");
QTimer::singleShot(0, this, [this]() {
m_commandSocket->close();
m_h264Socket->close();
if (m_otaTimer == nullptr) {
m_otaTimer = new QTimer(this);
m_otaTimer->setSingleShot(true);
}
m_otaTimer->callOnTimeout(
this,
[this]() {
emit otaProgressChanged(false, m_otaProgress, "升级超时,请检查设备并重新尝试...");
m_otaProgress = -1;
m_commandSocket->close();
m_h264Socket->close();
},
Qt::SingleShotConnection);
m_otaTimer->start(120 * 1000);
});
QTimer::singleShot(25000, this, [this]() {
LOG(info) << "try connect after ota.";
m_commandSocket->connectToHost(m_infomation.ip, 8000);
m_h264Socket->connectToHost(m_infomation.ip, 8000);
});
} else {
emit otaProgressChanged(false, m_otaProgress, QString("升级失败,错误码: %1").arg(value.c_str()));
}
} else {
LOG(warning) << "unknown reply: " << replyText;
}
return QString::fromStdString(std::string(std::move(function)));
}
void DeviceConnection::onConnected() {
auto socket = dynamic_cast<QTcpSocket *>(sender());
if (socket == m_commandSocket) {
requestVersion();
requestOpenDoorArea();
requestShieldedArea();
requestAntiClipArea();
requestNetworkInfomation();
emit connected();
m_timerId = startTimer(2500);
if (m_otaProgress == 99) {
m_otaProgress = -1;
emit otaProgressChanged(true, 100, "设备升级成功!");
}
if (m_otaTimer != nullptr) {
m_otaTimer->stop();
}
} else if (socket == m_h264Socket) {
if (m_otaProgress >= 99) {
setLiveStreamEnabled(true);
}
}
}
void DeviceConnection::onDisconnected() {
auto socket = dynamic_cast<QTcpSocket *>(sender());
if (socket == m_commandSocket) {
if (m_timerId > 0) {
killTimer(m_timerId);
m_timerId = -1;
}
emit disconnected();
if ((m_otaProgress >= 0) && (m_otaProgress <= 98)) {
m_otaProgress = -1;
emit otaProgressChanged(false, m_otaProgress, "网络断开,设备升级失败!");
}
}
}
void DeviceConnection::setH264FrameCallback(H264FrameCallback &&callback) {
m_frameCallback = std::move(callback);
}
void DeviceConnection::onH264ReadyRead() {
auto data = m_h264Socket->readAll();
m_h264Buffer.push_back(data);
while (!m_h264Buffer.isEmpty()) {
auto packageSize = ntohl(*reinterpret_cast<uint32_t *>(m_h264Buffer.data()));
if (m_h264Buffer.size() < (packageSize + sizeof(uint32_t))) break;
// LOG(info) << "onH264ReadyRead " << data.size() << " " << packageSize;
if (m_receivedFirstJsonReply) {
if (m_frameCallback) {
m_frameCallback(m_h264Buffer.data() + sizeof(uint32_t), packageSize);
}
} else {
LOG(info) << "h264 reply: " << m_h264Buffer.data() + sizeof(uint32_t);
m_receivedFirstJsonReply = true;
}
m_h264Buffer.remove(0, packageSize + sizeof(uint32_t));
}
}
void DeviceConnection::onCommandReadyRead() {
auto data = m_commandSocket->readAll();
m_commandBuffer.push_back(data);
while (!m_commandBuffer.isEmpty()) {
auto packageSize = ntohl(*reinterpret_cast<uint32_t *>(m_commandBuffer.data()));
if (m_commandBuffer.size() < (packageSize + sizeof(uint32_t))) break;
auto command = handleCommand(std::string_view(m_commandBuffer.data() + sizeof(uint32_t), packageSize),
m_requests.empty() ? nullptr : &m_requests.front());
m_commandBuffer.remove(0, packageSize + sizeof(uint32_t));
if (!m_requests.empty()) {
auto &task = m_requests.front();
if (task.command == command) {
m_requests.pop();
}
if (!m_requests.empty()) {
m_requests.front().task();
}
}
}
}
void DeviceConnection::onErrorOccurred(QAbstractSocket::SocketError socketError) {
auto socket = dynamic_cast<QTcpSocket *>(sender());
qDebug() << "DeviceConnection::onErrorOccurred" << socketError;
if (m_otaProgress == 99) { // 99 表示设备正在OTA重启需要重连
if (socket->state() == QTcpSocket::UnconnectedState) {
LOG(info) << "try reconnect after ota.";
socket->connectToHost(m_infomation.ip, 8000);
}
}
}
void DeviceConnection::timerEvent(QTimerEvent *event) {
if (isConnected()) {
int index = heartbeats % 3;
if (index == 0) {
requestOpenDoorArea();
} else if (index == 1) {
requestShieldedArea();
} else if (index == 2) {
requestAntiClipArea();
}
heartbeats++;
}
}