AntiClipSettings/DeviceConnection.cpp

694 lines
25 KiB
C++
Raw Permalink 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 <QFileInfo>
#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::~DeviceConnection() {
close();
}
void DeviceConnection::close() {
if (m_otaTimer != nullptr) {
m_otaTimer->deleteLater();
m_otaTimer = nullptr;
}
if (m_timerId > 0) {
killTimer(m_timerId);
m_timerId = -1;
}
if (m_commandSocket != nullptr) {
m_commandSocket->deleteLater();
m_commandSocket = nullptr;
}
if (m_h264Socket != nullptr) {
m_h264Socket->deleteLater();
m_h264Socket = nullptr;
}
while (!m_requests.empty()) {
m_requests.pop();
}
}
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) {
close();
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) {
m_videoEnabled = 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 &firmware, const QString &file) {
m_otaProgress = 0;
emit otaProgressChanged(true, m_otaProgress, "正在向设备发起OTA请求......");
if (m_timerId > 0) {
killTimer(m_timerId);
m_timerId = -1;
}
if (!m_requests.empty()) {
m_requests.pop();
}
Task task;
task.command = "a22devicefirmware_setdata";
task.task = [this, file, firmware]() {
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"] = firmware.toStdString();
QFileInfo fileInfo(file);
data["datasize"] = fileInfo.size();
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;
};
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,
m_otaProgress < 98 ? "向设备发送升级固件......"
: "升级固件发送完成,等待设备校验升级固件......");
if ((m_sendedSize < m_uploadBuffer.size()) && isConnected()) {
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;
if (m_otaTimer == nullptr) {
m_otaTimer = new QTimer(this);
m_otaTimer->setSingleShot(true);
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
Qt::ConnectionType type = Qt::SingleShotConnection;
#else
Qt::ConnectionType type = Qt::UniqueConnection;
#endif
m_otaTimer->callOnTimeout(
this,
[this]() {
emit otaProgressChanged(false, m_otaProgress, "升级超时,请检查设备并重新尝试...");
m_otaProgress = -1;
m_commandSocket->close();
m_h264Socket->close();
},
type);
m_otaTimer->start(60 * 1000);
}
}
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);
}
}
requestNetworkInfomation();
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, "设备已进入升级状态......");
QTimer::singleShot(0, this, [this]() { transferBinContent(); });
} else if (value == "2") {
bool isWireConnect = true;
if (data.contains("is_eth")) {
isWireConnect = data.at("is_eth").as_int64() >= 1;
}
LOG(info) << "md5 check finished";
m_otaProgress = 99;
QTimer::singleShot(0, this, [this]() {
m_commandSocket->close(); // 等待设备重新上线后,发起广播。由搜索服务触发重连
m_h264Socket->close();
});
if (isWireConnect) {
emit otaProgressChanged(true, m_otaProgress, "设备正在升级中,请稍后......");
} else {
m_otaTimer->stop(); // 这里不需要再超时了
emit otaProgressChanged(true, 100, "设备正在升级中请于一分钟后重新连接wifi搜索设备");
}
} else {
const char *message = nullptr;
if (value == "3") {
message = "升级固件MD5校验错误(3)";
} else if (value == "4") {
message = "升级固件大小错误(4)";
} else if (value == "5") {
message = "升级固件太大(5)";
} else if (value == "6") {
message = "升级固件版本不匹配(6)";
}
QString tip;
if (message == nullptr) {
tip = QString("升级失败,错误码: %1").arg(value.c_str());
} else {
tip = QString("升级失败: %1").arg(message);
}
emit otaProgressChanged(false, m_otaProgress, tip);
}
} 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_videoEnabled) {
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, "网络断开,设备升级失败!");
}
m_h264Socket->close();
}
}
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());
LOG(info) << "DeviceConnection::onErrorOccurred" << socketError;
}
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++;
}
}