• 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/z21/serverkernel.cpp
1
/**
2
 * server/src/hardware/protocol/z21/serverkernel.cpp
3
 *
4
 * This file is part of the traintastic source code.
5
 *
6
 * Copyright (C) 2019-2023 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 "serverkernel.hpp"
24
#include "messages.hpp"
25
#include "../xpressnet/messages.hpp"
26
#include "../../decoder/list/decoderlist.hpp"
27
#include "../../protocol/dcc/dcc.hpp"
28
#include "../../../core/eventloop.hpp"
29
#include "../../../log/log.hpp"
30

31
namespace Z21 {
32

33
ServerKernel::ServerKernel(std::string logId_, const ServerConfig& config, std::shared_ptr<DecoderList> decoderList)
×
34
  : Kernel(std::move(logId_))
×
35
  , m_inactiveClientPurgeTimer{m_ioContext}
×
36
  , m_config{config}
×
37
  , m_decoderList{std::move(decoderList)}
×
38
{
39
}
×
40

41
void ServerKernel::setConfig(const ServerConfig& config)
×
42
{
NEW
43
  boost::asio::post(m_ioContext, 
×
44
    [this, newConfig=config]()
×
45
    {
46
      m_config = newConfig;
×
47
    });
×
48
}
×
49

50
void ServerKernel::setState(bool trackPowerOn, bool emergencyStop)
×
51
{
NEW
52
  boost::asio::post(m_ioContext, 
×
53
    [this, trackPowerOn, emergencyStop]()
×
54
    {
55
      const auto trackPowerOnTri = toTriState(trackPowerOn);
×
56
      const auto emergencyStopTri = toTriState(emergencyStop);
×
57

58
      const bool trackPowerOnChanged = m_trackPowerOn != trackPowerOnTri;
×
59
      const bool emergencyStopChanged = m_emergencyStop != emergencyStopTri;
×
60

61
      m_trackPowerOn = trackPowerOnTri;
×
62
      m_emergencyStop = emergencyStopTri;
×
63

64
      if(emergencyStopChanged && m_emergencyStop == TriState::True)
×
65
        sendTo(LanXBCStopped(), BroadcastFlags::PowerLocoTurnoutChanges);
×
66

67
      if(trackPowerOnChanged && m_trackPowerOn == TriState::False)
×
68
        sendTo(LanXBCTrackPowerOff(), BroadcastFlags::PowerLocoTurnoutChanges);
×
69

70
      if((trackPowerOnChanged || emergencyStopChanged) && m_trackPowerOn == TriState::True && m_emergencyStop == TriState::False)
×
71
        sendTo(LanXBCTrackPowerOn(), BroadcastFlags::PowerLocoTurnoutChanges);
×
72

73
      if(trackPowerOnChanged || emergencyStopChanged)
×
74
        sendTo(getLanSystemStateDataChanged(), BroadcastFlags::PowerLocoTurnoutChanges);
×
75
    });
×
76
}
×
77

78
void ServerKernel::receiveFrom(const Message& message, IOHandler::ClientId clientId)
×
79
{
80
  if(m_config.debugLogRXTX)
×
81
    EventLoop::call(
×
82
      [this, clientId, msg=toString(message)]()
×
83
      {
84
        Log::log(logId, LogMessage::D2005_X_RX_X, clientId, msg);
×
85
      });
×
86

87
  m_clients[clientId].lastSeen = std::chrono::steady_clock::now();
×
88

89
  switch(message.header())
×
90
  {
91
    case LAN_X:
×
92
    {
93
      const auto& lanX = static_cast<const LanX&>(message);
×
94

95
      if(!XpressNet::isChecksumValid(*reinterpret_cast<const XpressNet::Message*>(&lanX.xheader)))
×
96
        break;
×
97

98
      switch(lanX.xheader)
×
99
      {
100
        case 0x21:
×
101
          if(message == LanXGetVersion())
×
102
          {
103
            sendTo(LanXGetVersionReply(ServerConfig::xBusVersion, ServerConfig::commandStationId), clientId);
×
104
          }
105
          else if(message == LanXGetStatus())
×
106
          {
107
            LanXStatusChanged response;
×
108
            if(m_emergencyStop != TriState::False)
×
109
              response.db1 |= Z21_CENTRALSTATE_EMERGENCYSTOP;
×
110
            if(m_trackPowerOn != TriState::True)
×
111
              response.db1 |= Z21_CENTRALSTATE_TRACKVOLTAGEOFF;
×
112
            response.updateChecksum();
×
113
            sendTo(response, clientId);
×
114
          }
115
          else if(message == LanXSetTrackPowerOn())
×
116
          {
117
            if(m_config.allowTrackPowerOnReleaseEmergencyStop && (m_trackPowerOn != TriState::True || m_emergencyStop != TriState::False) && m_onTrackPowerOn)
×
118
            {
119
              EventLoop::call(
×
120
                [this]()
×
121
                {
122
                  m_onTrackPowerOn();
×
123
                });
×
124
            }
125
          }
126
          else if(message == LanXSetTrackPowerOff())
×
127
          {
128
            if(m_config.allowTrackPowerOff && m_trackPowerOn != TriState::False && m_onTrackPowerOff)
×
129
            {
130
              EventLoop::call(
×
131
                [this]()
×
132
                {
133
                  m_onTrackPowerOff();
×
134
                });
×
135
            }
136
          }
137
          break;
×
138

139
        case LAN_X_SET_STOP:
×
140
          if(message == LanXSetStop())
×
141
          {
142
            if(m_config.allowEmergencyStop && m_emergencyStop != TriState::True && m_onEmergencyStop)
×
143
            {
144
              EventLoop::call(
×
145
                [this]()
×
146
                {
147
                  m_onEmergencyStop();
×
148
                });
×
149
            }
150
          }
151
          break;
×
152

153
        case LAN_X_GET_LOCO_INFO:
×
154
          if(const auto& getLocoInfo = static_cast<const LanXGetLocoInfo&>(message);
×
155
              getLocoInfo.db0 == 0xF0)
×
156
          {
157
            subscribe(clientId, getLocoInfo.address(), getLocoInfo.isLongAddress());
×
158

159
            EventLoop::call(
×
160
              [this, getLocoInfo, clientId]()
×
161
              {
162
                if(auto decoder = getDecoder(getLocoInfo.address(), getLocoInfo.isLongAddress()))
×
163
                  postSendTo(LanXLocoInfo(*decoder), clientId);
×
164
              });
×
165
          }
166
          break;
×
167

168
        case LAN_X_SET_LOCO:
×
169
          if(const auto& setLocoDrive = static_cast<const LanXSetLocoDrive&>(message);
×
170
              setLocoDrive.db0 >= 0x10 && setLocoDrive.db0 <= 0x13)
×
171
          {
172
            subscribe(clientId, setLocoDrive.address(), setLocoDrive.isLongAddress());
×
173

174
            EventLoop::call(
×
175
              [this, setLocoDrive]()
×
176
              {
177
                if(auto decoder = getDecoder(setLocoDrive.address(), setLocoDrive.isLongAddress()))
×
178
                {
179
                  decoder->direction = setLocoDrive.direction();
×
180
                  decoder->emergencyStop = setLocoDrive.isEmergencyStop();
×
181
                  decoder->throttle = Decoder::speedStepToThrottle(setLocoDrive.speedStep(), setLocoDrive.speedSteps());
×
182
                }
×
183
                //else
184
                //  Log::log(*this, LogMessage::I2001_UNKNOWN_LOCO_ADDRESS_X, setLocoDrive.address());
185
              });
×
186
          }
×
187
          else if(const auto& setLocoFunction = static_cast<const LanXSetLocoFunction&>(message);
×
188
                  setLocoFunction.db0 == 0xF8 &&
×
189
                  setLocoFunction.switchType() != LanXSetLocoFunction::SwitchType::Invalid)
×
190
          {
191
            subscribe(clientId, setLocoFunction.address(), setLocoFunction.isLongAddress());
×
192

193
            EventLoop::call(
×
194
              [this, setLocoFunction]()
×
195
              {
196
                if(auto decoder = getDecoder(setLocoFunction.address(), setLocoFunction.isLongAddress()))
×
197
                {
198
                  if(auto function = decoder->getFunction(setLocoFunction.functionIndex()))
×
199
                  {
200
                    switch(setLocoFunction.switchType())
×
201
                    {
202
                      case LanXSetLocoFunction::SwitchType::Off:
×
203
                        function->value = false;
×
204
                        break;
×
205

206
                      case LanXSetLocoFunction::SwitchType::On:
×
207
                        function->value = true;
×
208
                        break;
×
209

210
                      case LanXSetLocoFunction::SwitchType::Toggle:
×
211
                        function->value = !function->value;
×
212
                        break;
×
213

214
                      case LanXSetLocoFunction::SwitchType::Invalid:
×
215
                        assert(false);
×
216
                        break;
217
                    }
218
                  }
×
219
                }
×
220
              });
×
221
          }
222
          break;
×
223

224
        case LAN_X_GET_FIRMWARE_VERSION:
×
225
          if(message == LanXGetFirmwareVersion())
×
226
            sendTo(LanXGetFirmwareVersionReply(ServerConfig::firmwareVersionMajor, ServerConfig::firmwareVersionMinor), clientId);
×
227
          break;
×
228
      }
229
      break;
×
230
    }
231
    case LAN_GET_LOCO_MODE:
×
232
      if(message.dataLen() == sizeof(LanGetLocoMode))
×
233
        sendTo(LanGetLocoModeReply(static_cast<const LanGetLocoMode&>(message).address(), LocoMode::DCC), clientId);
×
234
      break;
×
235

236
    case LAN_SET_LOCO_MODE:
×
237
      // ignore, we always report DCC
238
      break;
×
239

240
    case LAN_GET_SERIAL_NUMBER:
×
241
      if(message.dataLen() == sizeof(LanGetSerialNumber))
×
242
        sendTo(LanGetSerialNumberReply(ServerConfig::serialNumber), clientId);
×
243
      break;
×
244

245
    case LAN_GET_HWINFO:
×
246
      if(message.dataLen() == sizeof(LanGetHardwareInfo))
×
247
        sendTo(LanGetHardwareInfoReply(ServerConfig::hardwareType, ServerConfig::firmwareVersionMajor, ServerConfig::firmwareVersionMinor), clientId);
×
248
      break;
×
249

250
    case LAN_GET_BROADCASTFLAGS:
×
251
      if(message == LanGetBroadcastFlags())
×
252
        sendTo(LanGetBroadcastFlagsReply(m_clients[clientId].broadcastFlags), clientId);
×
253
      break;
×
254

255
    case LAN_SET_BROADCASTFLAGS:
×
256
      if(message.dataLen() == sizeof(LanSetBroadcastFlags))
×
257
        m_clients[clientId].broadcastFlags = static_cast<const LanSetBroadcastFlags&>(message).broadcastFlags();
×
258
      break;
×
259

260
    case LAN_SYSTEMSTATE_GETDATA:
×
261
      if(message == LanSystemStateGetData())
×
262
        sendTo(getLanSystemStateDataChanged(), clientId);
×
263
      break;
×
264

265
    case LAN_LOGOFF:
×
266
      if(message == LanLogoff())
×
267
        m_clients.erase(clientId);
×
268
      break;
×
269

270
    case LAN_GET_CODE:
×
271
    case LAN_GET_TURNOUTMODE:
272
    case LAN_SET_TURNOUTMODE:
273
    case LAN_RMBUS_DATACHANGED:
274
    case LAN_RMBUS_GETDATA:
275
    case LAN_RMBUS_PROGRAMMODULE:
276
    case LAN_SYSTEMSTATE_DATACHANGED:
277
    case LAN_RAILCOM_DATACHANGED:
278
    case LAN_RAILCOM_GETDATA:
279
    case LAN_LOCONET_Z21_RX:
280
    case LAN_LOCONET_Z21_TX:
281
    case LAN_LOCONET_FROM_LAN:
282
    case LAN_LOCONET_DISPATCH_ADDR:
283
    case LAN_LOCONET_DETECTOR:
284
    case LAN_CAN_DETECTOR:
285
      break; // not (yet) supported
×
286
  }
287
}
×
288

289
void ServerKernel::onStart()
×
290
{
291
  startInactiveClientPurgeTimer();
×
292
}
×
293

294
void ServerKernel::onStop()
×
295
{
296
  m_inactiveClientPurgeTimer.cancel();
×
297

298
  for(auto& it : m_decoderSubscriptions)
×
299
    it.second.connection.disconnect();
×
300
}
×
301

302
void ServerKernel::sendTo(const Message& message, IOHandler::ClientId clientId)
×
303
{
304
  if(m_ioHandler->sendTo(message, clientId))
×
305
  {
306
    if(m_config.debugLogRXTX)
×
307
      EventLoop::call(
×
308
        [this, clientId, msg=toString(message)]()
×
309
        {
310
          Log::log(logId, LogMessage::D2004_X_TX_X, clientId, msg);
×
311
        });
×
312
  }
313
  else
314
  {} // log message and go to error state
315
}
×
316

317
void ServerKernel::sendTo(const Message& message, BroadcastFlags broadcastFlags)
×
318
{
319
  for(const auto& client : m_clients)
×
320
    if((client.second.broadcastFlags & broadcastFlags) != BroadcastFlags::None)
×
321
      sendTo(message, client.first);
×
322
}
×
323

324
LanSystemStateDataChanged ServerKernel::getLanSystemStateDataChanged() const
×
325
{
326
  LanSystemStateDataChanged message;
×
327

328
  if(m_emergencyStop != TriState::False)
×
329
    message.centralState |= Z21_CENTRALSTATE_EMERGENCYSTOP;
×
330
  if(m_trackPowerOn != TriState::True)
×
331
    message.centralState |= Z21_CENTRALSTATE_TRACKVOLTAGEOFF;
×
332

333
  return message;
×
334
}
335

336
std::shared_ptr<Decoder> ServerKernel::getDecoder(uint16_t address, bool longAddress) const
×
337
{
338
  auto decoder = m_decoderList->getDecoder(longAddress ? DecoderProtocol::DCCLong : DecoderProtocol::DCCShort, address);
×
339
  if(!decoder)
×
340
    decoder = m_decoderList->getDecoder(address);
×
341
  return decoder;
×
342
}
×
343

344
void ServerKernel::removeClient(IOHandler::ClientId clientId)
×
345
{
346
  auto& subscriptions = m_clients[clientId].subscriptions;
×
347
  while(!subscriptions.empty())
×
348
    unsubscribe(clientId, *subscriptions.begin());
×
349
  m_clients.erase(clientId);
×
350
  m_ioHandler->purgeClient(clientId);
×
351
}
×
352

353
void ServerKernel::subscribe(IOHandler::ClientId clientId, uint16_t address, bool longAddress)
×
354
{
355
  auto& subscriptions = m_clients[clientId].subscriptions;
×
356
  const std::pair<uint16_t, bool> key{address, longAddress};
×
357
  if(std::find(subscriptions.begin(), subscriptions.end(), key) != subscriptions.end())
×
358
    return;
×
359
  subscriptions.emplace_back(address, longAddress);
×
360
  if(subscriptions.size() > ServerConfig::subscriptionMax)
×
361
    unsubscribe(clientId, *subscriptions.begin());
×
362

363
  EventLoop::call(
×
364
    [this, key]()
×
365
    {
366
      if(auto it = m_decoderSubscriptions.find(key); it == m_decoderSubscriptions.end())
×
367
      {
368
        if(auto decoder = getDecoder(key.first, key.second))
×
369
          m_decoderSubscriptions.emplace(key, DecoderSubscription{decoder->decoderChanged.connect(std::bind(&ServerKernel::decoderChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)), 1});
×
370
      }
371
      else
372
      {
373
        it->second.count++;
×
374
      }
375
    });
×
376
}
377

378
void ServerKernel::unsubscribe(IOHandler::ClientId clientId, std::pair<uint16_t, bool> key)
×
379
{
380
  {
381
    auto& subscriptions = m_clients[clientId].subscriptions;
×
382
    auto it = std::find(subscriptions.begin(), subscriptions.end(), key);
×
383
    if(it != subscriptions.end())
×
384
      subscriptions.erase(it);
×
385
  }
386

387
  EventLoop::call(
×
388
    [this, key]()
×
389
    {
390
      if(auto it = m_decoderSubscriptions.find(key); it != m_decoderSubscriptions.end())
×
391
      {
392
        assert(it->second.count > 0);
×
393
        if(--it->second.count == 0)
×
394
          m_decoderSubscriptions.erase(it);
×
395
      }
396
    });
×
397
}
×
398

399
void ServerKernel::decoderChanged(const Decoder& decoder, DecoderChangeFlags /*changes*/, uint32_t /*functionNumber*/)
×
400
{
401
  const std::pair<uint16_t, bool> key(decoder.address, decoder.protocol == DecoderProtocol::DCCLong);
×
402
  const LanXLocoInfo message(decoder);
×
403

404
  EventLoop::call(
×
405
    [this, key, message]()
×
406
    {
407
      for(auto it : m_clients)
×
408
        if((it.second.broadcastFlags & BroadcastFlags::PowerLocoTurnoutChanges) == BroadcastFlags::PowerLocoTurnoutChanges)
×
409
        {
410
          auto& subscriptions = it.second.subscriptions;
×
411
          if(std::find(subscriptions.begin(), subscriptions.end(), key) != subscriptions.end())
×
412
            sendTo(message, it.first);
×
413
        }
×
414
    });
×
415
}
×
416

417
void ServerKernel::startInactiveClientPurgeTimer()
×
418
{
419
  assert(ServerConfig::inactiveClientPurgeTime > 0);
420
  m_inactiveClientPurgeTimer.expires_after(boost::asio::chrono::seconds(std::max(1, ServerConfig::inactiveClientPurgeTime / 4)));
×
421
  m_inactiveClientPurgeTimer.async_wait(std::bind(&ServerKernel::inactiveClientPurgeTimerExpired, this, std::placeholders::_1));
×
422
}
×
423

424
void ServerKernel::inactiveClientPurgeTimerExpired(const boost::system::error_code& ec)
×
425
{
426
  if(ec)
×
427
    return;
×
428

429
  std::vector<IOHandler::ClientId> clientsToRemove;
×
430
  const auto purgeTime = std::chrono::steady_clock::now() - std::chrono::seconds(ServerConfig::inactiveClientPurgeTime);
×
431
  for(const auto& it : m_clients)
×
432
    if(it.second.lastSeen < purgeTime)
×
433
      clientsToRemove.emplace_back(it.first);
×
434

435
  for(const auto& clientId : clientsToRemove)
×
436
    removeClient(clientId);
×
437

438
  startInactiveClientPurgeTimer();
×
439
}
×
440

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