Add API V2 parallel to V1

master
Simon Stürz 2022-07-21 11:48:56 +02:00
parent 094f2e14ef
commit 6ddb84de97
2 changed files with 533 additions and 18 deletions

View File

@ -171,7 +171,15 @@ void IntegrationPluginGoECharger::setupThing(ThingSetupInfo *info)
}
break;
case ApiVersion2:
if (thing->paramValue(goeHomeThingUseMqttParamTypeId).toBool()) {
// Verify mqtt client and set it up
qCDebug(dcGoECharger()) << "Setup using MQTT connection for" << thing;
reconfigureMqttChannelV2(thing);
} else {
// Since we are not using mqtt, we are done with the setup, the refresh timer will be configured in post setup
qCDebug(dcGoECharger()) << "Setup using HTTP finished successfully";
updateV2(thing, statusMap);
}
break;
}
});
@ -210,10 +218,18 @@ void IntegrationPluginGoECharger::postSetupThing(Thing *thing)
void IntegrationPluginGoECharger::thingRemoved(Thing *thing)
{
// Cleanup mqtt channels if set up
if (m_mqttChannelsV1.contains(thing)) {
hardwareManager()->mqttProvider()->releaseChannel(m_mqttChannelsV1.take(thing));
switch (getApiVersion(thing)) {
case ApiVersion1:
if (m_mqttChannelsV1.contains(thing)) {
hardwareManager()->mqttProvider()->releaseChannel(m_mqttChannelsV1.take(thing));
}
break;
case ApiVersion2:
break;
}
// Cleanup possible pending replies
if (m_pendingReplies.contains(thing) && m_pendingReplies.value(thing)) {
m_pendingReplies.take(thing)->abort();
@ -230,8 +246,8 @@ void IntegrationPluginGoECharger::executeAction(ThingActionInfo *info)
{
Thing *thing = info->thing();
Action action = info->action();
QHostAddress address = getHostAddress(thing);
ApiVersion apiVersion = getApiVersion(thing);
QHostAddress address = getHostAddress(thing);
if (thing->thingClassId() != goeHomeThingClassId) {
info->finish(Thing::ThingErrorThingClassNotFound);
@ -245,7 +261,7 @@ void IntegrationPluginGoECharger::executeAction(ThingActionInfo *info)
}
switch (apiVersion) {
case ApiVersion1: {
case ApiVersion1:
if (action.actionTypeId() == goeHomePowerActionTypeId) {
bool power = action.paramValue(goeHomePowerActionPowerParamTypeId).toBool();
qCDebug(dcGoECharger()) << "Setting charging allowed to" << power;
@ -265,12 +281,84 @@ void IntegrationPluginGoECharger::executeAction(ThingActionInfo *info)
info->finish(Thing::ThingErrorActionTypeNotFound);
}
break;
}
case ApiVersion2:
// TODO
if (action.actionTypeId() == goeHomePowerActionTypeId) {
bool power = action.paramValue(goeHomePowerActionPowerParamTypeId).toBool();
qCDebug(dcGoECharger()) << "Setting charging allowed to" << power;
QUrlQuery configuration;
configuration.addQueryItem("frc", (power ? "0": "1"));
QNetworkRequest request = buildConfigurationRequestV2(address, configuration);
QNetworkReply *reply = hardwareManager()->networkManager()->sendCustomRequest(request, "SET");
connect(reply, &QNetworkReply::finished, info, [=](){
if (reply->error() != QNetworkReply::NoError) {
qCWarning(dcGoECharger()) << "Execute action failed. HTTP reply returned error:" << thing->name() << reply->errorString() << reply->readAll();
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("The wallbox does not seem to be reachable."));
return;
}
QByteArray data = reply->readAll();
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(dcGoECharger()) << "Execute action failed. Failed to parse data for thing " << thing->name() << qUtf8Printable(data) << error.errorString();
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The wallbox returned invalid data."));
return;
}
QVariantMap responseCode = jsonDoc.toVariant().toMap();
if (responseCode.value("frc", false).toBool()) {
qCDebug(dcGoECharger()) << "Execute action finished successfully. Power" << power;
info->finish(Thing::ThingErrorNoError);
thing->setStateValue("power", power);
} else {
qCWarning(dcGoECharger()) << "Action finished with error:" << responseCode.value("frc").toString();
info->finish(Thing::ThingErrorHardwareFailure);
}
});
return;
} else if (action.actionTypeId() == goeHomeMaxChargingCurrentActionTypeId) {
uint ampere = action.paramValue(goeHomeMaxChargingCurrentActionMaxChargingCurrentParamTypeId).toUInt();
qCDebug(dcGoECharger()) << "Setting max charging current to" << ampere << "A";
// FIXME: check if we can use amx since it is better for pv charging, not all version seen implement amx
// Maybe check if the user sets it or a automatism
QUrlQuery configuration;
configuration.addQueryItem("amp", QString::number(ampere));
QNetworkRequest request = buildConfigurationRequestV2(address, configuration);
QNetworkReply *reply = hardwareManager()->networkManager()->sendCustomRequest(request, "SET");
connect(reply, &QNetworkReply::finished, info, [=](){
if (reply->error() != QNetworkReply::NoError) {
qCWarning(dcGoECharger()) << "Execute action failed. HTTP status reply returned error:" << thing->name() << reply->errorString() << reply->readAll();
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("The wallbox does not seem to be reachable."));
return;
}
QByteArray data = reply->readAll();
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(dcGoECharger()) << "Execute action failed. Failed to parse data for thing " << thing->name() << qUtf8Printable(data) << error.errorString();
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The wallbox returned invalid data."));
return;
}
QVariantMap responseCode = jsonDoc.toVariant().toMap();
if (responseCode.value("amp", false).toBool()) {
qCDebug(dcGoECharger()) << "Execute action finished successfully. Charging current" << ampere;
info->finish(Thing::ThingErrorNoError);
thing->setStateValue("maxChargingCurrent", ampere);
} else {
qCWarning(dcGoECharger()) << "Action finished with error:" << responseCode.value("amp").toString();
info->finish(Thing::ThingErrorHardwareFailure);
}
});
return;
} else {
info->finish(Thing::ThingErrorActionTypeNotFound);
}
break;
}
}
@ -313,20 +401,40 @@ void IntegrationPluginGoECharger::setupGoeHome(ThingSetupInfo *info)
QVariantMap statusMap = jsonDoc.toVariant().toMap();
if (thing->paramValue(goeHomeThingUseMqttParamTypeId).toBool()) {
// Verify mqtt client and set it up
qCDebug(dcGoECharger()) << "Setup using MQTT connection for" << thing;
qCDebug(dcGoECharger()) << "Setting up using MQTT connection for" << thing;
setupMqttChannelV1(info, address, statusMap);
} else {
// Since we are not using mqtt, we are done with the setup, the refresh timer will be configured in post setup
info->finish(Thing::ThingErrorNoError);
qCDebug(dcGoECharger()) << "Setup using HTTP finished successfully";
thing->setStateValue("connected", true);
updateV1(thing, statusMap);
thing->setStateValue(goeHomeConnectedStateTypeId, true);
}
break;
}
case ApiVersion2:
// TODO
// Handle reconfigure
if (m_mqttChannelsV2.contains(thing))
hardwareManager()->mqttProvider()->releaseChannel(m_mqttChannelsV2.take(thing));
if (m_pendingReplies.contains(thing))
m_pendingReplies.take(thing)->abort();
qCDebug(dcGoECharger()) << "Initial status map" << qUtf8Printable(jsonDoc.toJson(QJsonDocument::Compact));
QVariantMap statusMap = jsonDoc.toVariant().toMap();
if (thing->paramValue(goeHomeThingUseMqttParamTypeId).toBool()) {
// Verify mqtt client and set it up
qCDebug(dcGoECharger()) << "Setting up using MQTT connection for" << thing;
setupMqttChannelV2(info, address, statusMap);
} else {
// Since we are not using mqtt, we are done with the setup, the refresh timer will be configured in post setup
info->finish(Thing::ThingErrorNoError);
qCDebug(dcGoECharger()) << "Setup using HTTP finished successfully";
thing->setStateValue("connected", true);
updateV2(thing, statusMap);
}
break;
}
});
@ -371,10 +479,6 @@ void IntegrationPluginGoECharger::updateV1(Thing *thing, const QVariantMap &stat
// Parse status map and update states...
CarState carState = static_cast<CarState>(statusMap.value("car").toUInt());
switch (carState) {
case CarStateUnknown:
thing->setStateValue(goeHomeCarStatusStateTypeId, "Unknown");
thing->setStateValue(goeHomePluggedInStateTypeId, false);
break;
case CarStateReadyNoCar:
thing->setStateValue(goeHomeCarStatusStateTypeId, "Ready but no vehicle connected");
thing->setStateValue(goeHomePluggedInStateTypeId, false);
@ -391,6 +495,10 @@ void IntegrationPluginGoECharger::updateV1(Thing *thing, const QVariantMap &stat
thing->setStateValue(goeHomeCarStatusStateTypeId, "Charging finished and vehicle still connected");
thing->setStateValue(goeHomePluggedInStateTypeId, true);
break;
default:
thing->setStateValue(goeHomeCarStatusStateTypeId, "Unknown");
thing->setStateValue(goeHomePluggedInStateTypeId, false);
break;
}
thing->setStateValue(goeHomeChargingStateTypeId, carState == CarStateCharging);
@ -508,7 +616,6 @@ void IntegrationPluginGoECharger::updateV1(Thing *thing, const QVariantMap &stat
}
}
QNetworkRequest IntegrationPluginGoECharger::buildConfigurationRequestV1(const QHostAddress &address, const QString &configuration)
{
QUrl requestUrl;
@ -904,6 +1011,383 @@ void IntegrationPluginGoECharger::reconfigureMqttChannelV1(Thing *thing, const Q
});
}
void IntegrationPluginGoECharger::updateV2(Thing *thing, const QVariantMap &statusMap)
{
if (statusMap.contains("car")) {
CarState carState = static_cast<CarState>(statusMap.value("car").toUInt());
switch (carState) {
case CarStateReadyNoCar:
thing->setStateValue(goeHomeCarStatusStateTypeId, "Ready but no vehicle connected");
thing->setStateValue(goeHomePluggedInStateTypeId, false);
break;
case CarStateCharging:
thing->setStateValue(goeHomeCarStatusStateTypeId, "Vehicle loads");
thing->setStateValue(goeHomePluggedInStateTypeId, true);
break;
case CarStateWaitForCar:
thing->setStateValue(goeHomeCarStatusStateTypeId, "Waiting for vehicle");
thing->setStateValue(goeHomePluggedInStateTypeId, true);
break;
case CarStateChargedCarConnected:
thing->setStateValue(goeHomeCarStatusStateTypeId, "Charging finished and vehicle still connected");
thing->setStateValue(goeHomePluggedInStateTypeId, true);
break;
default:
thing->setStateValue(goeHomeCarStatusStateTypeId, "Unknown");
thing->setStateValue(goeHomePluggedInStateTypeId, false);
break;
}
thing->setStateValue(goeHomeChargingStateTypeId, carState == CarStateCharging);
}
if (statusMap.contains("ast")) {
Access accessStatus = static_cast<Access>(statusMap.value("ast").toUInt());
switch (accessStatus) {
case AccessOpen:
thing->setStateValue(goeHomeAccessStateTypeId, "Open");
break;
case AccessRfid:
thing->setStateValue(goeHomeAccessStateTypeId, "RFID");
break;
case AccessAuto:
thing->setStateValue(goeHomeAccessStateTypeId, "Automatic");
break;
}
}
if (statusMap.contains("tma")) {
QVariantList temperatureSensorList = statusMap.value("tma").toList();
if (temperatureSensorList.count() >= 1)
thing->setStateValue(goeHomeTemperatureSensor1StateTypeId, temperatureSensorList.at(0).toDouble());
if (temperatureSensorList.count() >= 2)
thing->setStateValue(goeHomeTemperatureSensor2StateTypeId, temperatureSensorList.at(1).toDouble());
if (temperatureSensorList.count() >= 3)
thing->setStateValue(goeHomeTemperatureSensor3StateTypeId, temperatureSensorList.at(2).toDouble());
if (temperatureSensorList.count() >= 4)
thing->setStateValue(goeHomeTemperatureSensor4StateTypeId, temperatureSensorList.at(3).toDouble());
}
if (statusMap.contains("eto"))
thing->setStateValue(goeHomeTotalEnergyConsumedStateTypeId, statusMap.value("eto").toUInt() / 1000.0); // Wh -> kWh
if (statusMap.contains("wh"))
thing->setStateValue(goeHomeSessionEnergyStateTypeId, statusMap.value("wh").toUInt() / 1000.0); // Wh -> kWh
if (statusMap.contains("alw"))
thing->setStateValue(goeHomePowerStateTypeId, (statusMap.value("alw").toUInt() == 0 ? false : true));
if (statusMap.contains("upd"))
thing->setStateValue(goeHomeUpdateAvailableStateTypeId, (statusMap.value("upd").toUInt() == 0 ? false : true));
if (statusMap.contains("fwv"))
thing->setStateValue(goeHomeFirmwareVersionStateTypeId, statusMap.value("fwv").toString());
// FIXME: check if we can use amx since it is better for pv charging, not all version seen implement this
if (statusMap.contains("amp"))
thing->setStateValue(goeHomeMaxChargingCurrentStateTypeId, statusMap.value("amp").toUInt());
if (statusMap.contains("adi"))
thing->setStateValue(goeHomeAdapterConnectedStateTypeId, (statusMap.value("adi").toUInt() == 0 ? false : true));
if (statusMap.contains("fhz"))
thing->setStateValue(goeHomeFrequencyStateTypeId, statusMap.value("fhz").toDouble());
if (statusMap.contains("cbl"))
thing->setStateValue(goeHomeCableType2AmpereStateTypeId, statusMap.value("cbl").toUInt());
if (statusMap.contains("ama"))
thing->setStateValue(goeHomeAbsoluteMaxAmpereStateTypeId, statusMap.value("ama").toUInt());
if (statusMap.contains("var")) {
uint variant = statusMap.value("var").toUInt();
uint variantLimit = 16; // 11 kW
if (variant == 22) // 22 kW
variantLimit = 32;
thing->setStateValue(goeHomeModelMaxAmpereStateTypeId, variantLimit);
}
if (statusMap.contains("cbl") || statusMap.contains("ama")) {
// Check charging limits
uint amaLimit = thing->stateValue(goeHomeAbsoluteMaxAmpereStateTypeId).toUInt();
uint cableLimit = thing->stateValue(goeHomeCableType2AmpereStateTypeId).toUInt();
uint modelLimit = thing->stateValue(goeHomeModelMaxAmpereStateTypeId).toUInt();
uint finalLimit = 0;
if (cableLimit != 0) {
finalLimit = qMin(amaLimit, cableLimit);
} else {
finalLimit = amaLimit;
}
// Check hardware variant: 11 -> 16A and 22 -> 32A
if (modelLimit != 0)
finalLimit = qMin(finalLimit, modelLimit);
thing->setStateMaxValue(goeHomeMaxChargingCurrentStateTypeId, finalLimit);
}
if (statusMap.contains("pnp") && statusMap.value("pnp").toUInt() != 0)
thing->setStateValue(goeHomePhaseCountStateTypeId, statusMap.value("pnp").toUInt());
if (statusMap.contains("nrg")) {
// Parse nrg array
uint voltagePhaseA = 0; uint voltagePhaseB = 0; uint voltagePhaseC = 0;
double amperePhaseA = 0; double amperePhaseB = 0; double amperePhaseC = 0;
double currentPower = 0; double powerPhaseA = 0; double powerPhaseB = 0; double powerPhaseC = 0;
QVariantList measurementList = statusMap.value("nrg").toList();
if (measurementList.count() >= 1)
voltagePhaseA = measurementList.at(0).toUInt();
if (measurementList.count() >= 2)
voltagePhaseB = measurementList.at(1).toUInt();
if (measurementList.count() >= 3)
voltagePhaseC = measurementList.at(2).toUInt();
if (measurementList.count() >= 5)
amperePhaseA = measurementList.at(4).toUInt();
if (measurementList.count() >= 6)
amperePhaseB = measurementList.at(5).toUInt();
if (measurementList.count() >= 7)
amperePhaseC = measurementList.at(6).toUInt();
if (measurementList.count() >= 8)
powerPhaseA = measurementList.at(7).toUInt();
if (measurementList.count() >= 9)
powerPhaseB = measurementList.at(8).toUInt() ;
if (measurementList.count() >= 10)
powerPhaseC = measurementList.at(9).toUInt();
if (measurementList.count() >= 12)
currentPower = measurementList.at(11).toUInt();
// Update all states
thing->setStateValue(goeHomeVoltagePhaseAStateTypeId, voltagePhaseA);
thing->setStateValue(goeHomeVoltagePhaseBStateTypeId, voltagePhaseB);
thing->setStateValue(goeHomeVoltagePhaseCStateTypeId, voltagePhaseC);
thing->setStateValue(goeHomeCurrentPhaseAStateTypeId, amperePhaseA);
thing->setStateValue(goeHomeCurrentPhaseBStateTypeId, amperePhaseB);
thing->setStateValue(goeHomeCurrentPhaseCStateTypeId, amperePhaseC);
thing->setStateValue(goeHomeCurrentPowerPhaseAStateTypeId, powerPhaseA);
thing->setStateValue(goeHomeCurrentPowerPhaseBStateTypeId, powerPhaseB);
thing->setStateValue(goeHomeCurrentPowerPhaseCStateTypeId, powerPhaseC);
thing->setStateValue(goeHomeCurrentPowerStateTypeId, currentPower);
// Check how many phases are actually charging, and update the phase count only if something happens on the phases (current or power)
if (amperePhaseA != 0 || amperePhaseB != 0 || amperePhaseC != 0) {
uint phaseCount = 0;
if (amperePhaseA != 0)
phaseCount += 1;
if (amperePhaseB != 0)
phaseCount += 1;
if (amperePhaseC != 0)
phaseCount += 1;
// Use this loginc only if we don't have pnp available
if (!statusMap.contains("pnp") || statusMap.value("pnp").toUInt() == 0) {
thing->setStateValue(goeHomePhaseCountStateTypeId, phaseCount);
}
}
}
}
QNetworkRequest IntegrationPluginGoECharger::buildConfigurationRequestV2(const QHostAddress &address, const QUrlQuery &configuration)
{
QUrl requestUrl;
requestUrl.setScheme("http");
requestUrl.setHost(address.toString());
requestUrl.setPath("/api/set");
requestUrl.setQuery(configuration);
return QNetworkRequest(requestUrl);
}
void IntegrationPluginGoECharger::setupMqttChannelV2(ThingSetupInfo *info, const QHostAddress &address, const QVariantMap &statusMap)
{
Thing *thing = info->thing();
QString serialNumber = thing->paramValue(goeHomeThingSerialNumberParamTypeId).toString();
// At least in version 51.1
QString clientId = QString("go-echarger_%1").arg(serialNumber);
QString statusTopic = QString("/go-eCharger/%1/#").arg(serialNumber);
qCDebug(dcGoECharger()) << "Setting up mqtt channel for" << thing << address.toString() << statusTopic;
// Somehow limited to 8 characters...
QString username = QString::fromUtf8(QUuid::createUuid().toByteArray().toHex().left(8));
QString password = QString::fromUtf8(QUuid::createUuid().toByteArray().toHex().left(8));
MqttChannel *channel = hardwareManager()->mqttProvider()->createChannel(clientId, username, password, address, {statusTopic});
if (!channel) {
qCWarning(dcGoECharger()) << "Failed to create MQTT channel for" << thing;
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Error creating MQTT channel. Please check MQTT server settings."));
return;
}
m_mqttChannelsV2.insert(thing, channel);
connect(channel, &MqttChannel::clientConnected, this, &IntegrationPluginGoECharger::onMqttClientV2Connected);
connect(channel, &MqttChannel::clientDisconnected, this, &IntegrationPluginGoECharger::onMqttClientV2Disconnected);
// Build the mqtt url
QUrl mqttUrl;
mqttUrl.setScheme("mqtt");
mqttUrl.setHost(channel->serverAddress().toString());
mqttUrl.setPort(channel->serverPort());
mqttUrl.setUserName(channel->username());
mqttUrl.setPassword(channel->password());
// The query item must be JSON encoded, meaning: strings need quouts... for whatever reason...
QUrlQuery query;
query.addQueryItem("mcu", "\"" + mqttUrl.toString() + "\"");
query.addQueryItem("mce", "true");
QNetworkRequest request = buildConfigurationRequestV2(address, query);
qCDebug(dcGoECharger()) << "Configure nymea mqtt server address on" << thing << request.url().toString();
QNetworkReply *reply = hardwareManager()->networkManager()->get(request);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, info, [=](){
if (reply->error() != QNetworkReply::NoError) {
qCWarning(dcGoECharger()) << "HTTP status reply returned error:" << reply->errorString() << reply->readAll();
info->finish(Thing::ThingErrorHardwareNotAvailable, QT_TR_NOOP("The wallbox does not seem to be reachable."));
return;
}
QByteArray data = reply->readAll();
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(dcGoECharger()) << "Failed to set mqtt configuration for thing " << thing->name() << qUtf8Printable(data) << error.errorString();
info->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("The wallbox returned invalid data."));
return;
}
qCDebug(dcGoECharger()) << qUtf8Printable(jsonDoc.toJson());
info->finish(Thing::ThingErrorNoError);
qCDebug(dcGoECharger()) << "Configuration of MQTT for" << thing << "finished successfully";
// Update states...
updateV2(thing, statusMap);
// From now on we listen to topics
connect(channel, &MqttChannel::publishReceived, thing, [=](MqttChannel* channel, const QString &topic, const QByteArray &payload){
QString propertyKey = topic.split("/").last();
QString propertyValue = QString::fromUtf8(payload);
// Well...no better idea for now to keep the APIs / parsing methods
// compatible trought different APIs and protocols
QString statusMapJsonString = QString("{\"%1\":%2}").arg(propertyKey).arg(propertyValue);
QJsonDocument jsonDoc = QJsonDocument::fromJson(statusMapJsonString.toUtf8());
// Mute the spaming stuff..
if (propertyKey != "fhz" && propertyKey != "rssi" && propertyKey != "utc" && propertyKey != "loc" && propertyKey != "rbt") {
qCDebug(dcGoECharger()) << thing->name() << channel->clientId() << "publish received" << topic << payload;
//qCDebug(dcGoECharger()) << "-->" << jsonDoc.toJson(QJsonDocument::Compact);
}
updateV2(thing, jsonDoc.toVariant().toMap());
});
});
}
void IntegrationPluginGoECharger::reconfigureMqttChannelV2(Thing *thing)
{
QString serialNumber = thing->paramValue(goeHomeThingSerialNumberParamTypeId).toString();
QHostAddress address = getHostAddress(thing);
qCDebug(dcGoECharger()) << "Reconfigure mqtt channel for" << thing;
// At least in version 51.1
QString clientId = QString("go-echarger_%1").arg(serialNumber);
QString statusTopic = QString("/go-eCharger/%1/#").arg(serialNumber);
qCDebug(dcGoECharger()) << "Reconfigure mqtt channel for" << thing << address.toString() << statusTopic;
// Somehow limited to 8 characters...
QString username = QString::fromUtf8(QUuid::createUuid().toByteArray().toHex().left(8));
QString password = QString::fromUtf8(QUuid::createUuid().toByteArray().toHex().left(8));
MqttChannel *channel = hardwareManager()->mqttProvider()->createChannel(clientId, username, password, address, {statusTopic});
if (!channel) {
qCWarning(dcGoECharger()) << "Failed to create MQTT channel for" << thing;
return;
}
// Clean up existing channel if there is one
if (m_mqttChannelsV2.contains(thing)) {
qCDebug(dcGoECharger()) << "Release old mqtt channel...";
hardwareManager()->mqttProvider()->releaseChannel(m_mqttChannelsV2.take(thing));
}
m_mqttChannelsV2.insert(thing, channel);
connect(channel, &MqttChannel::clientConnected, this, &IntegrationPluginGoECharger::onMqttClientV2Connected);
connect(channel, &MqttChannel::clientDisconnected, this, &IntegrationPluginGoECharger::onMqttClientV2Disconnected);
// Build the mqtt url
QUrl mqttUrl;
mqttUrl.setScheme("mqtt");
mqttUrl.setHost(channel->serverAddress().toString());
mqttUrl.setPort(channel->serverPort());
mqttUrl.setUserName(channel->username());
mqttUrl.setPassword(channel->password());
// The query item must be JSON encoded, meaning: strings need quouts... for whatever reason...
QUrlQuery query;
query.addQueryItem("mcu", "\"" + mqttUrl.toString() + "\"");
QNetworkRequest request = buildConfigurationRequestV2(address, query);
qCDebug(dcGoECharger()) << "Configure nymea mqtt server address on" << thing << request.url().toString();
QNetworkReply *reply = hardwareManager()->networkManager()->get(request);
connect(reply, &QNetworkReply::finished, reply, &QNetworkReply::deleteLater);
connect(reply, &QNetworkReply::finished, thing, [=](){
if (reply->error() != QNetworkReply::NoError) {
qCWarning(dcGoECharger()) << "HTTP status reply returned error:" << reply->errorString() << reply->readAll();
hardwareManager()->mqttProvider()->releaseChannel(m_mqttChannelsV2.take(thing));
return;
}
QByteArray data = reply->readAll();
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError) {
qCWarning(dcGoECharger()) << "Failed to set mqtt configuration for thing " << thing->name() << qUtf8Printable(data) << error.errorString();
hardwareManager()->mqttProvider()->releaseChannel(m_mqttChannelsV2.take(thing));
return;
}
QVariantMap responseCode = jsonDoc.toVariant().toMap();
if (!responseCode.value("mcu", false).toBool()) {
qCWarning(dcGoECharger()) << "Failed to configure mqtt on" << thing;
hardwareManager()->mqttProvider()->releaseChannel(m_mqttChannelsV2.take(thing));
return;
}
qCDebug(dcGoECharger()) << "Configuration of MQTT for" << thing << "finished successfully";
// From now on we listen to topics
connect(channel, &MqttChannel::publishReceived, thing, [=](MqttChannel* channel, const QString &topic, const QByteArray &payload){
QString propertyKey = topic.split("/").last();
QString propertyValue = QString::fromUtf8(payload);
// Well...no better idea for now to keep the APIs / parsing methods
// compatible trought different APIs and protocols
QString statusMapJsonString = QString("{\"%1\":%2}").arg(propertyKey).arg(propertyValue);
QJsonDocument jsonDoc = QJsonDocument::fromJson(statusMapJsonString.toUtf8());
// Mute the spaming stuff..
if (propertyKey != "fhz" && propertyKey != "rssi" && propertyKey != "utc" && propertyKey != "loc" && propertyKey != "rbt") {
qCDebug(dcGoECharger()) << thing->name() << channel->clientId() << "publish received" << topic << payload;
//qCDebug(dcGoECharger()) << "-->" << jsonDoc.toJson(QJsonDocument::Compact);
}
updateV2(thing, jsonDoc.toVariant().toMap());
});
});
}
void IntegrationPluginGoECharger::refreshHttp()
{
// Update all things which don't use mqtt
@ -955,7 +1439,7 @@ void IntegrationPluginGoECharger::refreshHttp()
updateV1(thing, statusMap);
break;
case ApiVersion2:
// TODO:
updateV2(thing, statusMap);
break;
}
});
@ -1011,4 +1495,26 @@ void IntegrationPluginGoECharger::onMqttPublishV1Received(MqttChannel *channel,
}
}
void IntegrationPluginGoECharger::onMqttClientV2Connected(MqttChannel *channel)
{
Thing *thing = m_mqttChannelsV2.key(channel);
if (!thing) {
qCWarning(dcGoECharger()) << "Received a client disconnect for an unknown thing. Ignoring the event.";
return;
}
qCDebug(dcGoECharger()) << thing << "connected";
thing->setStateValue("connected", false);
}
void IntegrationPluginGoECharger::onMqttClientV2Disconnected(MqttChannel *channel)
{
Thing *thing = m_mqttChannelsV1.key(channel);
if (!thing) {
qCWarning(dcGoECharger()) << "Received a client disconnect for an unknown thing. Ignoring the event.";
return;
}
qCDebug(dcGoECharger()) << thing << "connected";
thing->setStateValue("connected", false);
}

View File

@ -118,11 +118,16 @@ private:
// API V1
void updateV1(Thing *thing, const QVariantMap &statusMap);
void sendActionRequestV1(Thing *thing, ThingActionInfo *info, const QString &configuration);
QNetworkRequest buildConfigurationRequestV1(const QHostAddress &address, const QString &configuration);
void sendActionRequestV1(Thing *thing, ThingActionInfo *info, const QString &configuration);
void setupMqttChannelV1(ThingSetupInfo *info, const QHostAddress &address, const QVariantMap &statusMap);
void reconfigureMqttChannelV1(Thing *thing, const QVariantMap &statusMap);
// API V2
void updateV2(Thing *thing, const QVariantMap &statusMap);
QNetworkRequest buildConfigurationRequestV2(const QHostAddress &address, const QUrlQuery &configuration);
void setupMqttChannelV2(ThingSetupInfo *info, const QHostAddress &address, const QVariantMap &statusMap);
void reconfigureMqttChannelV2(Thing *thing);
private slots:
void refreshHttp();
@ -132,6 +137,10 @@ private slots:
void onMqttClientV1Disconnected(MqttChannel* channel);
void onMqttPublishV1Received(MqttChannel* channel, const QString &topic, const QByteArray &payload);
// API V2
void onMqttClientV2Connected(MqttChannel* channel);
void onMqttClientV2Disconnected(MqttChannel* channel);
};
#endif // INTEGRATIONPLUGINGOECHARGER_H