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

traintastic / traintastic / 22823086659

08 Mar 2026 02:30PM UTC coverage: 26.831% (+0.06%) from 26.774%
22823086659

push

github

reinder
[cbus] renamed CBUSAccessory(Short) to Long/Short event and added node setting for Long events.

0 of 15 new or added lines in 2 files covered. (0.0%)

1909 existing lines in 38 files now uncovered.

8230 of 30674 relevant lines covered (26.83%)

186.8 hits per line

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

0.0
/server/src/hardware/protocol/dccex/kernel.cpp
1
/**
2
 * This file is part of Traintastic,
3
 * see <https://github.com/traintastic/traintastic>.
4
 *
5
 * Copyright (C) 2021-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 "kernel.hpp"
23
#include "messages.hpp"
24
#include "../../decoder/decoder.hpp"
25
#include "../../decoder/decoderchangeflags.hpp"
26
#include "../../input/inputcontroller.hpp"
27
#include "../../output/outputcontroller.hpp"
28
#include "../../protocol/dcc/dcc.hpp"
29
#include "../../protocol/dcc/messages.hpp"
30
#include "../../../utils/setthreadname.hpp"
31
#include "../../../utils/rtrim.hpp"
32
#include "../../../core/eventloop.hpp"
33
#include "../../../log/log.hpp"
34
#include "../../../log/logmessageexception.hpp"
35
#include "../../../utils/inrange.hpp"
36
#include "../../../utils/fromchars.hpp"
37
#include "../../../utils/displayname.hpp"
38

39
namespace DCCEX {
40

UNCOV
41
Kernel::Kernel(std::string logId_, const Config& config, bool simulation)
×
42
  : KernelBase(std::move(logId_))
×
43
  , m_simulation{simulation}
×
44
  , m_startupDelayTimer{m_ioContext}
×
45
  , m_decoderController{nullptr}
×
46
  , m_inputController{nullptr}
×
47
  , m_outputController{nullptr}
×
48
  , m_config{config}
×
49
{
UNCOV
50
}
×
51

UNCOV
52
void Kernel::setConfig(const Config& config)
×
53
{
UNCOV
54
  m_ioContext.post(
×
55
    [this, newConfig=config]()
×
56
    {
UNCOV
57
      if(newConfig.speedSteps != m_config.speedSteps)
×
58
        send(Messages::setSpeedSteps(newConfig.speedSteps));
×
59

UNCOV
60
      m_config = newConfig;
×
61
    });
×
62
}
×
63

UNCOV
64
void Kernel::start()
×
65
{
UNCOV
66
  assert(m_ioHandler);
×
67
  assert(!m_started);
×
68

69
  // reset all state values
UNCOV
70
  m_powerOn = TriState::Undefined;
×
71
  m_emergencyStop = TriState::Undefined;
×
72
  m_inputValues.clear();
×
73

UNCOV
74
  m_thread = std::thread(
×
75
    [this]()
×
76
    {
UNCOV
77
      setThreadName("dcc-ex");
×
78
      auto work = std::make_shared<boost::asio::io_context::work>(m_ioContext);
×
79
      m_ioContext.run();
×
80
    });
×
81

UNCOV
82
  m_ioContext.post(
×
83
    [this]()
×
84
    {
85
      try
86
      {
UNCOV
87
        m_ioHandler->start();
×
88
      }
UNCOV
89
      catch(const LogMessageException& e)
×
90
      {
UNCOV
91
        EventLoop::call(
×
92
          [this, e]()
×
93
          {
UNCOV
94
            Log::log(logId, e.message(), e.args());
×
95
            error();
×
96
          });
×
97
        return;
×
98
      }
×
99
    });
100

101
#ifndef NDEBUG
UNCOV
102
  m_started = true;
×
103
#endif
UNCOV
104
}
×
105

UNCOV
106
void Kernel::stop()
×
107
{
UNCOV
108
  m_ioContext.post(
×
109
    [this]()
×
110
    {
UNCOV
111
      m_startupDelayTimer.cancel();
×
112

UNCOV
113
      m_ioHandler->stop();
×
114
    });
×
115

UNCOV
116
  m_ioContext.stop();
×
117

UNCOV
118
  m_thread.join();
×
119

120
#ifndef NDEBUG
UNCOV
121
  m_started = false;
×
122
#endif
UNCOV
123
}
×
124

UNCOV
125
void Kernel::started()
×
126
{
UNCOV
127
  assert(isKernelThread());
×
128

UNCOV
129
  m_startupDelayTimer.expires_after(boost::asio::chrono::milliseconds(m_config.startupDelay));
×
130
  m_startupDelayTimer.async_wait(std::bind(&Kernel::startupDelayExpired, this, std::placeholders::_1));
×
131
}
×
132

UNCOV
133
void Kernel::receive(std::string_view message)
×
134
{
UNCOV
135
  if(m_config.debugLogRXTX)
×
136
    EventLoop::call(
×
137
      [this, msg=std::string(rtrim(message, '\n'))]()
×
138
      {
UNCOV
139
        Log::log(logId, LogMessage::D2002_RX_X, msg);
×
140
      });
×
141

UNCOV
142
  if(message.size() > 1 && message[0] == '<')
×
143
  {
UNCOV
144
    switch(message[1])
×
145
    {
UNCOV
146
      case 'H': // Turnout response
×
147
      {
148
        uint16_t id;
UNCOV
149
        if(auto r = fromChars(message.substr(3), id); r.ec == std::errc())
×
150
        {
UNCOV
151
          const char state = *(r.ptr + 1);
×
152
          TriState value = TriState::Undefined;
×
153

UNCOV
154
          if(state == '0' || state == 'C')
×
155
            value = TriState::False;
×
156
          else if(state == '1' || state == 'T')
×
157
            value = TriState::True;
×
158

UNCOV
159
          if(value != TriState::Undefined)
×
160
          {
UNCOV
161
            EventLoop::call(
×
162
              [this, id, value]()
×
163
              {
UNCOV
164
                m_outputController->updateOutputValue(OutputChannel::Turnout, OutputAddress(id), value);
×
165
              });
×
166
          }
167
        }
UNCOV
168
        break;
×
169
      }
UNCOV
170
      case 'p': // Power on/off response
×
171
        if(message[2] == '0')
×
172
        {
UNCOV
173
          if(m_powerOn != TriState::False)
×
174
          {
UNCOV
175
            m_powerOn = TriState::False;
×
176

UNCOV
177
            if(m_onPowerOnChanged)
×
178
              EventLoop::call(
×
179
                [this]()
×
180
                {
UNCOV
181
                  m_onPowerOnChanged(false);
×
182
                });
×
183
          }
184
        }
UNCOV
185
        else if(message[2] == '1')
×
186
        {
UNCOV
187
          if(m_powerOn != TriState::True)
×
188
          {
UNCOV
189
            m_powerOn = TriState::True;
×
190

UNCOV
191
            if(m_onPowerOnChanged)
×
192
              EventLoop::call(
×
193
                [this]()
×
194
                {
UNCOV
195
                  m_onPowerOnChanged(true);
×
196
                });
×
197
          }
198
        }
UNCOV
199
        break;
×
200

UNCOV
201
      case 'q': // Sensor/Input: ACTIVE to INACTIVE
×
202
      case 'Q': // Sensor/Input: INACTIVE to ACTIVE
UNCOV
203
        if(m_inputController && message[2] == ' ')
×
204
        {
205
          uint32_t id;
UNCOV
206
          if(auto r = fromChars(message.substr(3), id); r.ec == std::errc() && *r.ptr == '>' && id <= idMax)
×
207
          {
UNCOV
208
            const bool value = message[1] == 'Q';
×
209
            auto it = m_inputValues.find(id);
×
210
            if(it == m_inputValues.end() || it->second != value)
×
211
            {
UNCOV
212
              m_inputValues[id] = value;
×
213

UNCOV
214
              EventLoop::call(
×
215
                [this, id, value]()
×
216
                {
UNCOV
217
                  m_inputController->updateInputValue(InputChannel::Input, id, toTriState(value));
×
218
                });
×
219
            }
220
          }
221
        }
UNCOV
222
        break;
×
223

UNCOV
224
      case 'Y': // Output response
×
225
      {
226
        uint16_t id;
UNCOV
227
        if(auto r = fromChars(message.substr(3), id); r.ec == std::errc())
×
228
        {
UNCOV
229
          const char state = *(r.ptr + 1);
×
230
          TriState value = TriState::Undefined;
×
231

UNCOV
232
          if(state == '0')
×
233
            value = TriState::False;
×
234
          else if(state == '1')
×
235
            value = TriState::True;
×
236

UNCOV
237
          if(value != TriState::Undefined)
×
238
          {
UNCOV
239
            EventLoop::call(
×
240
              [this, id, value]()
×
241
              {
UNCOV
242
                m_outputController->updateOutputValue(OutputChannel::Output, OutputAddress(id), value);
×
243
              });
×
244
          }
245
        }
UNCOV
246
        break;
×
247
      }
248
    }
249
  }
UNCOV
250
}
×
251

UNCOV
252
void Kernel::powerOn()
×
253
{
UNCOV
254
  m_ioContext.post(
×
255
    [this]()
×
256
    {
UNCOV
257
      if(m_powerOn != TriState::True)
×
258
      {
UNCOV
259
        send(Messages::powerOn());
×
260
      }
UNCOV
261
    });
×
262
}
×
263

UNCOV
264
void Kernel::powerOff()
×
265
{
UNCOV
266
  m_ioContext.post(
×
267
    [this]()
×
268
    {
UNCOV
269
      if(m_powerOn != TriState::False)
×
270
      {
UNCOV
271
        send(Messages::powerOff());
×
272
      }
UNCOV
273
    });
×
274
}
×
275

UNCOV
276
void Kernel::emergencyStop()
×
277
{
UNCOV
278
  m_ioContext.post(
×
279
    [this]()
×
280
    {
UNCOV
281
      if(m_emergencyStop != TriState::True)
×
282
      {
UNCOV
283
        m_emergencyStop = TriState::True;
×
284
        send(Messages::emergencyStop());
×
285
      }
UNCOV
286
    });
×
287
}
×
288

UNCOV
289
void Kernel::clearEmergencyStop()
×
290
{
UNCOV
291
  m_ioContext.post(
×
292
    [this]()
×
293
    {
UNCOV
294
      m_emergencyStop = TriState::False;
×
295
    });
×
296
}
×
297

UNCOV
298
void Kernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber)
×
299
{
UNCOV
300
  if(has(changes, DecoderChangeFlags::EmergencyStop | DecoderChangeFlags::Throttle | DecoderChangeFlags::Direction))
×
301
  {
UNCOV
302
    const uint8_t speed = Decoder::throttleToSpeedStep<uint8_t>(decoder.throttle, 126);
×
303
    m_ioContext.post(
×
304
      [this, address=decoder.address.value(), emergencyStop=decoder.emergencyStop.value(), speed, direction=decoder.direction.value()]()
×
305
      {
UNCOV
306
        send(Messages::setLocoSpeedAndDirection(address, speed, emergencyStop | (m_emergencyStop != TriState::False), direction));
×
307
      });
×
308
  }
UNCOV
309
  else if(has(changes, DecoderChangeFlags::FunctionValue) && functionNumber <= Config::functionNumberMax)
×
310
  {
UNCOV
311
    postSend(Messages::setLocoFunction(decoder.address, static_cast<uint8_t>(functionNumber), decoder.getFunctionValue(functionNumber)));
×
312
  }
UNCOV
313
}
×
314

UNCOV
315
bool Kernel::setOutput(OutputChannel channel, uint16_t address, OutputValue value)
×
316
{
UNCOV
317
  switch(channel)
×
318
  {
UNCOV
319
    case OutputChannel::Accessory:
×
320
      assert(inRange<uint32_t>(address, DCC::Accessory::addressMin, DCC::Accessory::addressMax));
×
321
      assert(std::get<OutputPairValue>(value) != OutputPairValue::Undefined);
×
322
      m_ioContext.post(
×
323
        [this, address, value]()
×
324
        {
UNCOV
325
          send(Messages::setAccessory(address, std::get<OutputPairValue>(value) == OutputPairValue::Second));
×
326

327
          // no response for accessory command, assume it succeeds:
UNCOV
328
          EventLoop::call(
×
329
            [this, address, value]()
×
330
            {
UNCOV
331
              m_outputController->updateOutputValue(OutputChannel::Accessory, OutputAddress(address), value);
×
332
            });
×
333
        });
×
334
      return true;
×
335

UNCOV
336
    case OutputChannel::DCCext:
×
337
      assert(inRange(address, DCC::Accessory::addressMin, DCC::Accessory::addressMax));
×
338
      if(inRange<int16_t>(std::get<int16_t>(value), std::numeric_limits<uint8_t>::min(), std::numeric_limits<uint8_t>::max())) /*[[likely]]*/
×
339
      {
UNCOV
340
        m_ioContext.post(
×
341
          [this, address, data=static_cast<uint8_t>(std::get<int16_t>(value))]()
×
342
          {
UNCOV
343
            send(Messages::dccPacket(DCC::SetAdvancedAccessoryValue(address, data)));
×
344
          });
×
345
        return true;
×
346
      }
UNCOV
347
      return false;
×
348

UNCOV
349
    case OutputChannel::Turnout:
×
350
      assert(inRange<uint32_t>(address, idMin, idMax));
×
351
      assert(std::get<TriState>(value) != TriState::Undefined);
×
352
      m_ioContext.post(
×
353
        [this, address, value]()
×
354
        {
UNCOV
355
          send(Messages::setTurnout(address, std::get<TriState>(value) == TriState::True));
×
356
        });
×
357
      return true;
×
358

UNCOV
359
    case OutputChannel::Output:
×
360
      assert(inRange<uint32_t>(address, idMin, idMax));
×
361
      assert(std::get<TriState>(value) != TriState::Undefined);
×
362
      m_ioContext.post(
×
363
        [this, address, value]()
×
364
        {
UNCOV
365
          send(Messages::setOutput(address, std::get<TriState>(value) == TriState::True));
×
366
        });
×
367
      return true;
×
368

UNCOV
369
    default:
×
370
      break;
×
371
  }
372

UNCOV
373
  assert(false);
×
374
  return false;
375
}
376

UNCOV
377
void Kernel::simulateInputChange(uint16_t address, SimulateInputAction action)
×
378
{
UNCOV
379
  if(m_simulation)
×
380
    m_ioContext.post(
×
381
      [this, address, action]()
×
382
      {
383
        bool value;
UNCOV
384
        auto it = m_inputValues.find(address);
×
385
        switch(action)
×
386
        {
UNCOV
387
          case SimulateInputAction::SetFalse:
×
388
            if(it != m_inputValues.end() && !it->second)
×
389
              return; // no change
×
390
            value = false;
×
391
            break;
×
392

UNCOV
393
          case SimulateInputAction::SetTrue:
×
394
            if(it != m_inputValues.end() && it->second)
×
395
              return; // no change
×
396
            value = true;
×
397
            break;
×
398

UNCOV
399
          case SimulateInputAction::Toggle:
×
400
            value = it != m_inputValues.end() ? !it->second : true;
×
401
            break;
×
402

UNCOV
403
          default:
×
404
            assert(false);
×
405
            return;
406
        }
UNCOV
407
        receive(Messages::sensorTransition(address, value));
×
408
      });
UNCOV
409
}
×
410

UNCOV
411
void Kernel::setIOHandler(std::unique_ptr<IOHandler> handler)
×
412
{
UNCOV
413
  assert(handler);
×
414
  assert(!m_ioHandler);
×
415
  m_ioHandler = std::move(handler);
×
416
}
×
417

UNCOV
418
void Kernel::send(std::string_view message)
×
419
{
UNCOV
420
  if(m_ioHandler->send(message))
×
421
  {
UNCOV
422
    if(m_config.debugLogRXTX)
×
423
      EventLoop::call(
×
424
        [this, msg=std::string(rtrim(message, '\n'))]()
×
425
        {
UNCOV
426
          Log::log(logId, LogMessage::D2001_TX_X, msg);
×
427
        });
×
428
  }
429
  else
430
  {} // log message and go to error state
UNCOV
431
}
×
432

UNCOV
433
void Kernel::startupDelayExpired(const boost::system::error_code& ec)
×
434
{
UNCOV
435
  assert(isKernelThread());
×
436

UNCOV
437
  if(ec)
×
438
    return;
×
439

UNCOV
440
  KernelBase::started();
×
441
}
442

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