OSSIA
Open Scenario System for Interactive Application
Loading...
Searching...
No Matches
qml_nfc.hpp
1#pragma once
2
3#if __has_include(<QNearFieldManager>)
4#define OSSIA_HAS_NFC 1
5
6#include <ossia-qt/protocols/utils.hpp>
7
8#include <QJSValue>
9#include <QNdefMessage>
10#include <QNdefNfcTextRecord>
11#include <QNdefNfcUriRecord>
12#include <QNdefRecord>
13#include <QNearFieldManager>
14#include <QNearFieldTarget>
15#include <QObject>
16#include <QPointer>
17#include <QQmlEngine>
18#include <QVariant>
19
20#include <nano_observer.hpp>
21
22#include <verdigris>
23
24namespace ossia::qt
25{
26
27class qml_nfc_target
28 : public QObject
29 , public Nano::Observer
30{
31 W_OBJECT(qml_nfc_target)
32public:
33 explicit qml_nfc_target(QNearFieldTarget* target, QObject* parent = nullptr)
34 : QObject{parent}
35 , m_target{target}
36 {
37 QObject::connect(
38 m_target, &QNearFieldTarget::ndefMessageRead, this,
39 [this](const QNdefMessage& message) {
40 if(!onNdefMessage.isCallable())
41 return;
42 auto* engine = qjsEngine(this);
43 if(!engine)
44 return;
45
46 auto arr = engine->newArray(message.size());
47 for(qsizetype i = 0; i < message.size(); ++i)
48 {
49 const auto& record = message[i];
50 auto obj = engine->newObject();
51
52 if(record.isRecordType<QNdefNfcTextRecord>())
53 {
54 QNdefNfcTextRecord textRecord(record);
55 obj.setProperty("type", QStringLiteral("text"));
56 obj.setProperty("text", textRecord.text());
57 obj.setProperty("locale", textRecord.locale());
58 obj.setProperty(
59 "encoding",
60 textRecord.encoding() == QNdefNfcTextRecord::Utf8
61 ? QStringLiteral("UTF-8")
62 : QStringLiteral("UTF-16"));
63 }
64 else if(record.isRecordType<QNdefNfcUriRecord>())
65 {
66 QNdefNfcUriRecord uriRecord(record);
67 obj.setProperty("type", QStringLiteral("uri"));
68 obj.setProperty("uri", uriRecord.uri().toString());
69 }
70 else
71 {
72 obj.setProperty("type", QStringLiteral("raw"));
73 obj.setProperty(
74 "typeNameFormat",
75 static_cast<int>(record.typeNameFormat()));
76 obj.setProperty(
77 "recordType", engine->toScriptValue(record.type()));
78 obj.setProperty(
79 "payload", engine->toScriptValue(record.payload()));
80 obj.setProperty("id", engine->toScriptValue(record.id()));
81 }
82
83 arr.setProperty(i, obj);
84 }
85 onNdefMessage.call({arr});
86 });
87
88 QObject::connect(
89 m_target, &QNearFieldTarget::error, this,
90 [this](
91 QNearFieldTarget::Error error,
92 const QNearFieldTarget::RequestId&) {
93 if(!onError.isCallable())
94 return;
95 QString msg;
96 switch(error)
97 {
98 case QNearFieldTarget::NoError:
99 return;
100 case QNearFieldTarget::UnsupportedError:
101 msg = QStringLiteral("Unsupported operation");
102 break;
103 case QNearFieldTarget::TargetOutOfRangeError:
104 msg = QStringLiteral("Target out of range");
105 break;
106 case QNearFieldTarget::NoResponseError:
107 msg = QStringLiteral("No response");
108 break;
109 case QNearFieldTarget::ChecksumMismatchError:
110 msg = QStringLiteral("Checksum mismatch");
111 break;
112 case QNearFieldTarget::InvalidParametersError:
113 msg = QStringLiteral("Invalid parameters");
114 break;
115 case QNearFieldTarget::ConnectionError:
116 msg = QStringLiteral("Connection error");
117 break;
118 case QNearFieldTarget::NdefReadError:
119 msg = QStringLiteral("NDEF read error");
120 break;
121 case QNearFieldTarget::NdefWriteError:
122 msg = QStringLiteral("NDEF write error");
123 break;
124 case QNearFieldTarget::CommandError:
125 msg = QStringLiteral("Command error");
126 break;
127 case QNearFieldTarget::TimeoutError:
128 msg = QStringLiteral("Timeout");
129 break;
130 default:
131 msg = QStringLiteral("Unknown NFC error");
132 break;
133 }
134 onError.call({msg});
135 });
136 }
137
138 QString uid() const
139 {
140 if(!m_target)
141 return {};
142 return m_target->uid().toHex(':');
143 }
144 W_SLOT(uid)
145
146 int type() const
147 {
148 if(!m_target)
149 return -1;
150 return static_cast<int>(m_target->type());
151 }
152 W_SLOT(type)
153
154 void readNdef()
155 {
156 if(m_target)
157 m_target->readNdefMessages();
158 }
159 W_SLOT(readNdef)
160
161 void writeNdef(QJSValue records)
162 {
163 if(!m_target)
164 return;
165 auto* engine = qjsEngine(this);
166 if(!engine || !records.isArray())
167 return;
168
169 QNdefMessage message;
170 auto length = records.property("length").toInt();
171 for(int i = 0; i < length; ++i)
172 {
173 auto rec = records.property(i);
174 auto recType = rec.property("type").toString();
175 if(recType == "text")
176 {
177 QNdefNfcTextRecord textRecord;
178 textRecord.setText(rec.property("text").toString());
179 if(rec.hasProperty("locale"))
180 textRecord.setLocale(rec.property("locale").toString());
181 else
182 textRecord.setLocale(QStringLiteral("en"));
183 message.append(textRecord);
184 }
185 else if(recType == "uri")
186 {
187 QNdefNfcUriRecord uriRecord;
188 uriRecord.setUri(QUrl(rec.property("uri").toString()));
189 message.append(uriRecord);
190 }
191 else if(recType == "raw")
192 {
193 QNdefRecord rawRecord;
194 rawRecord.setTypeNameFormat(static_cast<QNdefRecord::TypeNameFormat>(
195 rec.property("typeNameFormat").toInt()));
196 rawRecord.setType(rec.property("recordType").toVariant().toByteArray());
197 rawRecord.setPayload(
198 rec.property("payload").toVariant().toByteArray());
199 if(rec.hasProperty("id"))
200 rawRecord.setId(rec.property("id").toVariant().toByteArray());
201 message.append(rawRecord);
202 }
203 }
204
205 m_target->writeNdefMessages({message});
206 }
207 W_SLOT(writeNdef)
208
209 void sendCommand(QByteArray command)
210 {
211 if(m_target)
212 m_target->sendCommand(command);
213 }
214 W_SLOT(sendCommand)
215
216 QJSValue onNdefMessage;
217 QJSValue onError;
218
219private:
220 QPointer<QNearFieldTarget> m_target;
221};
222
223class qml_nfc_scanner
224 : public QObject
225 , public Nano::Observer
226{
227 W_OBJECT(qml_nfc_scanner)
228public:
229 qml_nfc_scanner()
230 {
231 m_manager = new QNearFieldManager(this);
232
233 QObject::connect(
234 m_manager, &QNearFieldManager::targetDetected, this,
235 [this](QNearFieldTarget* target) {
236 if(!onTargetDetected.isCallable())
237 return;
238 auto* engine = qjsEngine(this);
239 if(!engine)
240 return;
241
242 // Qt's QNearFieldManager retains ownership of the target for the
243 // manager's lifetime (per docs). On some backends (Android) the
244 // same target pointer can be re-emitted via targetDetected when
245 // a previously seen tag re-enters range. Reuse the existing
246 // wrapper in that case so we don't end up with two wrappers
247 // connected to the same target's signals (which would double
248 // up onNdefMessage/onError callbacks).
249 auto existing = target->findChildren<qml_nfc_target*>(
250 QString(), Qt::FindDirectChildrenOnly);
251 if(!existing.isEmpty())
252 {
253 onTargetDetected.call({engine->newQObject(existing.first())});
254 return;
255 }
256
257 // Parent the wrapper to the target so it lives as long as the
258 // target itself (which is owned by m_manager). Otherwise the
259 // wrapper would be JS-owned and could be garbage-collected
260 // while asynchronous NFC operations are still in flight.
261 auto* wrapper = new qml_nfc_target(target, target);
262 auto jsWrapper = engine->newQObject(wrapper);
263
264 if(onNdefMessage.isCallable())
265 wrapper->onNdefMessage = onNdefMessage;
266 if(onError.isCallable())
267 wrapper->onError = onError;
268
269 onTargetDetected.call({jsWrapper});
270 });
271
272 QObject::connect(
273 m_manager, &QNearFieldManager::targetLost, this,
274 [this](QNearFieldTarget*) {
275 if(onTargetLost.isCallable())
276 onTargetLost.call();
277 });
278
279 QObject::connect(
280 m_manager, &QNearFieldManager::adapterStateChanged, this,
281 [this](QNearFieldManager::AdapterState state) {
282 if(!onAdapterStateChanged.isCallable())
283 return;
284 QString stateStr;
285 switch(state)
286 {
287 case QNearFieldManager::AdapterState::Offline:
288 stateStr = QStringLiteral("offline");
289 break;
290 case QNearFieldManager::AdapterState::TurningOn:
291 stateStr = QStringLiteral("turning_on");
292 break;
293 case QNearFieldManager::AdapterState::Online:
294 stateStr = QStringLiteral("online");
295 break;
296 case QNearFieldManager::AdapterState::TurningOff:
297 stateStr = QStringLiteral("turning_off");
298 break;
299 }
300 onAdapterStateChanged.call({stateStr});
301 });
302 }
303
304 ~qml_nfc_scanner() { stop(); }
305
306 void start()
307 {
308 m_manager->startTargetDetection(QNearFieldTarget::NdefAccess);
309 }
310 W_SLOT(start)
311
312 void startRaw()
313 {
314 m_manager->startTargetDetection(QNearFieldTarget::TagTypeSpecificAccess);
315 }
316 W_SLOT(startRaw)
317
318 void stop() { m_manager->stopTargetDetection(); }
319 W_SLOT(stop)
320
321 bool isEnabled() const { return m_manager->isEnabled(); }
322 W_SLOT(isEnabled)
323
324 QJSValue onTargetDetected;
325 QJSValue onTargetLost;
326 QJSValue onNdefMessage;
327 QJSValue onAdapterStateChanged;
328 QJSValue onError;
329
330private:
331 QNearFieldManager* m_manager{};
332};
333
334} // namespace ossia::qt
335
336#endif // __has_include(<QNearFieldManager>)
Definition qml_device.cpp:43