Merge PR #765: New plugin: v2xeamberelectric
This commit is contained in:
commit
776cb290c8
9
debian/control
vendored
9
debian/control
vendored
@ -841,6 +841,15 @@ Description: nymea integration plugin to connect to your Tado account
|
||||
This package contains the nymea integration plugin for Tado devices.
|
||||
|
||||
|
||||
Package: nymea-plugin-v2xeamberelectric
|
||||
Architecture: any
|
||||
Depends: ${shlibs:Depends},
|
||||
${misc:Depends},
|
||||
Conflicts: nymea-plugins-translations (<< 1.0.1)
|
||||
Description: This nymea plugin fetches live and forecasted price data,
|
||||
providing real-time and future pricing information for integration with smart energy management systems.
|
||||
|
||||
|
||||
Package: nymea-plugin-wheretheissat
|
||||
Architecture: any
|
||||
Depends: ${misc:Depends},
|
||||
|
||||
1
debian/nymea-plugin-v2xeambereletric.install.in
vendored
Normal file
1
debian/nymea-plugin-v2xeambereletric.install.in
vendored
Normal file
@ -0,0 +1 @@
|
||||
usr/lib/@DEB_HOST_MULTIARCH@/nymea/plugins/libnymea_integrationpluginv2xeamberelectric.so
|
||||
@ -84,6 +84,7 @@ PLUGIN_DIRS = \
|
||||
unifi \
|
||||
usbrelay \
|
||||
usbrly82 \
|
||||
v2xeamberelectric \
|
||||
wakeonlan \
|
||||
wemo \
|
||||
ws2812fx \
|
||||
|
||||
7
v2xeamberelectric/README.md
Normal file
7
v2xeamberelectric/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# v2xe_amber_electric_api
|
||||
--------------------------------
|
||||
|
||||
Description of the plugin...
|
||||
|
||||
This Nymea plugin fetches live and forecasted price data, providing real-time and future pricing information for integration with smart energy management systems
|
||||
|
||||
102
v2xeamberelectric/integrationpluginv2xeamberelectric.json
Normal file
102
v2xeamberelectric/integrationpluginv2xeamberelectric.json
Normal file
@ -0,0 +1,102 @@
|
||||
{
|
||||
"name": "AmberElectric",
|
||||
"displayName": "Amber Electric",
|
||||
"id": "0233c848-6f6f-4907-a51c-81391e4f5356",
|
||||
"vendors": [
|
||||
{
|
||||
"name": "AmberElectric",
|
||||
"displayName": "Amber Electric",
|
||||
"id": "ec1985b9-5891-435d-9264-e8e18d392978",
|
||||
"thingClasses": [
|
||||
{
|
||||
"name": "V2XeAmberElectric",
|
||||
"displayName": "Amber Electric - V2Xe Amber Electric",
|
||||
"id": "11295990-7002-432e-b90c-cd4010548a32",
|
||||
"setupMethod": "JustAdd",
|
||||
"createMethods": ["User"],
|
||||
"interfaces": ["smartmeter", "connectable"],
|
||||
"paramTypes": [
|
||||
{
|
||||
"id": "fc899f7b-f452-44dc-b72e-ee7bb8034ff0",
|
||||
"name": "authToken",
|
||||
"displayName": "auth token",
|
||||
"type" : "QString",
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "c50eac84-714e-4b9c-89d5-0b939b6c18d6",
|
||||
"name": "currentSite",
|
||||
"displayName": "current site",
|
||||
"type" : "QString",
|
||||
"defaultValue": ""
|
||||
}
|
||||
],
|
||||
"stateTypes": [
|
||||
{
|
||||
"id": "faf2a6c0-1897-4b9b-903a-e12b281efe24",
|
||||
"name": "connected",
|
||||
"displayName": "Reachable",
|
||||
"type": "bool",
|
||||
"defaultValue": false,
|
||||
"cached": false
|
||||
},
|
||||
{
|
||||
"id": "42c8a6f8-4f97-4871-8bfb-ac7e6ffffa74",
|
||||
"name": "Sites",
|
||||
"displayName": "Sites",
|
||||
"type": "QString",
|
||||
"writable": false,
|
||||
"defaultValue": ""
|
||||
},
|
||||
{
|
||||
"id": "27c3f8e0-0803-4eb8-ba4f-ce78fb0997a9",
|
||||
"name": "Type",
|
||||
"displayName": "type",
|
||||
"type": "QString",
|
||||
"defaultValue": "Amber"
|
||||
},
|
||||
{
|
||||
"id": "6e8d3fcd-c235-4793-95b5-321ca6e012a3",
|
||||
"name": "forecastprice",
|
||||
"displayName": "Feedin Price",
|
||||
"type": "double",
|
||||
"defaultValue": 0,
|
||||
"unit": "KiloWattHour",
|
||||
"suggestLogging": true
|
||||
},
|
||||
{
|
||||
"id": "a25d3b55-9b93-4465-90e3-5d36adb8fb23",
|
||||
"name": "currentprice",
|
||||
"displayName": "Current Price",
|
||||
"type": "double",
|
||||
"unit":"KiloWattHour",
|
||||
"defaultValue": 0,
|
||||
"suggestLogging": true
|
||||
},
|
||||
{
|
||||
"id": "cd1e22b2-2154-453a-bbdf-59156893258a",
|
||||
"name": "currentfeedin",
|
||||
"displayName": "Current Feedin",
|
||||
"type": "double",
|
||||
"unit":"KiloWattHour",
|
||||
"defaultValue": 0,
|
||||
"suggestLogging": true
|
||||
},
|
||||
{
|
||||
"id": "52188fdf-4a4d-4ca8-b02e-cea5ed8c1615",
|
||||
"name": "futurefeedin",
|
||||
"displayName": "Future Feedin",
|
||||
"type": "double",
|
||||
"unit":"KiloWattHour",
|
||||
"defaultValue": 0,
|
||||
"suggestLogging": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
||||
297
v2xeamberelectric/v2xeamberelectric.cpp
Normal file
297
v2xeamberelectric/v2xeamberelectric.cpp
Normal file
@ -0,0 +1,297 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2025 devendragajjar <devendragajjar@gmail.com> *
|
||||
* *
|
||||
* This library is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU Lesser General Public *
|
||||
* License as published by the Free Software Foundation; *
|
||||
* version 3 of the License. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; If not, see *
|
||||
* <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#include "v2xeamberelectric.h"
|
||||
#include "plugininfo.h"
|
||||
#include "hardwaremanager.h"
|
||||
#include "network/networkaccessmanager.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QJsonDocument>
|
||||
#include <QSslConfiguration>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
|
||||
|
||||
|
||||
|
||||
const QString constSite = "01J1XBQFGX57137EH0C6AG040D";
|
||||
const QString noOfDataNeed = "5";
|
||||
const QString siteHttpLink = "https://api.amber.com.au/v1/sites";
|
||||
const QString priceHttpLinkFront = "https://api.amber.com.au/v1/sites/";
|
||||
const QString priceHttpLinkBack = "/prices/current?next="+ noOfDataNeed +"&previous=" + noOfDataNeed;
|
||||
const QString authToken = "f5b2c823de5fed2f4c6bdb7c1b0db4f5";
|
||||
|
||||
|
||||
V2xeAmberElectric::V2xeAmberElectric()
|
||||
{
|
||||
mConsumerKey = authToken;
|
||||
}
|
||||
|
||||
V2xeAmberElectric::~V2xeAmberElectric()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void V2xeAmberElectric::init()
|
||||
{
|
||||
// Initialisation can be done here.
|
||||
qCDebug(dcAmberElectric()) << "Plugin initialized.";
|
||||
}
|
||||
|
||||
|
||||
|
||||
void V2xeAmberElectric::setupThing(ThingSetupInfo *info)
|
||||
{
|
||||
// A thing is being set up. Use info->thing() to get details of the thing, do
|
||||
// the required setup (e.g. connect to the device) and call info->finish() when done.
|
||||
|
||||
qCDebug(dcAmberElectric()) << "Setup thing" << info->thing()->name() << info->thing()->params();
|
||||
QString key = info->thing()->paramValue(V2XeAmberElectricThingAuthTokenParamTypeId).toString();
|
||||
if (!key.isEmpty()) {
|
||||
mConsumerKey = key;
|
||||
qCDebug(dcAmberElectric()) << "mConsumerKey set " << mConsumerKey << "key " << key;
|
||||
}
|
||||
|
||||
QString site = info->thing()->paramValue(V2XeAmberElectricThingCurrentSiteParamTypeId).toString();
|
||||
if (!site.isEmpty()) {
|
||||
mCurrentSite = site;
|
||||
qCDebug(dcAmberElectric()) << "mCurrentSite set " << mCurrentSite ;
|
||||
}
|
||||
else{
|
||||
mCurrentSite = constSite;
|
||||
}
|
||||
|
||||
// use default key for now
|
||||
qCDebug(dcAmberElectric()) << "No API key set.";
|
||||
qCDebug(dcAmberElectric()) << "Either install an API key pacakge (nymea-apikeysprovider-plugin-*) or provide a key in the plugin settings.";
|
||||
|
||||
if (!mPluginTimer) {
|
||||
mPluginTimer = hardwareManager()->pluginTimerManager()->registerTimer(1800);//TODO make 30 minutes after test for
|
||||
connect(mPluginTimer, &PluginTimer::timeout, this, &V2xeAmberElectric::onPluginTimer);
|
||||
}
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
}
|
||||
|
||||
void V2xeAmberElectric::onPluginTimer()
|
||||
{
|
||||
foreach (Thing *thing, myThings()) {
|
||||
|
||||
requestSiteData(thing);
|
||||
requestSitePriceData(thing);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void V2xeAmberElectric::executeAction(ThingActionInfo *info)
|
||||
{
|
||||
// An action is being executed. Use info->action() to get details about the action,
|
||||
// do the required operations (e.g. send a command to the network) and call info->finish() when done.
|
||||
|
||||
qCDebug(dcAmberElectric()) << "Executing action for thing" << info->thing() << info->action().actionTypeId().toString() << info->action().params();
|
||||
|
||||
info->finish(Thing::ThingErrorNoError);
|
||||
}
|
||||
|
||||
void V2xeAmberElectric::thingRemoved(Thing *thing)
|
||||
{
|
||||
// A thing is being removed from the system. Do the required cleanup
|
||||
// (e.g. disconnect from the device) here.
|
||||
|
||||
qCDebug(dcAmberElectric()) << "Remove thing" << thing;
|
||||
}
|
||||
|
||||
void V2xeAmberElectric::requestSitePriceData(Thing* thing, ThingSetupInfo* setup) {
|
||||
|
||||
QString price_http_link = priceHttpLinkFront + mCurrentSite + priceHttpLinkBack;
|
||||
|
||||
mServerUrls[V2XeAmberElectricThingClassId] = price_http_link;
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setUrl(QUrl(mServerUrls[V2XeAmberElectricThingClassId]));
|
||||
|
||||
// Add the Authorization header with the token
|
||||
QString token = thing->paramValue(mConsumerKey).toString();
|
||||
token = mConsumerKey;
|
||||
qCDebug(dcAmberElectric) << " using mConsumerKey " <<mConsumerKey;
|
||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(token).toUtf8());
|
||||
request.setRawHeader("accept", "application/json");
|
||||
|
||||
QNetworkReply* reply =hardwareManager()->networkManager()->get(request);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, thing, setup, reply]() {
|
||||
reply->deleteLater();
|
||||
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (status != 200) {
|
||||
qCWarning(dcAmberElectric) << "Update reply HTTP error:" << status << reply->errorString();
|
||||
if (setup) {
|
||||
setup->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Error getting data from server."));
|
||||
} else {
|
||||
thing->setStateValue(V2XeAmberElectricConnectedStateTypeId, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
onSitePriceDataReceived(thing, setup, reply);
|
||||
});
|
||||
}
|
||||
|
||||
void V2xeAmberElectric::onSitePriceDataReceived(Thing* thing, ThingSetupInfo* setup, QNetworkReply* reply) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qCWarning(dcAmberElectric) << "Network error:" << reply->errorString();
|
||||
if (setup) {
|
||||
setup->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Network error."));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll(), &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qCWarning(dcAmberElectric) << "JSON parse error:" << error.errorString();
|
||||
if (setup) {
|
||||
setup->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("JSON parse error."));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (jsonDoc.isArray()) {
|
||||
QString channelType;
|
||||
QJsonArray jsonArray = jsonDoc.array();
|
||||
for (const QJsonValue &value : jsonArray) {
|
||||
if (value.isObject()) {
|
||||
QJsonObject jsonObject = value.toObject();
|
||||
QString type = jsonObject["type"].toString(); //Get type
|
||||
channelType = jsonObject["channelType"].toString();
|
||||
//Current Price
|
||||
if((type == "CurrentInterval") && channelType == "general"){
|
||||
if (jsonObject.contains("perKwh") && jsonObject["perKwh"].isDouble()) { // Get perKwh
|
||||
double perkwh = jsonObject["perKwh"].toDouble();
|
||||
thing->setStateValue(V2XeAmberElectricCurrentpriceStateTypeId, qAbs(perkwh));
|
||||
}
|
||||
}
|
||||
//Future Price
|
||||
if((type == "ForecastInterval") && channelType == "general"){
|
||||
if (jsonObject.contains("perKwh") && jsonObject["perKwh"].isDouble()) { // Get perKwh
|
||||
double perkwh = jsonObject["perKwh"].toDouble();
|
||||
thing->setStateValue(V2XeAmberElectricForecastpriceStateTypeId, qAbs(perkwh));
|
||||
}
|
||||
}
|
||||
// Current Feedin //TODO what is feed in price its dummy data
|
||||
if((type == "CurrentInterval") && (channelType == "feedIn")){
|
||||
if (jsonObject.contains("perKwh") && jsonObject["perKwh"].isDouble()) { // Get SportPerKwh
|
||||
double spotperkwh = jsonObject["perKwh"].toDouble();
|
||||
thing->setStateValue(V2XeAmberElectricCurrentfeedinStateTypeId, qAbs(spotperkwh));
|
||||
}
|
||||
}
|
||||
//Future Feedin
|
||||
if((type == "ForecastInterval") && (channelType == "feedIn")){
|
||||
if (jsonObject.contains("perKwh") && jsonObject["perKwh"].isDouble()) { // Get SportPerKwh
|
||||
double spotperkwh = jsonObject["perKwh"].toDouble();
|
||||
thing->setStateValue(V2XeAmberElectricFuturefeedinStateTypeId, qAbs(spotperkwh));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (setup) {
|
||||
setup->finish(Thing::ThingErrorNoError);
|
||||
}
|
||||
thing->setStateValue(V2XeAmberElectricConnectedStateTypeId, true);
|
||||
}
|
||||
|
||||
|
||||
void V2xeAmberElectric::requestSiteData(Thing* thing, ThingSetupInfo* setup) {
|
||||
|
||||
mServerUrls[V2XeAmberElectricThingClassId] = siteHttpLink;
|
||||
|
||||
|
||||
QNetworkRequest request;
|
||||
request.setUrl(QUrl(mServerUrls[V2XeAmberElectricThingClassId]));
|
||||
|
||||
// Add the Authorization header with the token
|
||||
QString token = thing->paramValue(mConsumerKey).toString();
|
||||
token = mConsumerKey;
|
||||
qCDebug(dcAmberElectric) << " using mConsumerKey " <<mConsumerKey;
|
||||
request.setRawHeader("Authorization", QString("Bearer %1").arg(token).toUtf8());
|
||||
request.setRawHeader("accept", "application/json");
|
||||
|
||||
QNetworkReply* reply =hardwareManager()->networkManager()->get(request);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, thing, setup, reply]() {
|
||||
reply->deleteLater();
|
||||
|
||||
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (status != 200) {
|
||||
qCWarning(dcAmberElectric) << "Update reply HTTP error:" << status << reply->errorString();
|
||||
if (setup) {
|
||||
setup->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Error getting data from server."));
|
||||
} else {
|
||||
thing->setStateValue(V2XeAmberElectricConnectedStateTypeId, false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
onSiteDataReceived(thing, setup, reply);
|
||||
});
|
||||
}
|
||||
|
||||
void V2xeAmberElectric::onSiteDataReceived(Thing* thing, ThingSetupInfo* setup, QNetworkReply* reply) {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qCWarning(dcAmberElectric) << "Network error:" << reply->errorString();
|
||||
if (setup) {
|
||||
setup->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("Network error."));
|
||||
}
|
||||
return;
|
||||
}
|
||||
QVariantList stringList = {};
|
||||
QString l_site;
|
||||
QJsonParseError error;
|
||||
QJsonDocument jsonDoc = QJsonDocument::fromJson(reply->readAll(), &error);
|
||||
if (error.error != QJsonParseError::NoError) {
|
||||
qCWarning(dcAmberElectric) << "JSON parse error:" << error.errorString();
|
||||
if (setup) {
|
||||
setup->finish(Thing::ThingErrorHardwareFailure, QT_TR_NOOP("JSON parse error."));
|
||||
}
|
||||
return;
|
||||
}
|
||||
qCDebug(dcAmberElectric) << "onSiteDataReceived " ;
|
||||
if (jsonDoc.isArray()) {
|
||||
QJsonArray jsonArray = jsonDoc.array();
|
||||
for (const QJsonValue &value : jsonArray) {
|
||||
if (value.isObject()) {
|
||||
QJsonObject jsonObject = value.toObject();
|
||||
QString type = jsonObject["id"].toString(); //Get type
|
||||
|
||||
if(!type.isEmpty()){ //update
|
||||
stringList.append(type);
|
||||
qCDebug(dcAmberElectric) << "type :" << type;
|
||||
l_site = type;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
thing->setStateValue(V2XeAmberElectricSitesStateTypeId, l_site);
|
||||
if (setup) {
|
||||
setup->finish(Thing::ThingErrorNoError);
|
||||
}
|
||||
thing->setStateValue(V2XeAmberElectricConnectedStateTypeId, true);
|
||||
}
|
||||
|
||||
71
v2xeamberelectric/v2xeamberelectric.h
Normal file
71
v2xeamberelectric/v2xeamberelectric.h
Normal file
@ -0,0 +1,71 @@
|
||||
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
* *
|
||||
* Copyright (C) 2025 devendragajjar <devendragajjar@gmail.com> *
|
||||
* *
|
||||
* This library is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU Lesser General Public *
|
||||
* License as published by the Free Software Foundation; *
|
||||
* version 3 of the License. *
|
||||
* *
|
||||
* This library is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with this library; If not, see *
|
||||
* <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
||||
|
||||
#ifndef INTEGRATIONPLUGINEV_CHARGER_H
|
||||
#define INTEGRATIONPLUGINEV_CHARGER_H
|
||||
|
||||
#include "integrations/integrationplugin.h"
|
||||
#include "extern-plugininfo.h"
|
||||
#include "plugintimer.h"
|
||||
#include "network/networkaccessmanager.h"
|
||||
|
||||
#include <QHash>
|
||||
#include <QDebug>
|
||||
#include <QTimer>
|
||||
#include <QPointer>
|
||||
#include <QNetworkReply>
|
||||
|
||||
|
||||
class V2xeAmberElectric: public IntegrationPlugin
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
Q_PLUGIN_METADATA(IID "io.nymea.IntegrationPlugin" FILE "integrationpluginv2xeamberelectric.json")
|
||||
Q_INTERFACES(IntegrationPlugin)
|
||||
|
||||
|
||||
public:
|
||||
explicit V2xeAmberElectric();
|
||||
~V2xeAmberElectric();
|
||||
|
||||
void init() override;
|
||||
|
||||
void setupThing(ThingSetupInfo *info) override;
|
||||
|
||||
void executeAction(ThingActionInfo *info) override;
|
||||
|
||||
void thingRemoved(Thing *thing) override;
|
||||
|
||||
private slots:
|
||||
void onPluginTimer();
|
||||
void requestSitePriceData(Thing* thing, ThingSetupInfo* setup = nullptr);
|
||||
void requestSiteData(Thing* thing, ThingSetupInfo* setup = nullptr);
|
||||
|
||||
private:
|
||||
PluginTimer *mPluginTimer = nullptr;
|
||||
QString mConsumerKey;
|
||||
QString mCurrentSite;
|
||||
QHash<ThingClassId, QString> mServerUrls;
|
||||
|
||||
void onSiteDataReceived(Thing* thing, ThingSetupInfo* setup = nullptr, QNetworkReply* reply = nullptr);
|
||||
void onSitePriceDataReceived(Thing* thing, ThingSetupInfo* setup = nullptr, QNetworkReply* reply = nullptr);
|
||||
};
|
||||
|
||||
#endif // INTEGRATIONPLUGINEV_CHARGER_H
|
||||
13
v2xeamberelectric/v2xeamberelectric.pro
Normal file
13
v2xeamberelectric/v2xeamberelectric.pro
Normal file
@ -0,0 +1,13 @@
|
||||
include(../plugins.pri)
|
||||
|
||||
QT += network
|
||||
|
||||
SOURCES += \
|
||||
v2xeamberelectric.cpp
|
||||
|
||||
HEADERS += \
|
||||
v2xeamberelectric.h
|
||||
|
||||
DISTFILES += \
|
||||
integrationpluginv2xeamberelectric.json
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user