• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mcallegari / qlcplus / 18357067171

08 Oct 2025 08:16PM UTC coverage: 34.26% (+2.2%) from 32.066%
18357067171

push

github

mcallegari
Merge branch 'master' into filedialog

1282 of 4424 new or added lines in 152 files covered. (28.98%)

1342 existing lines in 152 files now uncovered.

17704 of 51675 relevant lines covered (34.26%)

19430.31 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

16.84
/plugins/artnet/src/artnetpacketizer.cpp
1
/*
2
  Q Light Controller Plus
3
  artnetpacketizer.cpp
4

5
  Copyright (c) Massimo Callegari
6

7
  Licensed under the Apache License, Version 2.0 (the "License");
8
  you may not use this file except in compliance with the License.
9
  You may obtain a copy of the License at
10

11
      http://www.apache.org/licenses/LICENSE-2.0.txt
12

13
  Unless required by applicable law or agreed to in writing, software
14
  distributed under the License is distributed on an "AS IS" BASIS,
15
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
  See the License for the specific language governing permissions and
17
  limitations under the License.
18
*/
19

20
#include "artnetpacketizer.h"
21
#include "rdmprotocol.h"
22

23
#include <QStringList>
24
#include <QDebug>
25

26
ArtNetPacketizer::ArtNetPacketizer()
1✔
27
{
28
    // Initialize a commond header.
29
    // Changing only the tenth byte will create a valid ArtNet header
30
    m_commonHeader.clear();
1✔
31
    m_commonHeader.append(ARTNET_CODE_STR);
1✔
32
    m_commonHeader.append('\0');
1✔
33

34
    // empty opcode
35
    m_commonHeader.append('\0');
1✔
36
    m_commonHeader.append('\0');
1✔
37

38
    // version 14 by default
39
    m_commonHeader.append('\0');
1✔
40
    m_commonHeader.append((char)0x0e);
1✔
41

42
    m_sequence[0] = 1;
1✔
43
    m_sequence[1] = 1;
1✔
44
    m_sequence[2] = 1;
1✔
45
    m_sequence[3] = 1;
1✔
46
}
1✔
47

48
ArtNetPacketizer::~ArtNetPacketizer()
1✔
49
{
50
}
1✔
51

52
/*********************************************************************
53
 * Sender functions
54
 *********************************************************************/
55

56
void ArtNetPacketizer::setupArtNetPoll(QByteArray& data)
×
57
{
58
    data.clear();
×
59
    data.append(m_commonHeader);
×
60
    const char opCodeMSB = (ARTNET_POLL >> 8);
×
61
    data[9] = opCodeMSB;
×
62
    data.append((char)0x02); // TalkToMe
×
63
    data.append('\0'); // Priority
×
64
}
×
65

NEW
66
void ArtNetPacketizer::setupArtNetPollReply(QByteArray &data, QHostAddress ipAddr, QString MACaddr, quint32 universe, bool isInput)
×
67
{
68
    int i = 0;
×
69
    data.clear();
×
70
    data.append(m_commonHeader);
×
71
    data.remove(9, 2);
×
72
    const char opCodeMSB = (ARTNET_POLLREPLY >> 8);
×
73
    data[9] = opCodeMSB;
×
74
    QString ipStr = ipAddr.toString();
×
75
    QStringList ipAddrList = ipStr.split(".");
×
76
    foreach (QString val, ipAddrList)
×
77
        data.append((char)val.toInt()); // IP address[4]
×
78
    data.append((char)0x36);     // Port LSB
×
79
    data.append((char)0x19);     // Port MSB
×
80
    data.append((char)0x04);     // Version MSB
×
81
    data.append((char)0x20);     // Version LSB
×
NEW
82
    data.append((char)((universe >> 8) & 0xFF)); // NetSwitch (universe bits 14-8)Add commentMore actions
×
NEW
83
    data.append((char)((universe >> 4) & 0x0F)); // SubSwitch (universe bits 7-4)
×
84
    data.append((char)0xFF);     // OEM Value MSB
×
85
    data.append((char)0xFF);     // OEM Value LSB
×
86
    data.append((char)0x00);     // UBEA version
×
87
    data.append((char)0xF0);     // Status1 - Ready and booted
×
88
    data.append((char)0xFF);     // ESTA Manufacturer MSB
×
89
    data.append((char)0xFF);     // ESTA Manufacturer LSB
×
90

91
    data.append("QLC+");   // Short Name
×
92
    for (i = 0; i < 14; i++)
×
93
        data.append((char)0x00); // 14 bytes of stuffing
×
94
    data.append("Q Light Controller Plus - ArtNet interface"); // Long Name
×
95
    for (i = 0; i < 22; i++) // 64-42 bytes of stuffing. 42 is the length of the long name
×
96
        data.append((char)0x00);
×
97

98
    for (i = 0; i < 64; i++)
×
99
        data.append((char)0x00); // Node report
×
NEW
100
    data.append((char)0x00);     // NumPortsHi
×
NEW
101
    data.append((char)0x01);     // NumPortsLo
×
NEW
102
    data.append(isInput ? (char)0x40 : (char)0x80); // PortTypes[0]: can input or output DMX512 data
×
NEW
103
    data.append((char)0x00);     // PortTypes[1]: nothing
×
NEW
104
    data.append((char)0x00);     // PortTypes[2]: nothing
×
NEW
105
    data.append((char)0x00);     // PortTypes[3]: nothing
×
106

NEW
107
    data.append(isInput ? (char)0x80 : (char)0x00); // GoodInput[0] - input status port 1
×
NEW
108
    data.append((char)0x00);     // GoodInput[1] - input status port 2
×
NEW
109
    data.append((char)0x00);     // GoodInput[2] - input status port 3
×
NEW
110
    data.append((char)0x00);     // GoodInput[3] - input status port 4
×
111

NEW
112
    data.append(isInput ? (char)0x00 : (char)0x80); // GoodOutputA[0] - output status port 1
×
NEW
113
    data.append((char)0x00);     // GoodOutputA[0] - output status port 2
×
NEW
114
    data.append((char)0x00);     // GoodOutputA[0] - output status port 3
×
NEW
115
    data.append((char)0x00);     // GoodOutputA[0] - output status port 4
×
116

NEW
117
    data.append(isInput ? (char)universe : (char)0x00); // SwIn[0] - port 1
×
NEW
118
    data.append((char)0x00);     // SwIn[1] - port 2
×
NEW
119
    data.append((char)0x00);     // SwIn[2] - port 3
×
NEW
120
    data.append((char)0x00);     // SwIn[3] - port 4
×
121

NEW
122
    data.append(isInput ? (char)0x00 : (char)universe); // SwOut[0] - port 1
×
NEW
123
    data.append((char)0x00);     // SwOut[1] - port 2
×
NEW
124
    data.append((char)0x00);     // SwOut[2] - port 3
×
NEW
125
    data.append((char)0x00);     // SwOut[3] - port 4
×
126
    for (i = 0; i < 7; i++)
×
127
        data.append((char)0x00);  // SwVideo, SwMacro, SwRemote and 4 spare bytes
×
128
    QStringList MAC = MACaddr.split(":");
×
129
    foreach (QString couple, MAC)
×
130
    {
131
        bool ok;
132
        data.append((char)couple.toInt(&ok, 16));
×
UNCOV
133
    }
×
134
    for (i = 0; i < 32; i++)
×
135
        data.append((char)0x00); // bindIp[4], BindIndex, Status2 and filler
×
136
}
×
137

138
void ArtNetPacketizer::setupArtNetDmx(QByteArray& data, const int &universe, const QByteArray &values)
4✔
139
{
140
    data.clear();
4✔
141
    data.append(m_commonHeader);
4✔
142
    const char opCodeMSB = (ARTNET_DMX >> 8);
4✔
143
    data[9] = opCodeMSB;
4✔
144
    data.append(m_sequence[universe]); // Sequence
4✔
145
    data.append('\0'); // Physical
4✔
146
    data.append((char)(universe & 0x00FF));
4✔
147
    data.append((char)(universe >> 8));
4✔
148
    int padLength = values.isEmpty() ? 2 : (values.length() % 2); // length must be even in the range 2-512
4✔
149
    int len = values.length() + padLength;
4✔
150
    data.append((char)(len >> 8));
4✔
151
    data.append((char)(len & 0x00FF));
4✔
152
    data.append(values);
4✔
153
    data.append(QByteArray(padLength, 0));
4✔
154

155
    if (m_sequence[universe] == 0xff)
4✔
156
        m_sequence[universe] = 1;
×
157
    else
158
        m_sequence[universe]++;
4✔
159
}
4✔
160

161
void ArtNetPacketizer::setupArtNetTodRequest(QByteArray &data, const int &universe)
×
162
{
163
    data.clear();
×
164
    data.append(m_commonHeader);
×
165
    data[9] = char(ARTNET_TODREQUEST >> 8);
×
166
    data.append(char(0x00)); // Filler1
×
167
    data.append(char(0x00)); // Filler2
×
168
    data.append(char(0x00)); // Spare1
×
169
    data.append(char(0x00)); // Spare2
×
170
    data.append(char(0x00)); // Spare3
×
171
    data.append(char(0x00)); // Spare4
×
172
    data.append(char(0x00)); // Spare5
×
173
    data.append(char(0x00)); // Spare6
×
174
    data.append(char(0x00)); // Spare7
×
175

176
    data.append((char)(universe >> 8));     // Net
×
177
    data.append(char(0x00));                // Command: TodFull
×
178
    data.append(char(0x01));                // AddCount
×
179
    data.append((char)(universe & 0x00FF)); // Address
×
180
}
×
181

182
void ArtNetPacketizer::setupArtNetRdm(QByteArray &data, const int &universe, uchar command, QVariantList params)
×
183
{
184
    RDMProtocol rdm;
×
185
    QByteArray ba;
×
186

187
    data.clear();
×
188
    data.append(m_commonHeader);
×
189
    data[9] = char(ARTNET_RDM >> 8);
×
190
    data.append(char(0x01)); // RDM version 1.0
×
191
    data.append(char(0x00)); // Filler1
×
192
    data.append(char(0x00)); // Spare1
×
193
    data.append(char(0x00)); // Spare2
×
194
    data.append(char(0x00)); // Spare3
×
195
    data.append(char(0x00)); // Spare4
×
196
    data.append(char(0x00)); // Spare5
×
197
    data.append(char(0x00)); // Spare6
×
198
    data.append(char(0x00)); // Spare7
×
199

200
    data.append((char)(universe >> 8));     // Net
×
201
    data.append(char(0x00));                // ArProcess
×
202
    data.append((char)(universe & 0x00FF)); // Address
×
203

204
    rdm.packetizeCommand(command, params, false, ba);
×
205
    data.append(ba);
×
206
}
×
207

208
/*********************************************************************
209
 * Receiver functions
210
 *********************************************************************/
211

212
bool ArtNetPacketizer::checkPacketAndCode(QByteArray const& data, quint16 &code)
×
213
{
214
    /* An ArtNet header must be at least 12 bytes long */
215
    if (data.length() < 12)
×
216
        return false;
×
217

218
    /* Check "Art-Net" keyword presence */
219
    if (data.indexOf(ARTNET_CODE_STR) != 0)
×
220
        return false;
×
221

222
    if (data.at(7) != 0x00)
×
223
        return false;
×
224

NEW
225
    code = (quint16(data.at(9)) << 8) + quint16(data.at(8));
×
226

227
    return true;
×
228
}
229

230
bool ArtNetPacketizer::fillArtPollReplyInfo(QByteArray const& data, ArtNetNodeInfo& info)
×
231
{
232
    if (data.isNull())
×
233
        return false;
×
234

235
    QByteArray shortName = data.mid(26, 18);
×
236
    QByteArray longName = data.mid(44, 64);
×
NEW
237
    QByteArray nodeReport = data.mid(108, 64);
×
NEW
238
        uchar inputStatus = uchar(data.at(178));
×
239

NEW
240
    info.shortName = QString(shortName.replace(0, 0x20)).simplified();
×
NEW
241
    info.longName = QString(longName.replace(0, 0x20)).simplified();
×
NEW
242
    info.portsNumber = (uchar(data.at(172)) << 8) + uchar(data.at(173));
×
NEW
243
    info.isInput = (inputStatus & 0x04) == 0 ? true : false;
×
NEW
244
    info.isOutput = (inputStatus & 0x04) ? true : false;
×
NEW
245
    info.universe = (ushort(data.at(18)) << 8) + (ushort(data.at(19)) << 4) + ushort(data.at(186));
×
246

247
#if 0
248
    qDebug() << "getArtPollReplyInfo shortName:" << info.shortName;
249
    qDebug() << "getArtPollReplyInfo longName:" << info.longName;Add commentMore actions
250
    qDebug() << "getArtPollReplyInfo nodeReport:" << QString(nodeReport).simplified();
251
    qDebug() << "getArtPollReplyInfo universe:" << QString::number(info.universe);
252
#endif
253

254
    return true;
×
UNCOV
255
}
×
256

257
bool ArtNetPacketizer::fillDMXdata(QByteArray const& data, QByteArray &dmx, quint32 &universe)
×
258
{
259
    if (data.isNull())
×
260
        return false;
×
261
    dmx.clear();
×
262
    //char sequence = data.at(12);
263
    //qDebug() << "Sequence: " << sequence;
264
    // char physical = data.at(13) // skipped
265
    universe = (data.at(15) << 8) + data.at(14);
×
266

267
    unsigned int msb = (data.at(16)&0xff);
×
268
    unsigned int lsb = (data.at(17)&0xff);
×
269
    int length = (msb << 8) | lsb;
×
270

271
    //qDebug() << "length: " << length;
272
    dmx.append(data.mid(18, length));
×
273
    return true;
×
274
}
275

276
bool ArtNetPacketizer::processTODdata(const QByteArray &data, quint32 &universe, QVariantMap &values)
×
277
{
278
    if (data.isNull() || data.length() < 28)
×
279
        return false;
×
280

281
    // 0 - 11 ArtNet header
282
    // 12 RDM version
283
    // 13 Port
284
    // 14 - 20 Spare
285
    // 21, 23 address
286
    universe = (data.at(21) << 8) + data.at(23);
×
287
    // 22 Command response
288
    // 24 - 25 UID total
289
    //quint16 uidTotal = (quint8(data.at(24)) << 8) + quint8(data.at(25));
290
    // 26 BlockCount (consider only when total > 200)
291
    // 27 UID count
292
    quint8 uidCount = quint8(data.at(27));
×
293

294
    qDebug() << "UID count:" << uidCount;
×
295

296
    for (int i = 0; i < uidCount; i++)
×
297
    {
298
        quint16 ESTAId;
299
        quint32 deviceId;
300
        QString UID = RDMProtocol::byteArrayToUID(data.mid(28 + (i * 6), 6), ESTAId, deviceId);
×
301
        qDebug() << "UID:" << UID;
×
302
        values.insert(QString("UID-%1").arg(i), UID);
×
UNCOV
303
    }
×
304
    values.insert("DISCOVERY_COUNT", uidCount);
×
305

306
    return true;
×
307
}
308

309
bool ArtNetPacketizer::processRDMdata(const QByteArray &data, quint32 &universe, QVariantMap &values)
×
310
{
311
    if (data.isNull() || data.length() < 24)
×
312
        return false;
×
313

314
    // 0 - 11 ArtNet header
315
    // 12 RDM version
316
    // 13 - 20 zero fillers
317
    // 21, 23 address
318
    universe = (data.at(21) << 8) + data.at(23);
×
319

320
    RDMProtocol rdm;
×
321
    return rdm.parsePacket(data.mid(24), values);
×
322
}
323

STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc