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

traintastic / traintastic / 23872903160

01 Apr 2026 09:58PM UTC coverage: 25.802% (+0.003%) from 25.799%
23872903160

push

github

reinder
[cbus] added cbus booster support, uses ACON2 events

44 of 136 new or added lines in 5 files covered. (32.35%)

62 existing lines in 7 files now uncovered.

8300 of 32168 relevant lines covered (25.8%)

179.72 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>)
33
std::string formatEngineAddress(const T& message)
×
34
{
35
  return std::format(" {}={}", message.isLongAddress() ? "long_address" : "short_address", message.address());
×
36
}
37

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

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

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

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

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

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

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

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

75
}
76

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

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

85
    // 00-1F – 0 data byte packets:
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:
101
      break; // no data
×
102

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

115
    case EXTC:
×
116
      break;
×
117

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

128
    case SNN:
×
129
      break;
×
130

131
    case ALOC:
×
132
      break;
×
133

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

143
    case KCON:
×
144
      break;
×
145

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

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

165
    case NNRSM:
×
166
      break;
×
167

168
    case RQNN:
×
169
      break;
×
170

171
    case NNREL:
×
172
      break;
×
173

174
    case NNACK:
×
175
      break;
×
176

177
    case NNLRN:
×
178
      break;
×
179

180
    case NNULN:
×
181
      break;
×
182

183
    case NNCLR:
×
184
      break;
×
185

186
    case NNEVN:
×
187
      break;
×
188

189
    case NERD:
×
190
      break;
×
191

192
    case RQEVN:
×
193
      break;
×
194

195
    case WRACK:
×
196
      break;
×
197

198
    case RQDAT:
×
199
      break;
×
200

201
    case RQDDS:
×
202
      break;
×
203

204
    case BOOTM:
×
205
      break;
×
206

207
    case ENUM:
×
208
      break;
×
209

210
    case NNRST:
×
211
      break;
×
212

213
    case EXTC1:
×
214
      break;
×
215

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

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

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

262
    case EVNLF:
×
263
      break;
×
264

265
    case NVRD:
×
266
      break;
×
267

268
    case NENRD:
×
269
      break;
×
270

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

280
    case CANID:
×
281
      break;
×
282

283
    case EXTC2:
×
284
      break;
×
285

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

290
    case WCVO:
×
291
      break;
×
292

293
    case WCVB:
×
294
      break;
×
295

296
    case QCVS:
×
297
      break;
×
298

299
    case PCVS:
×
300
      break;
×
301

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

315
    case NVSET:
×
316
      break;
×
317

318
    case NVANS:
×
319
      break;
×
320

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

340
    case EXTC3:
×
341
      break;
×
342

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

347
    case WCVS:
×
348
      break;
×
349

350
    case ACON1:
×
351
      break;
×
352

353
    case ACOF1:
×
354
      break;
×
355

356
    case REQEV:
×
357
      break;
×
358

359
    case ARON1:
×
360
      break;
×
361

362
    case AROF1:
×
363
      break;
×
364

365
    case NEVAL:
×
366
      break;
×
367

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

377
    case ASOF1:
×
378
      break;
×
379

380
    case ARSON1:
×
381
      break;
×
382

383
    case ARSOF1:
×
384
      break;
×
385

386
    case EXTC4:
×
387
      break;
×
388

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

393
    case WCVOA:
×
394
      break;
×
395

396
    case CABDAT:
×
397
      break;
×
398

399
    case FCLK:
×
400
      break;
×
401

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

414
    case EVANS:
×
415
      break;
×
416

417
    case ASON2:
×
UNCOV
418
      break;
×
419

420
    case ASOF2:
×
UNCOV
421
      break;
×
422

UNCOV
423
    case ARSON2:
×
424
      break;
×
425

426
    case ARSOF2:
×
UNCOV
427
      break;
×
428

429
    case EXTC5:
×
UNCOV
430
      break;
×
431

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

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

452
    case STAT:
×
453
    {
UNCOV
454
      const auto& m = static_cast<const CommandStationStatusReport&>(message);
×
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));
×
456
      break;
×
457
    }
458
    case PARAMS:
×
459
      break;
×
460

461
    case ACON3:
×
462
      break;
×
463

464
    case ACOF3:
×
465
      break;
×
466

467
    case ENRSP:
×
468
      break;
×
469

470
    case ARON3:
×
471
      break;
×
472

473
    case AROF3:
×
474
      break;
×
475

UNCOV
476
    case EVLRNI:
×
477
      break;
×
478

479
    case ACDAT:
×
UNCOV
480
      break;
×
481

482
    case ARDAT:
×
UNCOV
483
      break;
×
484

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

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

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

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

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

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

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

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

UNCOV
511
  return s;
×
UNCOV
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