// SPDX-License-Identifier: GPL-3.0-or-later
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* Copyright (C) 2013 - 2024, nymea GmbH
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
*
* This file is part of nymea-app.
*
* nymea-app is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* nymea-app 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
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with nymea-app. If not, see .
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include "nfcthingactionwriter.h"
#include "types/thingclass.h"
#include "types/statetype.h"
#include "types/ruleaction.h"
#include "types/ruleactionparams.h"
#include "types/ruleactionparam.h"
#include
#include
#include
#include
#include
#include
NfcThingActionWriter::NfcThingActionWriter(QObject *parent):
QObject(parent),
m_manager(new QNearFieldManager(this)),
m_actions(new RuleActions(this))
{
connect(m_manager, &QNearFieldManager::targetDetected, this, &NfcThingActionWriter::targetDetected);
connect(m_manager, &QNearFieldManager::targetLost, this, &NfcThingActionWriter::targetLost);
connect(m_actions, &RuleActions::countChanged, this, &NfcThingActionWriter::updateContent);
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
m_manager->startTargetDetection();
#else
m_manager->startTargetDetection(QNearFieldTarget::AnyAccess);
#endif
}
NfcThingActionWriter::~NfcThingActionWriter()
{
m_manager->stopTargetDetection();
}
bool NfcThingActionWriter::isAvailable() const
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
return m_manager->isAvailable();
#else
return m_manager->isEnabled();
#endif
}
Engine *NfcThingActionWriter::engine() const
{
return m_engine;
}
void NfcThingActionWriter::setEngine(Engine *engine)
{
if (m_engine != engine) {
m_engine = engine;
emit engineChanged();
updateContent();
}
}
Thing *NfcThingActionWriter::thing() const
{
return m_thing;
}
void NfcThingActionWriter::setThing(Thing *thing)
{
if (m_thing != thing) {
m_thing = thing;
emit thingChanged();
updateContent();
}
}
RuleActions *NfcThingActionWriter::actions() const
{
return m_actions;
}
int NfcThingActionWriter::messageSize() const
{
return static_cast(m_currentMessage.toByteArray().size());
}
NfcThingActionWriter::TagStatus NfcThingActionWriter::status() const
{
return m_status;
}
void NfcThingActionWriter::updateContent()
{
qDebug() << "Updating" << m_engine << m_thing;
// Creating an URI type record with this format:
// nymea://
// ? t=
// & a[0]=
// & a[1]=#:
// & a[2]=#:+:
// & ...
// NOTE: We're using actionType and paramType *name* instead of the ID because NFC tags are
// small and normally names are shorter than ids so we save some space.
// NOTE: param values are percentage encoded to prevent messing with the parsing if they
// contain + or :
QUrl url;
url.setScheme("nymea");
if (!m_engine || !m_thing) {
return;
}
url.setHost(m_engine->jsonRpcClient()->currentHost()->uuid().toString().remove(QRegularExpression("[{}]")));
QUrlQuery query;
query.addQueryItem("t", m_thing->id().toString().remove(QRegularExpression("[{}]")));
for (int i = 0; i < m_actions->rowCount(); i++) {
RuleAction *action = m_actions->get(i);
QStringList params;
ActionType *at = m_thing->thingClass()->actionTypes()->getActionType(action->actionTypeId());
if (!at) {
qWarning() << "ActionType not found in thing" << action->actionTypeId();
continue;
}
for (int j = 0; j < action->ruleActionParams()->rowCount(); j++) {
RuleActionParam *param = action->ruleActionParams()->get(j);
ParamType *pt = at->paramTypes()->getParamType(param->paramTypeId());
if (!pt) {
qWarning() << "ParamType not found in thing";
continue;
}
params.append(pt->name() + ":" + param->value().toByteArray().toPercentEncoding());
}
QString actionString = at->name();
if (params.length() > 0) {
actionString += "#" + params.join("+");
}
query.addQueryItem(QString("a[%1]").arg(i), actionString);
}
url.setQuery(query);
qDebug() << "writing message" << url;
QNdefNfcUriRecord record;
record.setUri(url);
m_currentMessage.clear();
m_currentMessage.append(record);
emit messageSizeChanged();
}
void NfcThingActionWriter::targetDetected(QNearFieldTarget *target)
{
QDateTime startTime = QDateTime::currentDateTime();
qDebug() << "target detected";
QNearFieldTarget::RequestId m_request = target->writeNdefMessages(QList() << m_currentMessage);
if (!m_request.isValid()) {
qDebug() << "Error writing tag";
m_status = TagStatusFailed;
emit statusChanged();
}
connect(target, &QNearFieldTarget::error, this, [=](QNearFieldTarget::Error error, const QNearFieldTarget::RequestId &id){
Q_UNUSED(id)
qDebug() << "Tag error:" << error;
m_status = TagStatusFailed;
emit statusChanged();
});
connect(target, &QNearFieldTarget::requestCompleted, this, [=](const QNearFieldTarget::RequestId &id){
if (id == m_request) {
qDebug() << "Tag written in" << startTime.msecsTo(QDateTime::currentDateTime());
m_status = TagStatusWritten;
emit statusChanged();
}
});
m_status = TagStatusWriting;
emit statusChanged();
}
void NfcThingActionWriter::targetLost(QNearFieldTarget *target)
{
qDebug() << "Target lost" << target;
m_status = TagStatusWaiting;
emit statusChanged();
}