• 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/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

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
{
50
}
×
51

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

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

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

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

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

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

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

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

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

116
  m_ioContext.stop();
×
117

118
  m_thread.join();
×
119

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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
      {
NEW
340
        boost::asio::post(m_ioContext, 
×
341
          [this, address, data=static_cast<uint8_t>(std::get<int16_t>(value))]()
×
342
          {
343
            send(Messages::dccPacket(DCC::SetAdvancedAccessoryValue(address, data)));
×
344
          });
×
345
        return true;
×
346
      }
347
      return false;
×
348

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

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

369
    default:
×
370
      break;
×
371
  }
372

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

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

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

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

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

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

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

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

437
  if(ec)
×
438
    return;
×
439

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