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

traintastic / traintastic / 23669027368

27 Mar 2026 09:50PM UTC coverage: 26.198% (+0.02%) from 26.176%
23669027368

push

github

reinder
Merge remote-tracking branch 'origin/master' into cbus

11 of 144 new or added lines in 34 files covered. (7.64%)

1 existing line in 1 file now uncovered.

8256 of 31514 relevant lines covered (26.2%)

182.55 hits per line

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

0.0
/server/src/hardware/protocol/xpressnet/kernel.cpp
1
/**
2
 * server/src/hardware/protocol/xpressnet/kernel.cpp
3
 *
4
 * This file is part of the traintastic source code.
5
 *
6
 * Copyright (C) 2019-2026 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 "kernel.hpp"
24
#include "messages.hpp"
25
#include "../../decoder/decoder.hpp"
26
#include "../../decoder/decoderchangeflags.hpp"
27
#include "../../input/inputcontroller.hpp"
28
#include "../../../utils/setthreadname.hpp"
29
#include "../../../core/eventloop.hpp"
30
#include "../../../log/log.hpp"
31
#include "../../../log/logmessageexception.hpp"
32

33
namespace XpressNet {
34

35
Kernel::Kernel(std::string logId_, const Config& config, bool simulation)
×
36
  : KernelBase(std::move(logId_))
×
37
  , m_simulation{simulation}
×
38
  , m_decoderController{nullptr}
×
39
  , m_inputController{nullptr}
×
40
  , m_outputController{nullptr}
×
41
  , m_config{config}
×
42
{
43
}
×
44

45
void Kernel::setConfig(const Config& config)
×
46
{
NEW
47
  boost::asio::post(m_ioContext, 
×
48
    [this, newConfig=config]()
×
49
    {
50
      m_config = newConfig;
×
51
    });
×
52
}
×
53

54
void Kernel::start()
×
55
{
56
  assert(m_ioHandler);
×
57
  assert(!m_started);
×
58

59
  // reset all state values
60
  m_trackPowerOn = TriState::Undefined;
×
61
  m_emergencyStop = TriState::Undefined;
×
62
  m_inputValues.fill(TriState::Undefined);
×
63

64
  m_thread = std::thread(
×
65
    [this]()
×
66
    {
67
      setThreadName("xpressnet");
×
NEW
68
      boost::asio::executor_work_guard<decltype(m_ioContext.get_executor())> work{m_ioContext.get_executor()};
×
69
      m_ioContext.run();
×
70
    });
×
71

NEW
72
  boost::asio::post(m_ioContext, 
×
73
    [this]()
×
74
    {
75
      try
76
      {
77
        m_ioHandler->start();
×
78
      }
79
      catch(const LogMessageException& e)
×
80
      {
81
        EventLoop::call(
×
82
          [this, e]()
×
83
          {
84
            Log::log(logId, e.message(), e.args());
×
85
            error();
×
86
          });
×
87
        return;
×
88
      }
×
89
    });
90

91
#ifndef NDEBUG
92
  m_started = true;
×
93
#endif
94
}
×
95

96
void Kernel::stop()
×
97
{
NEW
98
  boost::asio::post(m_ioContext, 
×
99
    [this]()
×
100
    {
101
      m_ioHandler->stop();
×
102
    });
×
103

104
  m_ioContext.stop();
×
105

106
  m_thread.join();
×
107

108
#ifndef NDEBUG
109
  m_started = false;
×
110
#endif
111
}
×
112

113
void Kernel::started()
×
114
{
115
  assert(isKernelThread());
×
116

117
  KernelBase::started();
×
118
}
×
119

120
void Kernel::receive(const Message& message)
×
121
{
122
  if(m_config.debugLogRXTX)
×
123
    EventLoop::call(
×
124
      [this, msg=toString(message)]()
×
125
      {
126
        Log::log(logId, LogMessage::D2002_RX_X, msg);
×
127
      });
×
128

129
  switch(message.identification())
×
130
  {
131
    case idFeedbackBroadcast:
×
132
    {
133
      const auto* feedback = static_cast<const FeedbackBroadcast*>(&message);
×
134

135
      for(uint8_t i = 0; i < feedback->pairCount(); i++)
×
136
      {
137
        const FeedbackBroadcast::Pair& pair = feedback->pair(i);
×
138
        switch(pair.type())
×
139
        {
140
          case FeedbackBroadcast::Pair::Type::AccessoryDecoderWithoutFeedback:
×
141
            break; // not yet implemented
×
142

143
          case FeedbackBroadcast::Pair::Type::AccessoryDecoderWithFeedback:
×
144
            break; // not yet implemented
×
145

146
          case FeedbackBroadcast::Pair::Type::FeedbackModule:
×
147
            if(m_inputController)
×
148
            {
149
              const uint16_t baseAddress = pair.groupAddress() << 2;
×
150

151
              for(uint16_t j = 0; j < 4; j++)
×
152
              {
153
                const uint16_t fullAddress = baseAddress + j;
×
154
                const TriState value = toTriState((pair.statusNibble() & (1 << j)) != 0);
×
155
                if(m_inputValues[fullAddress] != value)
×
156
                {
157
                  if(m_config.debugLogInput)
×
158
                    EventLoop::call(
×
159
                      [this, address=1 + fullAddress, value]()
×
160
                      {
161
                        Log::log(logId, LogMessage::D2007_INPUT_X_IS_X, address, value == TriState::True ? std::string_view{"1"} : std::string_view{"0"});
×
162
                      });
×
163

164
                  m_inputValues[fullAddress] = value;
×
165

166
                  EventLoop::call(
×
167
                    [this, address=1 + fullAddress, value]()
×
168
                    {
169
                      m_inputController->updateInputValue(InputChannel::Input, InputAddress(address), value);
×
170
                    });
×
171
                }
172
              }
173
            }
174
            break;
×
175

176
          case FeedbackBroadcast::Pair::Type::ReservedForFutureUse:
×
177
            break;
×
178
        }
179
      }
180

181
      break;
×
182
    }
183
    case 0x60:
×
184
    {
185
      if(message == TrackPowerOff())
×
186
      {
187
        EventLoop::call(
×
188
          [this]()
×
189
          {
190
            if(m_trackPowerOn != TriState::False)
×
191
            {
192
              m_trackPowerOn = TriState::False;
×
193
              m_emergencyStop = TriState::False;
×
194
              if(m_onTrackPowerChanged)
×
195
                m_onTrackPowerChanged(false, false);
×
196
            }
197
          });
×
198
      }
199
      else if(message == NormalOperationResumed())
×
200
      {
201
        EventLoop::call(
×
202
          [this]()
×
203
          {
204
            if(m_trackPowerOn != TriState::True || m_emergencyStop != TriState::False)
×
205
            {
206
              m_trackPowerOn = TriState::True;
×
207
              m_emergencyStop = TriState::False;
×
208
              if(m_onTrackPowerChanged)
×
209
                m_onTrackPowerChanged(true, false);
×
210
            }
211
          });
×
212
        }
213
      break;
×
214
    }
215
    case 0x80:
×
216
    {
217
      if(message == EmergencyStop())
×
218
      {
219
        EventLoop::call(
×
220
          [this]()
×
221
          {
222
            if(m_emergencyStop != TriState::True)
×
223
            {
224
              m_emergencyStop = TriState::True;
×
225
              m_trackPowerOn = TriState::True;
×
226

227
              if(m_onTrackPowerChanged)
×
228
                m_onTrackPowerChanged(true, true);
×
229
            }
230
          });
×
231
      }
232
      break;
×
233
    }
234
  }
235
}
×
236

237
void Kernel::resumeOperations()
×
238
{
239
  assert(isEventLoopThread());
×
240

241
  if(m_trackPowerOn != TriState::True || m_emergencyStop != TriState::False)
×
242
  {
NEW
243
    boost::asio::post(m_ioContext, 
×
244
      [this]()
×
245
      {
246
        send(ResumeOperationsRequest());
×
247
      });
×
248
  }
249
}
×
250

251
void Kernel::stopOperations()
×
252
{
253
  assert(isEventLoopThread());
×
254

255
  if(m_trackPowerOn != TriState::False || m_emergencyStop != TriState::False)
×
256
  {
NEW
257
    boost::asio::post(m_ioContext, 
×
258
      [this]()
×
259
      {
260
        send(StopOperationsRequest());
×
261
      });
×
262
  }
263
}
×
264

265
void Kernel::stopAllLocomotives()
×
266
{
267
  assert(isEventLoopThread());
×
268

269
  if(m_trackPowerOn != TriState::True || m_emergencyStop != TriState::True)
×
270
  {
NEW
271
    boost::asio::post(m_ioContext, 
×
272
      [this]()
×
273
      {
274
        send(StopAllLocomotivesRequest());
×
275
      });
×
276
  }
277
}
×
278

279
void Kernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
×
280
{
281
  if(m_config.useEmergencyStopLocomotiveCommand && changes == DecoderChangeFlags::EmergencyStop && decoder.emergencyStop)
×
282
  {
283
    postSend(EmergencyStopLocomotive(decoder.address));
×
284
  }
285
  else if(has(changes, DecoderChangeFlags::EmergencyStop | DecoderChangeFlags::Direction | DecoderChangeFlags::Throttle | DecoderChangeFlags::SpeedSteps))
×
286
  {
287
    switch(decoder.speedSteps)
×
288
    {
289
      case 14:
×
290
        postSend(SpeedAndDirectionInstruction14(
×
291
          decoder.address,
292
          decoder.emergencyStop,
293
          decoder.direction,
294
          Decoder::throttleToSpeedStep<uint8_t>(decoder.throttle, 14),
×
295
          decoder.getFunctionValue(0)));
×
296
        break;
×
297

298
      case 27:
×
299
        postSend(SpeedAndDirectionInstruction27(
×
300
          decoder.address,
301
          decoder.emergencyStop,
302
          decoder.direction,
303
          Decoder::throttleToSpeedStep<uint8_t>(decoder.throttle, 27)));
×
304
        break;
×
305

306
      case 28:
×
307
        postSend(SpeedAndDirectionInstruction28(
×
308
          decoder.address,
309
          decoder.emergencyStop,
310
          decoder.direction,
311
          Decoder::throttleToSpeedStep<uint8_t>(decoder.throttle, 28)));
×
312
        break;
×
313

314
      case 128:
×
315
        postSend(SpeedAndDirectionInstruction128(
×
316
          decoder.address,
317
          decoder.emergencyStop,
318
          decoder.direction,
319
          Decoder::throttleToSpeedStep<uint8_t>(decoder.throttle, 126)));
×
320
        break;
×
321

322
      default:
×
323
        assert(false);
×
324
        break;
325
    }
326
  }
327
  else if(has(changes, DecoderChangeFlags::FunctionValue))
×
328
  {
329
    if(functionNumber <= 4)
×
330
    {
331
      postSend(FunctionInstructionGroup1(
×
332
        decoder.address,
333
        decoder.getFunctionValue(0),
×
334
        decoder.getFunctionValue(1),
×
335
        decoder.getFunctionValue(2),
×
336
        decoder.getFunctionValue(3),
×
337
        decoder.getFunctionValue(4)));
×
338
    }
339
    else if(functionNumber <= 8)
×
340
    {
341
      postSend(FunctionInstructionGroup2(
×
342
        decoder.address,
343
        decoder.getFunctionValue(5),
×
344
        decoder.getFunctionValue(6),
×
345
        decoder.getFunctionValue(7),
×
346
        decoder.getFunctionValue(8)));
×
347
    }
348
    else if(functionNumber <= 12)
×
349
    {
350
      postSend(FunctionInstructionGroup3(
×
351
        decoder.address,
352
        decoder.getFunctionValue(9),
×
353
        decoder.getFunctionValue(10),
×
354
        decoder.getFunctionValue(11),
×
355
        decoder.getFunctionValue(12)));
×
356
    }
357
    else if(functionNumber <= 20)
×
358
    {
359
      if(m_config.useRocoF13F20Command)
×
360
      {
361
        postSend(RocoMultiMAUS::FunctionInstructionF13F20(
×
362
          decoder.address,
363
          decoder.getFunctionValue(13),
×
364
          decoder.getFunctionValue(14),
×
365
          decoder.getFunctionValue(15),
×
366
          decoder.getFunctionValue(16),
×
367
          decoder.getFunctionValue(17),
×
368
          decoder.getFunctionValue(18),
×
369
          decoder.getFunctionValue(19),
×
370
          decoder.getFunctionValue(20)));
×
371
      }
372
      else
373
      {
374
        postSend(FunctionInstructionGroup4(
×
375
          decoder.address,
376
          decoder.getFunctionValue(13),
×
377
          decoder.getFunctionValue(14),
×
378
          decoder.getFunctionValue(15),
×
379
          decoder.getFunctionValue(16),
×
380
          decoder.getFunctionValue(17),
×
381
          decoder.getFunctionValue(18),
×
382
          decoder.getFunctionValue(19),
×
383
          decoder.getFunctionValue(20)));
×
384
      }
385
    }
386
    else if(functionNumber <= 28)
×
387
    {
388
      postSend(FunctionInstructionGroup5(
×
389
        decoder.address,
390
        decoder.getFunctionValue(21),
×
391
        decoder.getFunctionValue(22),
×
392
        decoder.getFunctionValue(23),
×
393
        decoder.getFunctionValue(24),
×
394
        decoder.getFunctionValue(25),
×
395
        decoder.getFunctionValue(26),
×
396
        decoder.getFunctionValue(27),
×
397
        decoder.getFunctionValue(28)));
×
398
    }
399
  }
400
}
×
401

402
bool Kernel::setOutput(uint16_t address, OutputPairValue value)
×
403
{
404
  assert(isEventLoopThread());
×
405
  assert(address >= accessoryOutputAddressMin && address <= accessoryOutputAddressMax);
×
406
  assert(value == OutputPairValue::First || value == OutputPairValue::Second);
×
NEW
407
  boost::asio::post(m_ioContext, 
×
408
    [this, address, value]()
×
409
    {
410
      send(
×
411
        AccessoryDecoderOperationRequest(
×
412
          m_config.useRocoAccessoryAddressing ? address + 4 : address,
×
413
          value == OutputPairValue::Second,
414
          true));
415
    });
×
416
  return true;
×
417
}
418

419
void Kernel::simulateInputChange(uint16_t address, SimulateInputAction action)
×
420
{
421
  if(m_simulation)
×
NEW
422
    boost::asio::post(m_ioContext, 
×
423
      [this, address, action]()
×
424
      {
425
        if((action == SimulateInputAction::SetFalse && m_inputValues[address - 1] == TriState::False) ||
×
426
            (action == SimulateInputAction::SetTrue && m_inputValues[address - 1] == TriState::True))
×
427
          return; // no change
×
428

429
        const uint16_t groupAddress = (address - 1) >> 2;
×
430
        const auto index = static_cast<uint8_t>((address - 1) & 0x0003);
×
431

432
        std::byte message[sizeof(FeedbackBroadcast) + sizeof(FeedbackBroadcast::Pair) + 1];
433
        memset(message, 0, sizeof(message));
×
434
        auto* feedbackBroadcast = reinterpret_cast<FeedbackBroadcast*>(&message);
×
435
        feedbackBroadcast->header = idFeedbackBroadcast;
×
436
        feedbackBroadcast->setPairCount(1);
×
437
        auto& pair = feedbackBroadcast->pair(0);
×
438
        pair.setGroupAddress(groupAddress);
×
439
        pair.setType(FeedbackBroadcast::Pair::Type::FeedbackModule);
×
440
        for(uint8_t i = 0; i < 4; i++)
×
441
        {
442
          const uint16_t n = (groupAddress << 2) + i;
×
443
          if(i == index)
×
444
          {
445
            switch(action)
×
446
            {
447
              case SimulateInputAction::SetFalse:
×
448
                pair.setStatus(i, false);
×
449
                break;
×
450

451
              case SimulateInputAction::SetTrue:
×
452
                pair.setStatus(i, true);
×
453
                break;
×
454

455
              case SimulateInputAction::Toggle:
×
456
                pair.setStatus(i, m_inputValues[n] != TriState::True);
×
457
                break;
×
458
            }
459
          }
460
          else
461
            pair.setStatus(i, m_inputValues[n] == TriState::True);
×
462
        }
463
        updateChecksum(*feedbackBroadcast);
×
464

465
        receive(*feedbackBroadcast);
×
466
      });
467
}
×
468

469
void Kernel::setIOHandler(std::unique_ptr<IOHandler> handler)
×
470
{
471
  assert(handler);
×
472
  assert(!m_ioHandler);
×
473
  m_ioHandler = std::move(handler);
×
474
}
×
475

476
void Kernel::send(const Message& message)
×
477
{
478
  if(m_ioHandler->send(message))
×
479
  {
480
    if(m_config.debugLogRXTX)
×
481
      EventLoop::call(
×
482
        [this, msg=toString(message)]()
×
483
        {
484
          Log::log(logId, LogMessage::D2001_TX_X, msg);
×
485
        });
×
486
  }
487
  else
488
  {} // log message and go to error state
489
}
×
490

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