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

traintastic / traintastic / 23667775307

27 Mar 2026 09:13PM UTC coverage: 28.009% (-0.002%) from 28.011%
23667775307

push

github

reinder
[boost] fix timer cancel() error_code overload is removed in 1.87, see #220

0 of 5 new or added lines in 1 file covered. (0.0%)

220 existing lines in 10 files now uncovered.

8182 of 29212 relevant lines covered (28.01%)

194.75 hits per line

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

0.0
/server/src/hardware/protocol/marklincan/iohandler/simulationiohandler.cpp
1
/**
2
 * server/src/hardware/protocol/marklincan/iohandler/simulationiohandler.cpp
3
 *
4
 * This file is part of the traintastic source code.
5
 *
6
 * Copyright (C) 2023-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 "simulationiohandler.hpp"
24
#include <boost/asio/post.hpp>
25
#include "../kernel.hpp"
26
#include "../message/statusdataconfig.hpp"
27
#include "../../../../utils/random.hpp"
28
#include "../../../../utils/zlib.hpp"
29

30
namespace MarklinCAN {
31

UNCOV
32
SimulationIOHandler::SimulationIOHandler(Kernel& kernel)
×
33
  : IOHandler(kernel)
34
  , m_pingTimer{kernel.ioContext()}
×
35
  , m_bootloaderCANTimer{kernel.ioContext()}
×
UNCOV
36
  , m_delayedMessageTimer{kernel.ioContext()}
×
37
{
UNCOV
38
}
×
39

UNCOV
40
void SimulationIOHandler::start()
×
41
{
42
  using namespace std::chrono_literals;
43
  auto expireAfter = std::chrono::milliseconds(Random::value<int>(0, bootstrapCANInterval.count()));
×
44
  startBootloaderCANTimer(expireAfter);
×
45
  startPingTimer(expireAfter + 1s);
×
46
  m_kernel.started();
×
UNCOV
47
}
×
48

UNCOV
49
void SimulationIOHandler::startBootloaderCANTimer(std::chrono::milliseconds expireAfter)
×
50
{
51
  m_bootloaderCANTimer.expires_after(expireAfter);
×
52
  m_bootloaderCANTimer.async_wait(
×
UNCOV
53
    [this](const boost::system::error_code& ec)
×
54
    {
UNCOV
55
      if(!ec)
×
56
      {
UNCOV
57
        reply(BootloaderCAN(guiUID));
×
58
      }
59

UNCOV
60
      if(ec != boost::asio::error::operation_aborted)
×
61
      {
UNCOV
62
        startBootloaderCANTimer();
×
63
      }
64
    });
×
UNCOV
65
}
×
66

UNCOV
67
void SimulationIOHandler::startPingTimer(std::chrono::milliseconds expireAfter)
×
68
{
69
  m_pingTimer.expires_after(expireAfter);
×
70
  m_pingTimer.async_wait(
×
UNCOV
71
    [this](const boost::system::error_code& ec)
×
72
    {
UNCOV
73
      if(!ec)
×
74
      {
75
        reply(Ping(guiUID));
×
UNCOV
76
        replyPing();
×
77
      }
78

UNCOV
79
      if(ec != boost::asio::error::operation_aborted)
×
80
      {
UNCOV
81
        startPingTimer();
×
82
      }
83
    });
×
UNCOV
84
}
×
85

UNCOV
86
void SimulationIOHandler::stop()
×
87
{
88
  while(!m_delayedMessages.empty())
×
UNCOV
89
    m_delayedMessages.pop();
×
90

91
  m_bootloaderCANTimer.cancel();
×
92
  m_pingTimer.cancel();
×
93
  m_delayedMessageTimer.cancel();
×
UNCOV
94
}
×
95

UNCOV
96
bool SimulationIOHandler::send(const Message& message)
×
97
{
UNCOV
98
  switch(message.command())
×
99
  {
UNCOV
100
    case Command::System:
×
101
    {
UNCOV
102
      const auto& system = static_cast<const SystemMessage&>(message);
×
103

UNCOV
104
      switch(system.subCommand())
×
105
      {
UNCOV
106
        case SystemSubCommand::SystemStop:
×
107
        case SystemSubCommand::SystemGo:
108
        case SystemSubCommand::SystemHalt:
109
        case SystemSubCommand::LocomotiveEmergencyStop:
110
        case SystemSubCommand::LocomotiveCycleEnd:
111
          // not (yet) implemented
UNCOV
112
          break;
×
113

114
        case SystemSubCommand::AccessorySwitchTime:
×
UNCOV
115
          if(!message.isResponse() && message.dlc == 7)
×
116
          {
117
            auto response = static_cast<const AccessorySwitchTime&>(message);
×
118
            if(response.switchTime() == 0)
×
UNCOV
119
              m_switchTime = defaultSwitchTime;
×
120
            else
121
              m_switchTime = std::chrono::milliseconds(response.switchTime() * 10);
×
122
            response.setResponse(true);
×
UNCOV
123
            reply(response);
×
124
          }
UNCOV
125
          break;
×
126

UNCOV
127
        case SystemSubCommand::Overload:
×
128
        case SystemSubCommand::Status:
129
        case SystemSubCommand::ModelClock:
130
        case SystemSubCommand::MFXSeek:
131
          // not (yet) implemented
UNCOV
132
          break;
×
133
      }
UNCOV
134
      break;
×
135
    }
UNCOV
136
    case Command::Discovery:
×
137
    case Command::Bind:
138
    case Command::Verify:
139
    case Command::LocomotiveSpeed:
140
    case Command::LocomotiveDirection:
141
    case Command::LocomotiveFunction:
142
    case Command::ReadConfig:
143
    case Command::WriteConfig:
144
      // not (yet) implemented
UNCOV
145
      break;
×
146

147
    case Command::AccessoryControl:
×
UNCOV
148
      if(!message.isResponse() && (message.dlc == 6 || message.dlc == 8))
×
149
      {
150
        // confirm switch command:
151
        AccessoryControl response{static_cast<const AccessoryControl&>(message)};
×
152
        response.setResponse(true);
×
UNCOV
153
        reply(response);
×
154

155
        // handle switch time:
UNCOV
156
        if(response.current() != 0 && (response.isDefaultSwitchTime() || response.switchTime() > 0))
×
157
        {
UNCOV
158
          const auto switchTime = response.isDefaultSwitchTime() ? m_switchTime : std::chrono::milliseconds(response.switchTime() * 10);
×
159

160
          response.setResponse(false);
×
161
          response.setCurrent(0);
×
UNCOV
162
          reply(response, switchTime);
×
163

164
          response.setResponse(true);
×
UNCOV
165
          reply(response, switchTime + std::chrono::milliseconds(1));
×
166
        }
167
      }
UNCOV
168
      break;
×
169

UNCOV
170
    case Command::AccessoryConfig:
×
171
    case Command::S88Polling:
172
    case Command::FeedbackEvent:
173
    case Command::SX1Event:
174
      // not (yet) implemented
UNCOV
175
      break;
×
176

177
    case Command::Ping:
×
UNCOV
178
      if(message.dlc == 0 && !message.isResponse())
×
179
      {
180
        replyPing();
×
UNCOV
181
        reply(PingReply(guiUID, 4, 3, DeviceId::CS2GUI));
×
182
      }
UNCOV
183
      else if(message.dlc == 8 && message.isResponse())
×
184
      {
185
        const auto& pingReply = static_cast<const PingReply&>(message);
×
UNCOV
186
        if(m_knownPingUIDs.count(pingReply.uid()) == 0)
×
187
        {
188
          m_knownPingUIDs.emplace(pingReply.uid());
×
UNCOV
189
          reply(StatusDataConfig(guiUID, pingReply.uid(), 0));
×
190
        }
191
      }
UNCOV
192
      break;
×
193

UNCOV
194
    case Command::Update:
×
195
    case Command::ReadConfigData:
196
    case Command::BootloaderCAN:
197
    case Command::BootloaderTrack:
198
      // not (yet) implemented
UNCOV
199
      break;
×
200

201
    case Command::StatusDataConfig:
×
UNCOV
202
      if(message.dlc == 5 && !message.isResponse())
×
203
      {
204
        const uint32_t uid = static_cast<const UidMessage&>(message).uid();
×
205
        const uint8_t index = message.data[4];
×
UNCOV
206
        switch(index)
×
207
        {
UNCOV
208
          case 0x00:
×
209
          {
210
            StatusData::DeviceDescription desc;
×
UNCOV
211
            switch(uid)
×
212
            {
213
              case gfpUID:
×
214
                desc.numberOfReadings = 4;
×
215
                desc.numberOfConfigurationChannels = 2;
×
216
                desc.serialNumber = 12345;
×
217
                desc.articleNumber[0] = '6';
×
218
                desc.articleNumber[1] = '0';
×
219
                desc.articleNumber[2] = '2';
×
220
                desc.articleNumber[3] = '1';
×
221
                desc.articleNumber[4] = '4';
×
222
                desc.deviceName = "Central Station 2";
×
UNCOV
223
                break;
×
224

225
              case linkS88UID:
×
226
                desc.numberOfReadings = 0;
×
227
                desc.numberOfConfigurationChannels = 12;
×
228
                desc.serialNumber = 1234;
×
229
                desc.articleNumber[0] = '6';
×
230
                desc.articleNumber[1] = '0';
×
231
                desc.articleNumber[2] = '8';
×
232
                desc.articleNumber[3] = '8';
×
233
                desc.articleNumber[4] = '3';
×
234
                desc.deviceName = "Link S88";
×
UNCOV
235
                break;
×
236

UNCOV
237
              case guiUID: // CS2 GUI doesn't respond to this request
×
238
              default:
239
                // no response
UNCOV
240
                return true;
×
241
            }
242
            for(const auto& msg : statusDataConfigReply(uid, uid, index, desc))
×
243
              reply(msg);
×
244
            break;
×
UNCOV
245
          }
×
246
        }
247
      }
UNCOV
248
      break;
×
249

250
    case Command::ConfigData:
×
UNCOV
251
      if(message.dlc == 8 && !message.isResponse())
×
252
      {
UNCOV
253
        const auto& configData = static_cast<const ConfigData&>(message);
×
254

UNCOV
255
        if(configData.name() == ConfigDataName::loks)
×
256
        {
257
          static constexpr std::string_view emptyLoks = "[lokomotive]\nversion\n .minor=4\nsession\n .id=13749\n";
258

259
          // compress:
260
          std::vector<std::byte> data;
×
261
          data.resize(emptyLoks.size());
×
262
          if(!ZLib::compressString(emptyLoks, data))
×
263
            data.resize(data.size() * 2); // retry with larger buffer
×
264
          if(!ZLib::compressString(emptyLoks, data))
×
UNCOV
265
            break;
×
266

267
          // prepend uncompressed size (big endian):
268
          auto uncompressedSize = host_to_be<uint32_t>(emptyLoks.size());
×
269
          for(int i = sizeof(uncompressedSize) - 1; i >= 0; i--)
×
UNCOV
270
            data.insert(data.begin(), reinterpret_cast<const std::byte*>(&uncompressedSize)[i]);
×
271

272
          reply(ConfigData(guiUID, configData.name(), true));
×
UNCOV
273
          reply(ConfigDataStream(guiUID, data.size(), crc16(data)));
×
274

275
          const std::byte* end = data.data() + data.size();
×
UNCOV
276
          for(std::byte* p = data.data(); p < end; p += 8)
×
277
          {
UNCOV
278
            reply(ConfigDataStream(guiUID, p, std::min<size_t>(end - p, 8)));
×
279
          }
UNCOV
280
        }
×
281
      }
UNCOV
282
      break;
×
283

UNCOV
284
    case Command::ConfigDataStream:
×
285
      // not (yet) implemented
UNCOV
286
      break;
×
287
  }
UNCOV
288
  return true;
×
289
}
290

UNCOV
291
void SimulationIOHandler::reply(const Message& message)
×
292
{
293
  // post the reply, so it has some delay
294
  boost::asio::post(m_kernel.ioContext(), 
×
UNCOV
295
    [this, message]()
×
296
    {
297
      m_kernel.receive(message);
×
298
    });
×
UNCOV
299
}
×
300

UNCOV
301
void SimulationIOHandler::reply(const Message& message, std::chrono::milliseconds delay)
×
302
{
303
  const auto time = std::chrono::steady_clock::now() + delay;
×
304
  const bool restartTimer = m_delayedMessages.empty() || m_delayedMessages.top().time > time;
×
305
  m_delayedMessages.push({time, message});
×
306
  if(restartTimer)
×
307
    restartDelayedMessageTimer();
×
UNCOV
308
}
×
309

UNCOV
310
void SimulationIOHandler::replyPing()
×
311
{
312
  reply(PingReply(gfpUID, 3, 85, DeviceId::GleisFormatProzessorOrBooster));
×
313
  reply(PingReply(linkS88UID, 1, 1, static_cast<DeviceId>(64)));
×
UNCOV
314
}
×
315

UNCOV
316
void SimulationIOHandler::restartDelayedMessageTimer()
×
317
{
318
  assert(!m_delayedMessages.empty());
×
319
  m_delayedMessageTimer.cancel();
×
320
  m_delayedMessageTimer.expires_after(m_delayedMessages.top().time - std::chrono::steady_clock::now());
×
321
  m_delayedMessageTimer.async_wait(
×
UNCOV
322
    [this](const boost::system::error_code& ec)
×
323
    {
UNCOV
324
      if(!ec)
×
325
      {
UNCOV
326
        while(!m_delayedMessages.empty() && m_delayedMessages.top().time <= std::chrono::steady_clock::now())
×
327
        {
328
          reply(m_delayedMessages.top().message);
×
UNCOV
329
          m_delayedMessages.pop();
×
330
        }
331
      }
332

333
      if(ec != boost::asio::error::operation_aborted && !m_delayedMessages.empty())
×
334
        restartDelayedMessageTimer();
×
335
    });
×
UNCOV
336
}
×
337

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