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

traintastic / traintastic / 24288605255

11 Apr 2026 06:17PM UTC coverage: 25.599% (-2.4%) from 27.99%
24288605255

push

github

web-flow
Merge pull request #222 from traintastic/cbus

Added CBUS/VLCB hardware support

169 of 3369 new or added lines in 99 files covered. (5.02%)

5 existing lines in 4 files now uncovered.

8300 of 32423 relevant lines covered (25.6%)

178.31 hits per line

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

0.0
/server/src/hardware/protocol/cbus/cbustostring.cpp
1
/**
2
 * This file is part of Traintastic,
3
 * see <https://github.com/traintastic/traintastic>.
4
 *
5
 * Copyright (C) 2026 Reinder Feenstra
6
 *
7
 * This program is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU General Public License
9
 * as published by the Free Software Foundation; either version 2
10
 * of the License, or (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20
 */
21

22
#include "cbustostring.hpp"
23
#include "cbusmessages.hpp"
24
#include "../../../compat/stdformat.hpp"
25
#include "../../../utils/tohex.hpp"
26

27
namespace CBUS {
28

29
namespace {
30

31
template<typename T>
32
requires(std::is_base_of_v<Message, T>)
NEW
33
std::string formatEngineAddress(const T& message)
×
34
{
NEW
35
  return std::format(" {}={}", message.isLongAddress() ? "long_address" : "short_address", message.address());
×
36
}
37

NEW
38
constexpr std::string_view toString(GetEngineSession::Mode mode) noexcept
×
39
{
NEW
40
  switch(mode)
×
41
  {
42
    using enum GetEngineSession::Mode;
43

NEW
44
    case Request:
×
NEW
45
      return "request";
×
46

NEW
47
    case Steal:
×
NEW
48
      return "steal";
×
49

NEW
50
    case Share:
×
NEW
51
      return "share";
×
52
  }
NEW
53
  return {};
×
54
}
55

NEW
56
constexpr std::string_view toString(SetEngineSessionMode::SpeedMode mode) noexcept
×
57
{
NEW
58
  switch(mode)
×
59
  {
NEW
60
    case SetEngineSessionMode::SpeedMode128:
×
NEW
61
      return "128";
×
62

NEW
63
    case SetEngineSessionMode::SpeedMode14:
×
NEW
64
      return "14";
×
65

NEW
66
    case SetEngineSessionMode::SpeedMode28WithInterleaveSteps:
×
NEW
67
      return "28+interleave";
×
68

NEW
69
    case SetEngineSessionMode::SpeedMode28:
×
NEW
70
      return "28";
×
71
  }
NEW
72
  return {};
×
73
}
74

75
}
76

NEW
77
std::string toString(const Message& message)
×
78
{
NEW
79
  std::string s(toString(message.opCode));
×
80

NEW
81
  switch (message.opCode)
×
82
  {
83
    using enum OpCode;
84

85
    // 00-1F – 0 data byte packets:
NEW
86
    case ACK:
×
87
    case NAK:
88
    case HLT:
89
    case BON:
90
    case TOF:
91
    case TON:
92
    case ESTOP:
93
    case ARST:
94
    case RTOF:
95
    case RTON:
96
    case RESTP:
97
    case RSTAT:
98
    case QNN:
99
    case RQNP:
100
    case RQMN:
NEW
101
      break; // no data
×
102

103
    // 20–3F - 1 data byte packets:
NEW
104
    case KLOC:
×
105
    case QLOC:
106
    case DKEEP:
107
    {
NEW
108
      const auto& m = static_cast<const EngineSessionMessage&>(message);
×
NEW
109
      s.append(std::format(" session={}", m.session));
×
NEW
110
      break;
×
111
    }
NEW
112
    case DBG1:
×
NEW
113
      break;
×
114

NEW
115
    case EXTC:
×
NEW
116
      break;
×
117

118
    // 40–5F - 2 data byte packets:
NEW
119
    case RLOC:
×
120
    {
NEW
121
      const auto& m = static_cast<const RequestEngineSession&>(message);
×
NEW
122
      s.append(formatEngineAddress(m));
×
NEW
123
      break;
×
124
    }
NEW
125
    case QCON:
×
NEW
126
      break;
×
127

NEW
128
    case SNN:
×
NEW
129
      break;
×
130

NEW
131
    case ALOC:
×
NEW
132
      break;
×
133

NEW
134
    case STMOD:
×
135
    {
NEW
136
      const auto& m = static_cast<const SetEngineSessionMode&>(message);
×
NEW
137
      s.append(std::format(" session={} speed_mode={} service_mode={} sound_control_mode={}", m.session, toString(m.speedMode()), m.serviceMode(), m.soundControlMode()));
×
NEW
138
      break;
×
139
    }
NEW
140
    case PCON:
×
NEW
141
      break;
×
142

NEW
143
    case KCON:
×
NEW
144
      break;
×
145

NEW
146
    case DSPD:
×
147
    {
NEW
148
      const auto& m = static_cast<const SetEngineSpeedDirection&>(message);
×
NEW
149
      s.append(std::format(" session={} speed={} direction={}", m.session, m.speed(), m.directionForward() ? "fwd" : "rev"));
×
NEW
150
      break;
×
151
    }
NEW
152
    case DFLG:
×
NEW
153
      break;
×
154

NEW
155
    case DFNON:
×
156
    case DFNOF:
157
    {
NEW
158
      const auto& m = static_cast<const SetEngineFunction&>(message);
×
NEW
159
      s.append(std::format(" session={} number={}", m.session, m.number));
×
NEW
160
      break;
×
161
    }
NEW
162
    case SSTAT:
×
NEW
163
      break;
×
164

NEW
165
    case NNRSM:
×
NEW
166
      break;
×
167

NEW
168
    case RQNN:
×
NEW
169
      break;
×
170

NEW
171
    case NNREL:
×
NEW
172
      break;
×
173

NEW
174
    case NNACK:
×
NEW
175
      break;
×
176

NEW
177
    case NNLRN:
×
NEW
178
      break;
×
179

NEW
180
    case NNULN:
×
NEW
181
      break;
×
182

NEW
183
    case NNCLR:
×
NEW
184
      break;
×
185

NEW
186
    case NNEVN:
×
NEW
187
      break;
×
188

NEW
189
    case NERD:
×
NEW
190
      break;
×
191

NEW
192
    case RQEVN:
×
NEW
193
      break;
×
194

NEW
195
    case WRACK:
×
NEW
196
      break;
×
197

NEW
198
    case RQDAT:
×
NEW
199
      break;
×
200

NEW
201
    case RQDDS:
×
NEW
202
      break;
×
203

NEW
204
    case BOOTM:
×
NEW
205
      break;
×
206

NEW
207
    case ENUM:
×
NEW
208
      break;
×
209

NEW
210
    case NNRST:
×
NEW
211
      break;
×
212

NEW
213
    case EXTC1:
×
NEW
214
      break;
×
215

216
    // 60-7F - 3 data byte packets:
NEW
217
    case DFUN:
×
NEW
218
      break;
×
219

NEW
220
    case GLOC:
×
221
    {
NEW
222
      const auto& m = static_cast<const GetEngineSession&>(message);
×
NEW
223
      s.append(formatEngineAddress(m));
×
NEW
224
      s.append(std::format(" mode={}", toString(m.mode())));
×
NEW
225
      break;
×
226
    }
NEW
227
    case ERR:
×
228
    {
NEW
229
      const auto& m = static_cast<const CommandStationErrorMessage&>(message);
×
NEW
230
      s.append(std::format(" error={}", toString(m.errorCode)));
×
NEW
231
      switch(m.errorCode)
×
232
      {
233
        using enum DCCErr;
234

NEW
235
        case LocoStackFull:
×
236
        case LocoAddressTaken:
237
        case InvalidRequest:
238
        {
NEW
239
          s.append(formatEngineAddress(static_cast<const CommandStationLocoStackFullError&>(message)));
×
NEW
240
          break;
×
241
        }
NEW
242
        case SessionNotPresent:
×
243
        case LocoNotFound:
244
        case SessionCancelled:
245
        {
NEW
246
          s.append(std::format(" session={}", static_cast<const CommandStationSessionNotPresentError&>(message).session()));
×
NEW
247
          break;
×
248
        }
NEW
249
        case ConsistEmpty:
×
250
        {
NEW
251
          s.append(std::format(" consist={}", static_cast<const CommandStationConsistEmptyError&>(message).consist()));
×
NEW
252
          break;
×
253
        }
NEW
254
        case CANBusError:
×
NEW
255
          break;
×
256
      }
NEW
257
      break;
×
258
    }
NEW
259
    case CMDERR:
×
NEW
260
      break;
×
261

NEW
262
    case EVNLF:
×
NEW
263
      break;
×
264

NEW
265
    case NVRD:
×
NEW
266
      break;
×
267

NEW
268
    case NENRD:
×
NEW
269
      break;
×
270

NEW
271
    case RQNPN:
×
272
    {
NEW
273
      const auto& m = static_cast<const ReadNodeParameter&>(message);
×
NEW
274
      s.append(std::format(" node={} param={}", m.nodeNumber(), static_cast<uint8_t>(m.parameter)));
×
NEW
275
      break;
×
276
    }
NEW
277
    case NUMEV:
×
NEW
278
      break;
×
279

NEW
280
    case CANID:
×
NEW
281
      break;
×
282

NEW
283
    case EXTC2:
×
NEW
284
      break;
×
285

286
    // 80-9F - 4 data byte packets:
NEW
287
    case RDCC3:
×
NEW
288
      break;
×
289

NEW
290
    case WCVO:
×
NEW
291
      break;
×
292

NEW
293
    case WCVB:
×
NEW
294
      break;
×
295

NEW
296
    case QCVS:
×
NEW
297
      break;
×
298

NEW
299
    case PCVS:
×
NEW
300
      break;
×
301

NEW
302
    case ACON:
×
303
    case ACOF:
304
    case AREQ:
305
    case ARON:
306
    case AROF:
307
    {
NEW
308
      const auto& m = static_cast<const AccessoryRequestEvent&>(message); // same memory layout, only opcode is different
×
NEW
309
      s.append(std::format(" node={} event={}", m.nodeNumber(), m.eventNumber()));
×
NEW
310
      break;
×
311
    }
NEW
312
    case EVULN:
×
NEW
313
      break;
×
314

NEW
315
    case NVSET:
×
NEW
316
      break;
×
317

NEW
318
    case NVANS:
×
NEW
319
      break;
×
320

NEW
321
    case ASON:
×
322
    case ASOF:
323
    case ASRQ:
324
    case ARSON:
325
    case ARSOF:
326
    {
NEW
327
      const auto& m = static_cast<const AccessoryShortRequestEvent&>(message); // same memory layout, only opcode is different
×
NEW
328
      s.append(std::format(" node={} device={}", m.nodeNumber(), m.deviceNumber()));
×
NEW
329
      break;
×
330
    }
NEW
331
    case PARAN:
×
332
    {
NEW
333
      const auto& m = static_cast<const NodeParameterResponse&>(message);
×
NEW
334
      s.append(std::format(" node={} param={} value={}", m.nodeNumber(), static_cast<uint8_t>(m.parameter), m.value));
×
NEW
335
      break;
×
336
    }
NEW
337
    case REVAL:
×
NEW
338
      break;
×
339

NEW
340
    case EXTC3:
×
NEW
341
      break;
×
342

343
    // A0-BF - 5 data byte packets:
NEW
344
    case RDCC4:
×
NEW
345
      break;
×
346

NEW
347
    case WCVS:
×
NEW
348
      break;
×
349

NEW
350
    case ACON1:
×
NEW
351
      break;
×
352

NEW
353
    case ACOF1:
×
NEW
354
      break;
×
355

NEW
356
    case REQEV:
×
NEW
357
      break;
×
358

NEW
359
    case ARON1:
×
NEW
360
      break;
×
361

NEW
362
    case AROF1:
×
NEW
363
      break;
×
364

NEW
365
    case NEVAL:
×
NEW
366
      break;
×
367

NEW
368
    case PNN:
×
369
    {
NEW
370
      const auto& m = static_cast<const PresenceOfNode&>(message);
×
NEW
371
      s.append(std::format(" node={} manufacturer_id={} module_id={} flags=0x{:02X}", m.nodeNumber(), m.manufacturerId, m.moduleId, m.flags));
×
NEW
372
      break;
×
373
    }
NEW
374
    case ASON1:
×
NEW
375
      break;
×
376

NEW
377
    case ASOF1:
×
NEW
378
      break;
×
379

NEW
380
    case ARSON1:
×
NEW
381
      break;
×
382

NEW
383
    case ARSOF1:
×
NEW
384
      break;
×
385

NEW
386
    case EXTC4:
×
NEW
387
      break;
×
388

389
    // C0-DF - 6 data byte packets:
NEW
390
    case RDCC5:
×
NEW
391
      break;
×
392

NEW
393
    case WCVOA:
×
NEW
394
      break;
×
395

NEW
396
    case CABDAT:
×
NEW
397
      break;
×
398

NEW
399
    case FCLK:
×
NEW
400
      break;
×
401

NEW
402
    case ACON2:
×
403
    case ACOF2:
404
    case ARON2:
405
    case AROF2:
406
    {
NEW
407
      const auto& m = static_cast<const Accessory2On&>(message); // same memory layout, only opcode is different
×
NEW
408
      s.append(std::format(" node={} event={} data={} {} ({})", m.nodeNumber(), m.eventNumber(), m.data1, m.data2, m.data()));
×
NEW
409
      break;
×
410
    }
NEW
411
    case EVLRN:
×
NEW
412
      break;
×
413

NEW
414
    case EVANS:
×
NEW
415
      break;
×
416

NEW
417
    case ASON2:
×
NEW
418
      break;
×
419

NEW
420
    case ASOF2:
×
NEW
421
      break;
×
422

NEW
423
    case ARSON2:
×
NEW
424
      break;
×
425

NEW
426
    case ARSOF2:
×
NEW
427
      break;
×
428

NEW
429
    case EXTC5:
×
NEW
430
      break;
×
431

432
    // E0-FF - 7 data byte packets:
NEW
433
    case RDCC6:
×
NEW
434
      break;
×
435

NEW
436
    case PLOC:
×
437
    {
NEW
438
      const auto& m = static_cast<const EngineReport&>(message);
×
NEW
439
      s.append(std::format(" session={}", m.session));
×
NEW
440
      s.append(formatEngineAddress(m));
×
NEW
441
      s.append(std::format(" speed={} direction={} f0={} f1={} f2={} f3={} f4={} f5={} f6={} f7={} f8={} f9={} f10={} f11={} f12={}",
×
NEW
442
        m.speed(),
×
NEW
443
        m.directionForward() ? "fwd" : "rev",
×
NEW
444
        m.f0(), m.f1(), m.f2(), m.f3(), m.f4(),
×
NEW
445
        m.f5(), m.f6(), m.f7(), m.f8(),
×
NEW
446
        m.f9(), m.f10(), m.f11(), m.f12()));
×
NEW
447
      break;
×
448
    }
NEW
449
    case NAME:
×
NEW
450
      break;
×
451

NEW
452
    case STAT:
×
453
    {
NEW
454
      const auto& m = static_cast<const CommandStationStatusReport&>(message);
×
NEW
455
      s.append(std::format(" node={} cs_num={} flags=0x{:02X} major={} minor={} build={}", m.nodeNumber(), m.csNum, m.flags, m.majorRev, m.minorRev, m.build));
×
NEW
456
      break;
×
457
    }
NEW
458
    case PARAMS:
×
NEW
459
      break;
×
460

NEW
461
    case ACON3:
×
NEW
462
      break;
×
463

NEW
464
    case ACOF3:
×
NEW
465
      break;
×
466

NEW
467
    case ENRSP:
×
NEW
468
      break;
×
469

NEW
470
    case ARON3:
×
NEW
471
      break;
×
472

NEW
473
    case AROF3:
×
NEW
474
      break;
×
475

NEW
476
    case EVLRNI:
×
NEW
477
      break;
×
478

NEW
479
    case ACDAT:
×
NEW
480
      break;
×
481

NEW
482
    case ARDAT:
×
NEW
483
      break;
×
484

NEW
485
    case ASON3:
×
NEW
486
      break;
×
487

NEW
488
    case ASOF3:
×
NEW
489
      break;
×
490

NEW
491
    case DDES:
×
NEW
492
      break;
×
493

NEW
494
    case DDRS:
×
NEW
495
      break;
×
496

NEW
497
    case ARSON3:
×
NEW
498
      break;
×
499

NEW
500
    case ARSOF3:
×
NEW
501
      break;
×
502

NEW
503
    case EXTC6:
×
NEW
504
      break;
×
505
  }
506

NEW
507
  s.append(" [");
×
NEW
508
  s.append(toHex(&message, message.size(), true));
×
NEW
509
  s.append("]");
×
510

NEW
511
  return s;
×
NEW
512
}
×
513

514
}
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