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

traintastic / traintastic / 26121453997

19 May 2026 07:52PM UTC coverage: 25.063% (-0.6%) from 25.624%
26121453997

Pull #221

github

web-flow
Merge 598936246 into 15e38bcf7
Pull Request #221: Xpressnet new messages

49 of 1129 new or added lines in 16 files covered. (4.34%)

782 existing lines in 21 files now uncovered.

8483 of 33847 relevant lines covered (25.06%)

172.88 hits per line

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

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

23
#include "messages.hpp"
24
#include "../../../utils/tohex.hpp"
25

26
namespace XpressNet {
27

28
uint8_t calcChecksum(const Message& msg, const int dataSize)
×
29
{
30
  const auto* p = reinterpret_cast<const uint8_t*>(&msg);
×
31
  uint8_t checksum = p[0];
×
32
  for(int i = 1; i <= dataSize; i++)
×
33
    checksum ^= p[i];
×
34
  return checksum;
×
35
}
36

37
void updateChecksum(Message& msg)
×
38
{
39
  *(reinterpret_cast<uint8_t*>(&msg) + msg.dataSize() + 1) = calcChecksum(msg);
×
40
}
×
41

42
bool isChecksumValid(const Message& msg, const int dataSize)
×
43
{
44
  return calcChecksum(msg, dataSize) == *(reinterpret_cast<const uint8_t*>(&msg) + dataSize + 1);
×
45
}
46

NEW
47
std::string toString(const Message& message, bool raw, const Utils::PendingQuery &pendingQuery)
×
48
{
49
  std::string s = toHex(message.identification());
×
50

51
  // Human readable:
52
  switch(message.header)
×
53
  {
NEW
54
    case STOP_REQUEST:
×
55
    {
56
      if(message == ResumeOperationsRequest())
×
57
      {
58
        s = "RESUME_OPERATIONS_REQUEST";
×
59
      }
60
      else if(message == StopOperationsRequest())
×
61
      {
62
        s = "STOP_OPERATIONS_REQUEST";
×
63
      }
NEW
64
      else if(message == QueryCentralVersion())
×
65
      {
NEW
66
        s = "QUERY_CENTRAL_VERSION";
×
67
      }
NEW
68
      else if(message == CentralStatusRequest())
×
69
      {
NEW
70
        s = "CENTRAL_STATUS_REQUEST";
×
71
      }
72
      else
73
        raw = true;
×
74
      break;
×
75
    }
NEW
76
    case SET_ACCESSORY_V1:
×
77
    {
NEW
78
      const auto& req = static_cast<const AccessoryDecoderOperationRequestV1&>(message);
×
NEW
79
      s = "AccessoryDecoderOperationRequestOLD";
×
NEW
80
      s.append(" address=").append(std::to_string(req.address()));
×
NEW
81
      s.append(" port=").append(req.port() ? "2" : "1");
×
NEW
82
      s.append(req.activate() ? " activate" : " deactivate");
×
NEW
83
      break;
×
84
    }
NEW
85
    case SET_ACCESSORY_V3_8:
×
86
    {
NEW
87
      const auto& req = static_cast<const AccessoryDecoderOperationRequestV3&>(message);
×
NEW
88
      s = "AccessoryDecoderOperationRequest";
×
NEW
89
      s.append(" address=").append(std::to_string(req.address()));
×
NEW
90
      s.append(" port=").append(req.port() ? "2" : "1");
×
NEW
91
      s.append(req.activate() ? " activate" : " deactivate");
×
NEW
92
      break;
×
93
    }
NEW
94
    case BC_HEADER:
×
95
    {
96
      if(message == NormalOperationResumed())
×
97
      {
98
        s = "NORMAL_OPERATIONS_RESUMED";
×
99
      }
100
      else if(message == TrackPowerOff())
×
101
      {
102
        s = "TRACK_POWER_OFF";
×
103
      }
NEW
104
      else if(message == CommandStationBusy())
×
105
      {
NEW
106
        s = "CS_BUSY";
×
107
      }
NEW
108
      else if(message == CommandUnknown())
×
109
      {
NEW
110
        s = "CS_COMMAND_UNKNOWN";
×
111
      }
112
      else
113
        raw = true;
×
114
      break;
×
115
    }
NEW
116
    case REPLY_VERSION_2_3:
×
117
    {
NEW
118
      const auto& reply = static_cast<const CentralVersionReplyV1&>(message);
×
NEW
119
      if(reply.db1 == idCentralVersion)
×
120
      {
NEW
121
        s = "CS_VERSION_OLD";
×
NEW
122
        s.append(" version=");
×
NEW
123
        s.append(std::to_string(Utils::xbusVersionMajor(reply.versionHex)));
×
NEW
124
        s.append(".");
×
NEW
125
        s.append(std::to_string(Utils::xbusVersionMinor(reply.versionHex)));
×
126
      }
NEW
127
      else if(reply.db1 == idCentralStatusReply)
×
128
      {
NEW
129
        const auto& statusReply = static_cast<const CentralStatusReply&>(message);
×
NEW
130
        s = "CS_STATUS_REPLY";
×
NEW
131
        s.append(" status=");
×
NEW
132
        if(has(statusReply.status,
×
133
          CentralStatusFlags::EStop |
NEW
134
          CentralStatusFlags::PowerOff |
×
135
          CentralStatusFlags::ProgrammingModeOn))
136
        {
NEW
137
          if(has(statusReply.status, CentralStatusFlags::EStop))
×
NEW
138
            s.append("estop, ");
×
NEW
139
          if(has(statusReply.status, CentralStatusFlags::PowerOff))
×
NEW
140
            s.append("poweroff, ");
×
NEW
141
          if(has(statusReply.status, CentralStatusFlags::ProgrammingModeOn))
×
NEW
142
            s.append("programming, ");
×
143
        }
144
        else
145
        {
NEW
146
          s.append("run, ");
×
147
        }
148

NEW
149
        if(has(statusReply.status, CentralStatusFlags::AutoStart))
×
NEW
150
          s.append("auto-start, ");
×
151
        else
NEW
152
          s.append("maual-start, ");
×
153

NEW
154
        if(has(statusReply.status, CentralStatusFlags::ColdStart))
×
NEW
155
          s.append("cold-start, ");
×
NEW
156
        if(has(statusReply.status, CentralStatusFlags::RAMCheckFailure))
×
NEW
157
          s.append("RAM failure!, ");
×
158

NEW
159
        if(s.ends_with(", "))
×
160
        {
NEW
161
          s.pop_back();
×
NEW
162
          s.pop_back();
×
163
        }
164
      }
165
      else
NEW
166
        raw = true;
×
NEW
167
      break;
×
168
    }
NEW
169
  case REPLY_VERSION_3_0:
×
170
  {
NEW
171
    const auto& reply = static_cast<const CentralVersionReplyV3&>(message);
×
NEW
172
    if(reply.db1 == idCentralVersion)
×
173
    {
NEW
174
      s = "CS_VERSION_V3";
×
NEW
175
      s.append(" version=");
×
NEW
176
      s.append(std::to_string(Utils::xbusVersionMajor(reply.versionHex)));
×
NEW
177
      s.append(".");
×
NEW
178
      s.append(std::to_string(Utils::xbusVersionMinor(reply.versionHex)));
×
NEW
179
      s.append(" id=");
×
NEW
180
      s.append(std::to_string(uint8_t(reply.commandStationId())));
×
181
    }
182
    else
NEW
183
      raw = true;
×
NEW
184
    break;
×
185
  }
NEW
186
    case SET_STOP_LOCO:
×
187
    {
188
      if(message == StopAllLocomotivesRequest())
×
189
      {
190
        s = "STOP_ALL_LOCO_REQUEST";
×
191
      }
192
      else
193
        raw = true;
×
194
      break;
×
195
    }
NEW
196
    case BC_STOPPED:
×
197
    {
198
      if(message == EmergencyStop())
×
199
      {
200
        s = "EMERGENCY_STOP";
×
201
      }
202
      else
203
        raw = true;
×
204
      break;
×
205
    }
NEW
206
    case SET_STOP_LOCO_SINGLE:
×
207
    {
NEW
208
      const auto& stopLoco = static_cast<const EmergencyStopLocomotive&>(message);
×
NEW
209
      s = "EmergencyStopLocomotive";
×
NEW
210
      s.append(" address=").append(std::to_string(stopLoco.address()));
×
NEW
211
      if(stopLoco.isLongAddress())
×
NEW
212
        s.append("/long");
×
NEW
213
      break;
×
214
    }
NEW
215
    case GET_LOCO_INFO:
×
216
    {
NEW
217
      const auto& fakeReq = static_cast<const LocomotiveInstruction&>(message);
×
NEW
218
      switch (fakeReq.identification)
×
219
      {
NEW
220
      case idQueryLocoInfoBasic:
×
221
      {
NEW
222
        s = "QueryLocomotive";
×
NEW
223
        s.append(" address=").append(std::to_string(fakeReq.address()));
×
NEW
224
        break;
×
225
      }
NEW
226
      case idQueryFuncGroup4and5:
×
227
      case idQueryFuncGroup6above:
228
      {
NEW
229
        const auto& queryFunc = static_cast<const QueryLocomotiveFunctions&>(message);
×
NEW
230
        s = "QueryLocomotiveFunctions";
×
NEW
231
        if(queryFunc.identification == idQueryFuncGroup4and5)
×
NEW
232
          s += "4and5";
×
233
        else
NEW
234
          s += "6to10";
×
NEW
235
        s.append(" address=").append(std::to_string(fakeReq.address()));
×
NEW
236
        break;
×
237
      }
NEW
238
      case idReplyFuncF13F28:
×
239
      {
NEW
240
        const auto& funcInfo = static_cast<const FunctionInfoF13F28&>(message);
×
NEW
241
        s = "FunctionInfoF13F28";
×
NEW
242
        s.append(" address=");
×
NEW
243
        if(pendingQuery.address != 0 && pendingQuery.type == Utils::PendingQuery::FuncInfoF13F28)
×
NEW
244
          s.append(std::to_string(pendingQuery.address));
×
245
        else
NEW
246
          s.append("ERR!");
×
NEW
247
        for(uint8_t i = 13; i <= 28; i++)
×
NEW
248
          s.append(" f").append(std::to_string(i)).append("=").append(funcInfo.getFunction(i) ? "1" : "0");
×
NEW
249
        break;
×
250
      }
NEW
251
      case idLocomotiveBusy:
×
252
      {
NEW
253
        s = "LocomotiveBusy";
×
NEW
254
        s.append(" address=").append(std::to_string(fakeReq.address()));
×
NEW
255
        break;
×
256
      }
NEW
257
      case idQueryLocoCumulative_Roco:
×
258
      {
NEW
259
        s = "ROCOQueryLocoCumulative";
×
NEW
260
        s.append(" address=").append(std::to_string(fakeReq.address()));
×
NEW
261
        break;
×
262
      }
NEW
263
      default:
×
NEW
264
        raw = true;
×
NEW
265
        break;
×
266
      }
NEW
267
      break;
×
268
    }
NEW
269
    case SET_LOCO:
×
270
    {
NEW
271
      const auto& req = static_cast<const LocomotiveInstruction&>(message);
×
NEW
272
      switch (req.identification)
×
273
      {
NEW
274
      case idSetSpeed14:
×
275
      case idSetSpeed27:
276
      case idSetSpeed28:
277
      case idSetSpeed128:
278
      {
NEW
279
        const auto& spd = static_cast<const SpeedAndDirectionInstruction&>(message);
×
280

NEW
281
        s = "SpeedAndDirectionInstruction" + std::to_string(spd.speedSteps());
×
NEW
282
        s.append(" address=").append(std::to_string(spd.address()));
×
NEW
283
        if(spd.isLongAddress())
×
NEW
284
          s.append("/long");
×
NEW
285
        s.append(" direction=").append(spd.direction() == Direction::Forward ? "fwd" : "rev");
×
NEW
286
        s.append(" speed=");
×
NEW
287
        if(spd.isEmergencyStop())
×
NEW
288
          s.append("estop");
×
289
        else
NEW
290
          s.append(std::to_string(spd.speedStep())).append("/").append(std::to_string(spd.speedSteps()));
×
291

NEW
292
        if(spd.identification == idSetSpeed14)
×
NEW
293
          s.append(" f0=").append(static_cast<const SpeedAndDirectionInstruction14&>(spd).getFl() ? "1" : "0");
×
NEW
294
        break;
×
295
      }
NEW
296
      case idSetFuncGroup4_Roco:
×
297
      {
NEW
298
        const auto& setFunc = static_cast<const RocoMultiMAUS::FunctionInstructionF13F20&>(message);
×
299

NEW
300
        s = "ROCOFunctionInstructionF13F20";
×
NEW
301
        s.append(" address=").append(std::to_string(setFunc.address()));
×
NEW
302
        for(uint8_t i = 13; i <= 20; i++)
×
NEW
303
          s.append(" f").append(std::to_string(i)).append("=").append(setFunc.getFunction(i) ? "1" : "0");
×
NEW
304
        break;
×
305
      }
NEW
306
      default:
×
307
      {
NEW
308
        const auto& setFunc = static_cast<const FunctionInstructionGroup&>(message);
×
NEW
309
        const uint8_t funcGroup = setFunc.getGroup();
×
NEW
310
        if(funcGroup > 0)
×
311
        {
NEW
312
          const uint8_t funcMin = FunctionInstructionGroup::getMinFunctionIndex(funcGroup);
×
NEW
313
          const uint8_t funcMax = FunctionInstructionGroup::getMaxFunctionIndex(funcGroup);
×
314

NEW
315
          s = "FunctionInstructionGroup" + std::to_string(funcGroup);
×
NEW
316
          s.append(" address=").append(std::to_string(setFunc.address()));
×
NEW
317
          for(uint8_t i = funcMin; i <= funcMax; i++)
×
NEW
318
            s.append(" f").append(std::to_string(i)).append("=").append(setFunc.getFunction(i) ? "1" : "0");
×
NEW
319
          break;
×
320
        }
NEW
321
        else if((req.identification & LocomotiveInfo::identificationMask) == 0)
×
322
        {
NEW
323
          const auto& locoInfo = static_cast<const LocomotiveInfo&>(message);
×
NEW
324
          s = "LocomotiveInfo";
×
NEW
325
          s.append(" address=");
×
NEW
326
          if(pendingQuery.address != 0 && pendingQuery.type == Utils::PendingQuery::LocoInfoAndF0F12)
×
NEW
327
            s.append(std::to_string(pendingQuery.address));
×
328
          else
NEW
329
            s.append("ERR!");
×
NEW
330
          s.append(" direction=").append(locoInfo.direction() == Direction::Forward ? "fwd" : "rev");
×
NEW
331
          s.append(" speed=");
×
NEW
332
          if(locoInfo.isEmergencyStop())
×
NEW
333
            s.append("estop");
×
334
          else
NEW
335
            s.append(std::to_string(locoInfo.speedStep())).append("/").append(std::to_string(locoInfo.speedSteps()));
×
336

NEW
337
          for(uint8_t i = 0; i <= 12; i++)
×
NEW
338
            s.append(" f").append(std::to_string(i)).append("=").append(locoInfo.getFunction(i) ? "1" : "0");
×
339

NEW
340
          s.append(" busy=").append(locoInfo.isBusy() ? "1" : "0");
×
NEW
341
          break;
×
342
        }
343

NEW
344
        raw = true;
×
NEW
345
        break;
×
346
      }
347
      }
NEW
348
      break;
×
349
    }
NEW
350
    case FUNC_INFO_V4:
×
351
    {
NEW
352
      const auto& funcInfo = static_cast<const FunctionInfoF29F68&>(message);
×
NEW
353
      if(funcInfo.identification == idReplyFuncF29F68)
×
354
      {
NEW
355
        s = "FunctionInfoF29F68";
×
NEW
356
        s.append(" address=");
×
NEW
357
        if(pendingQuery.address != 0 && pendingQuery.type == Utils::PendingQuery::FuncInfoF29F68)
×
NEW
358
          s.append(std::to_string(pendingQuery.address));
×
359
        else
NEW
360
          s.append("ERR!");
×
NEW
361
        for(uint8_t i = 29; i <= 68; i++)
×
NEW
362
          s.append(" f").append(std::to_string(i)).append("=").append(funcInfo.getFunction(i) ? "1" : "0");
×
NEW
363
        break;
×
364
      }
365

NEW
366
      raw = true;
×
NEW
367
      break;
×
368
    }
NEW
369
    case LOCO_INFO_CUMULATIVE:
×
370
    {
NEW
371
      const auto& locoInfo = static_cast<const RocoMultiMAUS::LocomotiveCumulativeInfo&>(message);
×
NEW
372
      if((locoInfo.identification & RocoMultiMAUS::LocomotiveCumulativeInfo::identificationMask) == 0)
×
373
      {
NEW
374
        s = "ROCOLocoInfoCumulative";
×
NEW
375
        s.append(" address=");
×
NEW
376
        if(pendingQuery.address != 0 && pendingQuery.type == Utils::PendingQuery::ROCOCumulativeLocoInfo)
×
NEW
377
          s.append(std::to_string(pendingQuery.address));
×
378
        else
NEW
379
          s.append("ERR!");
×
NEW
380
        s.append(" direction=").append(locoInfo.direction() == Direction::Forward ? "fwd" : "rev");
×
NEW
381
        s.append(" speed=");
×
NEW
382
        if(locoInfo.isEmergencyStop())
×
NEW
383
          s.append("estop");
×
384
        else
NEW
385
          s.append(std::to_string(locoInfo.speedStep())).append("/").append(std::to_string(locoInfo.speedSteps()));
×
386

NEW
387
        for(uint8_t i = 0; i <= 20; i++)
×
NEW
388
          s.append(" f").append(std::to_string(i)).append("=").append(locoInfo.getFunction(i) ? "1" : "0");
×
389

NEW
390
        s.append(" busy=").append(locoInfo.isBusy() ? "1" : "0");
×
NEW
391
        break;
×
392
      }
393
      else
394
      {
NEW
395
        raw = true;
×
NEW
396
        break;
×
397
      }
398
      break;
399
    }
400
    default:
×
401
    {
402
      raw = true;
×
403
      break;
×
404
    }
405
    // FIXME: add all messages
406
  }
407

408
  if(raw)
×
409
  {
410
    // Raw data:
411
    s.append(" [");
×
412
    s.append(toHex(reinterpret_cast<const uint8_t*>(&message), message.size(), true));
×
413
    s.append("]");
×
414
  }
415

416
  return s;
×
417
}
×
418

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