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

mcallegari / qlcplus / 19144422256

06 Nov 2025 05:33PM UTC coverage: 34.256% (-0.1%) from 34.358%
19144422256

push

github

mcallegari
Back to 5.1.0 debug

17718 of 51723 relevant lines covered (34.26%)

19528.23 hits per line

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

80.92
/plugins/enttecwing/src/playbackwing.cpp
1
/*
2
  Q Light Controller
3
  playbackwing.cpp
4

5
  Copyright (c) Heikki Junnila
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 <QHostAddress>
21
#include <QMessageBox>
22
#include <QByteArray>
23
#include <QUdpSocket>
24
#include <QString>
25
#include <QDebug>
26

27
#include "playbackwing.h"
28

29

30
/****************************************************************************
31
 * Playback wing specifics
32
 ****************************************************************************/
33
/*
34
The ENTTEC Playback wing produces packets that contain
35
WING_PLAYBACK_BUTTON_SIZE bytes of button data. Each bit in the button bytes
36
signifies the state of one button. Thus, each byte contains 8 buttons, and
37
they are in reversed order, with some of them (27, 28, 29, 22, 23, 24, 25, 26)
38
mixed up in a very weird way:
39

40
WING_PLAYBACK_BYTE_BUTTON +
41
                04  03  02  01  00
42
----------------------------------
43
bit 0 : buttons 07, 15, 28, 31, 39
44
bit 1 : buttons 06, 14, 27, 30, 38
45
bit 2 : buttons 05, 13, 21, 26, 37
46
bit 3 : buttons 04, 12, 20, 25, 36
47
bit 4 : buttons 03, 11, 19, 24, 35
48
bit 5 : buttons 02, 10, 18, 23, 34
49
bit 6 : buttons 01, 09, 17, 22, 33
50
bit 7 : buttons 00, 08, 16, 29, 32
51

52
As it can be seen from the table above, the byte order is also reversed:
53

54
WING_PLAYBACK_BYTE_BUTTON + 0: Buttons 32 - 39 (8 buttons)
55
WING_PLAYBACK_BYTE_BUTTON + 1: Buttons 24 - 31 (8 buttons)
56
WING_PLAYBACK_BYTE_BUTTON + 2: Buttons 16 - 23 (8 buttons)
57
WING_PLAYBACK_BYTE_BUTTON + 3: Buttons 08 - 15 (8 buttons)
58
WING_PLAYBACK_BYTE_BUTTON + 4: Buttons 00 - 07 (8 buttons)
59

60
The Playback Wing contains also WING_PLAYBACK_SLIDER_SIZE bytes of slider data,
61
where each byte contains an 8bit char value signifying the slider value. Slider
62
bytes are not reversed:
63

64
WING_PLAYBACK_BYTE_SLIDER + 0: Slider 01 (0-255)
65
WING_PLAYBACK_BYTE_SLIDER + 1: Slider 02 (0-255)
66
WING_PLAYBACK_BYTE_SLIDER + 2: Slider 03 (0-255)
67
WING_PLAYBACK_BYTE_SLIDER + 3: Slider 04 (0-255)
68
WING_PLAYBACK_BYTE_SLIDER + 4: Slider 05 (0-255)
69
WING_PLAYBACK_BYTE_SLIDER + 5: Slider 06 (0-255)
70
WING_PLAYBACK_BYTE_SLIDER + 6: Slider 07 (0-255)
71
WING_PLAYBACK_BYTE_SLIDER + 7: Slider 08 (0-255)
72
WING_PLAYBACK_BYTE_SLIDER + 8: Slider 09 (0-255)
73
WING_PLAYBACK_BYTE_SLIDER + 9: Slider 10 (0-255)
74
*/
75

76
#define WING_PLAYBACK_BYTE_BUTTON 7 /* Bytes 7-11 are for buttons */
77
#define WING_PLAYBACK_BUTTON_SIZE 5 /* 5 bytes of button states */
78

79
#define WING_PLAYBACK_BYTE_SLIDER 15 /* Bytes 15-25 are for sliders */
80
#define WING_PLAYBACK_SLIDER_SIZE 10 /* 10 slider values in all */
81

82
#define WING_PLAYBACK_PACKET_SIZE (WING_PLAYBACK_BYTE_SLIDER + WING_PLAYBACK_SLIDER_SIZE) /* 25, total packet size */
83

84
#define WING_PLAYBACK_BYTE_EXTRA_BUTTONS 6
85
#define WING_PLAYBACK_BIT_PAGEUP   (1 << 7)
86
#define WING_PLAYBACK_BIT_PAGEDOWN (1 << 6)
87
#define WING_PLAYBACK_BIT_BACK     (1 << 5)
88
#define WING_PLAYBACK_BIT_GO       (1 << 4)
89

90
/** Should constitute up to 50 channels */
91
#define WING_PLAYBACK_CHANNEL_COUNT 8 * WING_PLAYBACK_BUTTON_SIZE \
92
                                        + WING_PLAYBACK_SLIDER_SIZE
93

94
/** number of extra buttons (go,back, pageup, pagedown) */
95
#define WING_PLAYBACK_EXTRA_BUTTONS_COUNT 4
96
#define WING_PLAYBACK_BUTTON_GO 50
97
#define WING_PLAYBACK_BUTTON_BACK 51
98
#define WING_PLAYBACK_BUTTON_PAGEDOWN 52
99
#define WING_PLAYBACK_BUTTON_PAGEUP 53
100

101

102
#define WING_PLAYBACK_INPUT_VERSION 1
103
#define WING_PLAYBACK_INPUT_BYTE_VERSION 4
104
#define WING_PLAYBACK_INPUT_BYTE_PAGE 37
105

106
/****************************************************************************
107
 * Initialization
108
 ****************************************************************************/
109

110
PlaybackWing::PlaybackWing(QObject* parent, const QHostAddress& address,
1✔
111
                           const QByteArray& data)
1✔
112
    : Wing(parent, address, data)
1✔
113
{
114
    m_values = QByteArray((WING_PLAYBACK_CHANNEL_COUNT) + (WING_PLAYBACK_EXTRA_BUTTONS_COUNT), 0);
1✔
115

116
    /* Playback wing keys seem to be in a somewhat weird order */
117
    m_channelMap[0] = 7 + WING_PLAYBACK_SLIDER_SIZE;
1✔
118
    m_channelMap[1] = 6 + WING_PLAYBACK_SLIDER_SIZE;
1✔
119
    m_channelMap[2] = 5 + WING_PLAYBACK_SLIDER_SIZE;
1✔
120
    m_channelMap[3] = 4 + WING_PLAYBACK_SLIDER_SIZE;
1✔
121
    m_channelMap[4] = 3 + WING_PLAYBACK_SLIDER_SIZE;
1✔
122
    m_channelMap[5] = 2 + WING_PLAYBACK_SLIDER_SIZE;
1✔
123
    m_channelMap[6] = 1 + WING_PLAYBACK_SLIDER_SIZE;
1✔
124
    m_channelMap[7] = 0 + WING_PLAYBACK_SLIDER_SIZE;
1✔
125

126
    m_channelMap[8] = 15 + WING_PLAYBACK_SLIDER_SIZE;
1✔
127
    m_channelMap[9] = 14 + WING_PLAYBACK_SLIDER_SIZE;
1✔
128
    m_channelMap[10] = 13 + WING_PLAYBACK_SLIDER_SIZE;
1✔
129
    m_channelMap[11] = 12 + WING_PLAYBACK_SLIDER_SIZE;
1✔
130
    m_channelMap[12] = 11 + WING_PLAYBACK_SLIDER_SIZE;
1✔
131
    m_channelMap[13] = 10 + WING_PLAYBACK_SLIDER_SIZE;
1✔
132
    m_channelMap[14] = 9 + WING_PLAYBACK_SLIDER_SIZE;
1✔
133
    m_channelMap[15] = 8 + WING_PLAYBACK_SLIDER_SIZE;
1✔
134

135
    /* Weird order here */
136
    m_channelMap[16] = 28 + WING_PLAYBACK_SLIDER_SIZE;
1✔
137
    m_channelMap[17] = 27 + WING_PLAYBACK_SLIDER_SIZE;
1✔
138
    m_channelMap[18] = 21 + WING_PLAYBACK_SLIDER_SIZE;
1✔
139
    m_channelMap[19] = 20 + WING_PLAYBACK_SLIDER_SIZE;
1✔
140
    m_channelMap[20] = 19 + WING_PLAYBACK_SLIDER_SIZE;
1✔
141
    m_channelMap[21] = 18 + WING_PLAYBACK_SLIDER_SIZE;
1✔
142
    m_channelMap[22] = 17 + WING_PLAYBACK_SLIDER_SIZE;
1✔
143
    m_channelMap[23] = 16 + WING_PLAYBACK_SLIDER_SIZE;
1✔
144

145
    /* Weird order also here */
146
    m_channelMap[24] = 31 + WING_PLAYBACK_SLIDER_SIZE;
1✔
147
    m_channelMap[25] = 30 + WING_PLAYBACK_SLIDER_SIZE;
1✔
148
    m_channelMap[26] = 26 + WING_PLAYBACK_SLIDER_SIZE;
1✔
149
    m_channelMap[27] = 25 + WING_PLAYBACK_SLIDER_SIZE;
1✔
150
    m_channelMap[28] = 24 + WING_PLAYBACK_SLIDER_SIZE;
1✔
151
    m_channelMap[29] = 23 + WING_PLAYBACK_SLIDER_SIZE;
1✔
152
    m_channelMap[30] = 22 + WING_PLAYBACK_SLIDER_SIZE;
1✔
153
    m_channelMap[31] = 29 + WING_PLAYBACK_SLIDER_SIZE;
1✔
154

155
    m_channelMap[32] = 39 + WING_PLAYBACK_SLIDER_SIZE;
1✔
156
    m_channelMap[33] = 38 + WING_PLAYBACK_SLIDER_SIZE;
1✔
157
    m_channelMap[34] = 37 + WING_PLAYBACK_SLIDER_SIZE;
1✔
158
    m_channelMap[35] = 36 + WING_PLAYBACK_SLIDER_SIZE;
1✔
159
    m_channelMap[36] = 35 + WING_PLAYBACK_SLIDER_SIZE;
1✔
160
    m_channelMap[37] = 34 + WING_PLAYBACK_SLIDER_SIZE;
1✔
161
    m_channelMap[38] = 33 + WING_PLAYBACK_SLIDER_SIZE;
1✔
162
    m_channelMap[39] = 32 + WING_PLAYBACK_SLIDER_SIZE;
1✔
163

164
    m_needSync = true;
1✔
165

166
    /* Take initial values from the first received datagram packet.
167
       The plugin hasn't yet connected to valueChanged() signal, so this
168
       won't cause any input events. */
169
    parseData(data);
1✔
170
    sendPageData();
1✔
171
}
1✔
172

173
PlaybackWing::~PlaybackWing()
2✔
174
{
175
}
2✔
176

177
/****************************************************************************
178
 * Wing data
179
 ****************************************************************************/
180

181
QString PlaybackWing::name() const
3✔
182
{
183
    QString name("Playback");
3✔
184
    name += QString(" ") + tr("at") + QString(" ");
3✔
185
    name += m_address.toString();
3✔
186

187
    return name;
3✔
188
}
×
189

190
/****************************************************************************
191
 * Input data
192
 ****************************************************************************/
193

194
void PlaybackWing::parseData(const QByteArray& data)
76✔
195
{
196
    if (data.size() < WING_PLAYBACK_PACKET_SIZE)
76✔
197
    {
198
        qWarning() << Q_FUNC_INFO << "Expected at least" << WING_PLAYBACK_PACKET_SIZE
2✔
199
                   << "bytes for buttons but got only" << data.size();
1✔
200
        return;
1✔
201
    }
202

203
    /* Check if page buttons were pressed and act accordingly */
204
    applyExtraButtons(data);
75✔
205

206
    int size = WING_PLAYBACK_BYTE_BUTTON + WING_PLAYBACK_BUTTON_SIZE;
75✔
207

208
    /* Read the state of each button */
209
    for (int byte = size - 1; byte >= WING_PLAYBACK_BYTE_BUTTON; byte--)
450✔
210
    {
211
        /* Each byte has 8 button values as binary bits */
212
        for (int bit = 7; bit >= 0; bit--)
3,375✔
213
        {
214
            uchar value;
215

216
            /* Calculate the key number, which is 10-49, since
217
               sliders are mapped to 0-9. */
218
            int key = (size - byte - 1) * 8;
3,000✔
219
            key += bit;
3,000✔
220

221
            /* 0 = button down, 1 = button up */
222
            if ((data[byte] & (1 << bit)) == 0)
3,000✔
223
                value = UCHAR_MAX;
160✔
224
            else
225
                value = 0;
2,840✔
226

227
            /* Get the correct channel number for each key. */
228
            setCacheValue(m_channelMap[key], value);
3,000✔
229
        }
230
    }
231

232
    //size = WING_PLAYBACK_BYTE_SLIDER + WING_PLAYBACK_SLIDER_SIZE;
233

234
    /* Read the state of each slider. Each value takes all 8 bits. */
235
    for (int slider = 0; slider < WING_PLAYBACK_SLIDER_SIZE; slider++)
825✔
236
    {
237
        if (m_needSync)
750✔
238
        {
239
            // store value diffs to sync qlcplus widgets and wing slider
240
            if (!m_feedbackDiffs.contains(page()))
10✔
241
                m_feedbackDiffs.insert(page(), QVector<int>(WING_PLAYBACK_SLIDER_SIZE, 0));
1✔
242
            if (!m_feedbackValues.contains(page()))
10✔
243
                m_feedbackValues.insert(page(), QByteArray(WING_PLAYBACK_SLIDER_SIZE, 0));
1✔
244

245
            m_feedbackDiffs[page()][slider] = quint8(m_feedbackValues[page()][slider]) - quint8(cacheValue(slider));
10✔
246
        }
247

248
        int diff = 0;
750✔
249
        if (m_feedbackDiffs.contains(page()))
750✔
250
            diff = m_feedbackDiffs[page()][slider];
750✔
251

252
        char value = data[WING_PLAYBACK_BYTE_SLIDER + slider];
750✔
253

254
        if (m_feedbackValues.contains(page()) && diff != 0)
750✔
255
        {
256
            //check sync status
257
            int curdiff = quint8(m_feedbackValues[page()][slider]) - quint8(data[WING_PLAYBACK_BYTE_SLIDER + slider]);
×
258

259
            // send input after crossing widget values (sign of diff is changing)
260
            if (curdiff == 0 || (curdiff > 0 && diff < 0)  || (curdiff < 0 && diff > 0))
×
261
            {
262
                setCacheValue(slider, value);
×
263
                if (m_feedbackDiffs.contains(page()))
×
264
                    m_feedbackDiffs[page()][slider] = 0;
×
265
            }
266
        }
267
        else
268
        {
269
            setCacheValue(slider, value);
750✔
270
        }
271
    }
272
    m_needSync = false;
75✔
273
}
274

275
void PlaybackWing::applyExtraButtons(const QByteArray& data)
75✔
276
{
277
    /* Check that there's enough data for flags */
278
    if (data.size() < WING_PLAYBACK_PACKET_SIZE)
75✔
279
        return;
×
280

281
    // WING_PLAYBACK_BIT_PAGEUP
282
    if (!(data[WING_PLAYBACK_BYTE_EXTRA_BUTTONS] & WING_PLAYBACK_BIT_PAGEUP))
75✔
283
    {
284
        nextPage();
75✔
285
        sendPageData();
75✔
286
        setCacheValue(WING_PLAYBACK_BUTTON_PAGEUP, UCHAR_MAX);
75✔
287
    }
288
    else
289
    {
290
        setCacheValue(WING_PLAYBACK_BUTTON_PAGEUP, 0);
×
291
    }
292

293
    // WING_PLAYBACK_BIT_PAGEDOWN
294
    if (!(data[WING_PLAYBACK_BYTE_EXTRA_BUTTONS] & WING_PLAYBACK_BIT_PAGEDOWN))
75✔
295
    {
296
        previousPage();
75✔
297
        sendPageData();
75✔
298
        setCacheValue(WING_PLAYBACK_BUTTON_PAGEDOWN, UCHAR_MAX);
75✔
299
    }
300
    else
301
    {
302
        setCacheValue(WING_PLAYBACK_BUTTON_PAGEDOWN, 0);
×
303
    }
304

305
    // WING_PLAYBACK_BIT_PAGEGO
306
    if (!(data[WING_PLAYBACK_BYTE_EXTRA_BUTTONS] & WING_PLAYBACK_BIT_GO))
75✔
307
    {
308
        setCacheValue(WING_PLAYBACK_BUTTON_GO, UCHAR_MAX);
75✔
309
    }
310
    else
311
    {
312
        setCacheValue(WING_PLAYBACK_BUTTON_GO, 0);
×
313
    }
314

315
    // WING_PLAYBACK_BIT_PAGEBACK
316
    if (!(data[WING_PLAYBACK_BYTE_EXTRA_BUTTONS] & WING_PLAYBACK_BIT_BACK))
75✔
317
    {
318
        setCacheValue(WING_PLAYBACK_BUTTON_BACK, UCHAR_MAX);
75✔
319
    }
320
    else
321
    {
322
        setCacheValue(WING_PLAYBACK_BUTTON_BACK, 0);
×
323
    }
324
}
325

326
void PlaybackWing::sendPageData()
151✔
327
{
328
    QByteArray sendData(42, char(0));
151✔
329
    sendData.replace(0, sizeof(WING_HEADER_INPUT), WING_HEADER_INPUT);
151✔
330
    sendData[WING_PLAYBACK_INPUT_BYTE_VERSION] = WING_PLAYBACK_INPUT_VERSION;
151✔
331
    sendData[WING_PLAYBACK_INPUT_BYTE_PAGE] = page() + 1;
151✔
332

333
    QUdpSocket sock(this);
151✔
334
    sock.writeDatagram(sendData, address(), Wing::UDPPort);
151✔
335
}
151✔
336

337
void PlaybackWing::feedBack(quint32 channel, uchar value)
×
338
{
339
    quint16 pageChan = channel & 0xFF;
×
340
    quint16 pageNum = channel >> 16;
×
341

342
    // create new byte array for page, to store values, if it not exists
343
    if (!m_feedbackValues.contains(pageNum))
×
344
        m_feedbackValues.insert(pageNum, QByteArray(WING_PLAYBACK_SLIDER_SIZE, 0));
×
345

346
    if (pageChan < WING_PLAYBACK_SLIDER_SIZE)
×
347
    {
348
        // store widget values for later use
349
        m_feedbackValues[pageNum][pageChan] = value;
×
350

351
        // check sync
352
        if (value != cacheValue(pageChan))
×
353
            m_needSync = true;
×
354
    }
355

356
    //set page
357
    if (pageChan == WING_PLAYBACK_BUTTON_PAGEDOWN || pageChan == WING_PLAYBACK_BUTTON_PAGEUP)
×
358
    {
359
        m_needSync = true;
×
360
        m_page = value;
×
361
        sendPageData();
×
362
    }
363
}
×
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