• 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/iohandler/simulationiohandler.cpp
1
/**
2
 * server/src/hardware/protocol/z21/iohandler/simulationiohandler.cpp
3
 *
4
 * This file is part of the traintastic source code.
5
 *
6
 * Copyright (C) 2022 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 "../clientkernel.hpp"
25
#include "../messages.hpp"
26

27
namespace Z21 {
28

29
static std::shared_ptr<std::byte[]> copy(const Message& message)
×
30
{
31
  auto* bytes = new std::byte[message.dataLen()];
×
32
  std::memcpy(bytes, &message, message.dataLen());
×
33
  return std::shared_ptr<std::byte[]>{bytes};
×
34
}
35

36
SimulationIOHandler::SimulationIOHandler(Kernel& kernel)
×
37
  : IOHandler(kernel)
×
38
{
39
}
×
40

41
bool SimulationIOHandler::send(const Message& message)
×
42
{
43
  switch(message.header())
×
44
  {
45
    case LAN_X:
×
46
    {
47
      const auto& lanX = static_cast<const LanX&>(message);
×
48

49
      switch(lanX.xheader)
×
50
      {
51
        case 0x21:
×
52
        {
53
          if(message == LanXGetVersion())
×
54
          {
55
            reply(LanXGetVersionReply(xBusVersion, CommandStationId::Z21));
×
56
          }
57
          else if(message == LanXGetStatus())
×
58
          {
59
            LanXStatusChanged response;
×
60
            if(m_emergencyStop)
×
61
              response.db1 |= Z21_CENTRALSTATE_EMERGENCYSTOP;
×
62
            if(!m_trackPowerOn)
×
63
              response.db1 |= Z21_CENTRALSTATE_TRACKVOLTAGEOFF;
×
64
            response.updateChecksum();
×
65
            reply(response);
×
66
          }
67
          else if(message == LanXSetTrackPowerOn())
×
68
          {
69
            const bool changed = !m_trackPowerOn || m_emergencyStop;
×
70
            m_trackPowerOn = true;
×
71
            m_emergencyStop = false;
×
72
            reply(LanXBCTrackPowerOn());
×
73
            if(changed && (m_broadcastFlags & BroadcastFlags::SystemStatusChanges) == BroadcastFlags::SystemStatusChanges)
×
74
            {
75
              replyLanSystemStateDataChanged();
×
76
            }
77
          }
78
          else if(message == LanXSetTrackPowerOff())
×
79
          {
80
            const bool changed = m_trackPowerOn;
×
81
            m_trackPowerOn = false;
×
82
            reply(LanXBCTrackPowerOff());
×
83
            if(changed && (m_broadcastFlags & BroadcastFlags::SystemStatusChanges) == BroadcastFlags::SystemStatusChanges)
×
84
            {
85
              replyLanSystemStateDataChanged();
×
86
            }
87
          }
88
          break;
×
89
        }
90
        case LAN_X_SET_STOP:
×
91
        {
92
          if(message == LanXSetStop())
×
93
          {
94
            const bool changed = !m_emergencyStop;
×
95
            m_emergencyStop = true;
×
96
            reply(LanXBCStopped());
×
97
            if(changed && (m_broadcastFlags & BroadcastFlags::SystemStatusChanges) == BroadcastFlags::SystemStatusChanges)
×
98
            {
99
              replyLanSystemStateDataChanged();
×
100
            }
101
          }
102
          break;
×
103
        }
104
        case LAN_X_GET_LOCO_INFO:
×
105
        {
106
          if(const auto& getLocoInfo = static_cast<const LanXGetLocoInfo&>(message);
×
107
              getLocoInfo.db0 == 0xF0)
×
108
          {
109
            auto it = m_decoderCache.find(getLocoInfo.address());
×
110
            if(it != m_decoderCache.cend())
×
111
              reply(it->second);
×
112
            else
113
            {
114
              LanXLocoInfo empty;
×
115
              empty.setAddress(getLocoInfo.address(), getLocoInfo.isLongAddress());
×
116
              empty.setSpeedSteps(126);
×
117
              empty.setEmergencyStop();
×
118
              empty.updateChecksum();
×
119
              reply(empty);
×
120
            }
121
          }
122
          break;
×
123
        }
124
        case LAN_X_SET_LOCO:
×
125
        {
126
          if(const auto& setLocoDrive = static_cast<const LanXSetLocoDrive&>(message);
×
127
              setLocoDrive.db0 >= 0x10 && setLocoDrive.db0 <= 0x13)
×
128
          {
129
            auto it = m_decoderCache.find(setLocoDrive.address());
×
130
            if(it == m_decoderCache.cend())
×
131
            {
132
              // Insert in cache
133
              LanXLocoInfo empty;
×
134
              empty.setAddress(setLocoDrive.address(), setLocoDrive.isLongAddress());
×
135
              empty.setSpeedSteps(126);
×
136
              empty.setEmergencyStop();
×
137
              it = m_decoderCache.insert({setLocoDrive.address(), empty}).first;
×
138
            }
139

140
            LanXLocoInfo &info = it->second;
×
141
            info.setSpeedSteps(setLocoDrive.speedSteps());
×
142
            info.setDirection(setLocoDrive.direction());
×
143
            if(setLocoDrive.isEmergencyStop())
×
144
              info.setEmergencyStop();
×
145
            else
146
              info.setSpeedStep(setLocoDrive.speedStep());
×
147

148
            info.setBusy(true);
×
149
            info.updateChecksum();
×
150

151
            reply(info);
×
152
          }
×
153
          else if(const auto& setLocoFunction = static_cast<const LanXSetLocoFunction&>(message);
×
154
                  setLocoFunction.db0 == 0xF8 &&
×
155
                  setLocoFunction.switchType() != LanXSetLocoFunction::SwitchType::Invalid)
×
156
          {
157
            auto it = m_decoderCache.find(setLocoDrive.address());
×
158
            if(it == m_decoderCache.cend())
×
159
            {
160
              // Insert in cache
161
                LanXLocoInfo empty;
×
162
                empty.setAddress(setLocoFunction.address(), setLocoFunction.isLongAddress());
×
163
                empty.setSpeedSteps(126);
×
164
                empty.setEmergencyStop();
×
165
                it = m_decoderCache.insert({setLocoFunction.address(), empty}).first;
×
166
            }
167

168
            LanXLocoInfo &info = it->second;
×
169
            bool val = info.getFunction(setLocoFunction.functionIndex());
×
170
            switch (setLocoFunction.switchType())
×
171
            {
172
            case LanXSetLocoFunction::SwitchType::Off:
×
173
              val = false;
×
174
              break;
×
175
            case LanXSetLocoFunction::SwitchType::On:
×
176
              val = true;
×
177
              break;
×
178
            case LanXSetLocoFunction::SwitchType::Toggle:
×
179
              val = !val;
×
180
              break;
×
181
            default:
×
182
              break;
×
183
            }
184
            info.setFunction(setLocoFunction.functionIndex(), val);
×
185

186
            info.setBusy(true);
×
187
            info.updateChecksum();
×
188

189
            reply(info);
×
190
          }
191
          break;
×
192
        }
193
        case LAN_X_GET_FIRMWARE_VERSION:
×
194
        {
195
          if(message == LanXGetFirmwareVersion())
×
196
          {
197
            reply(LanXGetFirmwareVersionReply(firmwareVersionMajor, ServerConfig::firmwareVersionMinor));
×
198
          }
199
          break;
×
200
        }
201
        case LAN_X_SET_TURNOUT:
×
202
        {
203
          if(message.dataLen() == sizeof(LanXSetTurnout))
×
204
          {
205
            const auto& setTurnout = static_cast<const LanXSetTurnout&>(message);
×
206
            if((m_broadcastFlags & BroadcastFlags::PowerLocoTurnoutChanges) == BroadcastFlags::PowerLocoTurnoutChanges)
×
207
            {
208
              // Client has subscribed to turnout changes
209
              reply(LanXTurnoutInfo(setTurnout.address(), setTurnout.port(), false));
×
210
            }
211
          }
212
          break;
×
213
        }
214
        case LAN_X_TURNOUT_INFO:
×
215
        {
216
          if(message.dataLen() == sizeof(LanXGetTurnoutInfo))
×
217
          {
218
            const auto& getTurnout = static_cast<const LanXGetTurnoutInfo&>(message);
×
219
            //We do not keep a record of turnout states so send "Unknown Position"
220
            reply(LanXTurnoutInfo(getTurnout.address(), false, true));
×
221
          }
222
          break;
×
223
        }
224
        case LAN_X_SET_EXT_ACCESSORY:
×
225
        {
226
          if(message.dataLen() == sizeof(LanXSetExtAccessory))
×
227
          {
228
            const auto& setAccessory = static_cast<const LanXSetExtAccessory&>(message);
×
229
            if((m_broadcastFlags & BroadcastFlags::PowerLocoTurnoutChanges) == BroadcastFlags::PowerLocoTurnoutChanges)
×
230
            {
231
              // Client has subscribed to turnout changes
232
              reply(LanXExtAccessoryInfo(setAccessory.address(), setAccessory.aspect(), false));
×
233
            }
234
          }
235
          break;
×
236
        }
237
        case LAN_X_EXT_ACCESSORY_INFO:
×
238
        {
239
          if(message.dataLen() == sizeof(LanXGetExtAccessoryInfo))
×
240
          {
241
            const auto& getAccessory = static_cast<const LanXGetExtAccessoryInfo&>(message);
×
242
            //We do not keep a record of accessory states so send "Unknown Position"
243
            reply(LanXExtAccessoryInfo(getAccessory.address(), 0, true));
×
244
          }
245
          break;
×
246
        }
247
      }
248
      break;
×
249
    }
250
    case LAN_GET_SERIAL_NUMBER:
×
251
      if(message.dataLen() == sizeof(LanGetSerialNumber))
×
252
      {
253
        reply(LanGetSerialNumberReply(serialNumber));
×
254
      }
255
      break;
×
256

257
    case LAN_GET_HWINFO:
×
258
      if(message.dataLen() == sizeof(LanGetHardwareInfo))
×
259
      {
260
        reply(LanGetHardwareInfoReply(hardwareType, firmwareVersionMajor, firmwareVersionMinor));
×
261
      }
262
      break;
×
263

264
    case LAN_GET_BROADCASTFLAGS:
×
265
      if(message == LanGetBroadcastFlags())
×
266
      {
267
        reply(LanGetBroadcastFlagsReply(m_broadcastFlags));
×
268
      }
269
      break;
×
270

271
    case LAN_SET_BROADCASTFLAGS:
×
272
      if(message.dataLen() == sizeof(LanSetBroadcastFlags))
×
273
      {
274
        m_broadcastFlags = static_cast<const LanSetBroadcastFlags&>(message).broadcastFlags();
×
275
      }
276
      break;
×
277

278
    case LAN_SYSTEMSTATE_GETDATA:
×
279
      if(message == LanSystemStateGetData())
×
280
      {
281
        replyLanSystemStateDataChanged();
×
282
      }
283
      break;
×
284

285
    case LAN_LOGOFF:
×
286
    case LAN_GET_CODE:
287
    case LAN_GET_LOCO_MODE:
288
    case LAN_SET_LOCO_MODE:
289
    case LAN_GET_TURNOUTMODE:
290
    case LAN_SET_TURNOUTMODE:
291
    case LAN_RMBUS_DATACHANGED:
292
    case LAN_RMBUS_GETDATA:
293
    case LAN_RMBUS_PROGRAMMODULE:
294
    case LAN_SYSTEMSTATE_DATACHANGED:
295
    case LAN_RAILCOM_DATACHANGED:
296
    case LAN_RAILCOM_GETDATA:
297
    case LAN_LOCONET_Z21_RX:
298
    case LAN_LOCONET_Z21_TX:
299
    case LAN_LOCONET_FROM_LAN:
300
    case LAN_LOCONET_DISPATCH_ADDR:
301
    case LAN_LOCONET_DETECTOR:
302
    case LAN_CAN_DETECTOR:
303
      break; // not (yet) supported
×
304
  }
305

306
  return true;
×
307
}
308

309
void SimulationIOHandler::reply(const Message& message)
×
310
{
311
  // post the reply, so it has some delay
312
  //! \todo better delay simulation? at least z21 message transfer time?
NEW
313
  boost::asio::post(m_kernel.ioContext(), 
×
314
    [this, data=copy(message)]()
×
315
    {
316
      static_cast<ClientKernel&>(m_kernel).receive(*reinterpret_cast<const Message*>(data.get()));
×
317
    });
×
318
}
×
319

320
void SimulationIOHandler::replyLanSystemStateDataChanged()
×
321
{
322
  LanSystemStateDataChanged message;
×
323

324
  if(m_emergencyStop)
×
325
    message.centralState |= Z21_CENTRALSTATE_EMERGENCYSTOP;
×
326
  if(!m_trackPowerOn)
×
327
    message.centralState |= Z21_CENTRALSTATE_TRACKVOLTAGEOFF;
×
328

329
  reply(message);
×
330
}
×
331

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