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

27
namespace LocoNet {
28

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

36
static void updateActive(SlotReadData& locoSlot)
×
37
{
38
  locoSlot.setActive(
×
39
    (locoSlot.spd != SPEED_STOP && locoSlot.spd != SPEED_ESTOP) ||
×
40
    (locoSlot.dirf & (SL_F0 | SL_F4 | SL_F3 | SL_F2 | SL_F1)) != 0 ||
×
41
    (locoSlot.snd & (SL_F8 | SL_F7 | SL_F6 | SL_F5)) != 0);
×
42
}
×
43

44
SimulationIOHandler::SimulationIOHandler(Kernel& kernel)
×
45
  : IOHandler(kernel)
×
46
{
47
  for(uint8_t slot = SLOT_LOCO_MIN; slot <= SLOT_LOCO_MAX; slot++)
×
48
    m_locoSlots[slot - SLOT_LOCO_MIN].slot = slot;
×
49

50
  // LNCV modules:
51
  m_lncvModules.emplace_back(LNCVModule{
×
52
    6312, // Uhlenbrock USB LocoNet interface 68610
53
    false,
54
    {
55
      {0, 1},
56
      {1, 0},
57
      {2, 4},
58
      {4, 0},
59
    }});
60

61
  m_lncvModules.emplace_back(LNCVModule{
×
62
    6388, // Uhlenbrock S88 LocoNet adaptor 63880
63
    false,
64
    {
65
      {0, 1},
66
      {1, 0},
67
      {2, 20},
68
      {3, 31},
69
      {4, 1},
70
    }});
71
}
×
72

73
bool SimulationIOHandler::send(const Message& message)
×
74
{
75
  reply(message); // echo message back
×
76

77
  switch(message.opCode)
×
78
  {
79
    case OPC_UNLINK_SLOTS:
×
80
      break; // unimplemented
×
81

82
    case OPC_LINK_SLOTS:
×
83
      break; // unimplemented
×
84

85
    case OPC_MOVE_SLOTS:
×
86
      break; // unimplemented
×
87

88
    case OPC_RQ_SL_DATA:
×
89
    {
90
      const auto& requestSlotData = static_cast<const RequestSlotData&>(message);
×
91
      if(isLocoSlot(requestSlotData.slot))
×
92
      {
93
        auto& locoSlot = m_locoSlots[requestSlotData.slot - SLOT_LOCO_MIN];
×
94
        updateChecksum(locoSlot);
×
95
        reply(locoSlot);
×
96
      }
97
      break;
×
98
    }
99
    case OPC_SW_STATE:
×
100
      break; // unimplemented
×
101

102
    case OPC_SW_ACK:
×
103
      break; // unimplemented
×
104

105
    case OPC_LOCO_ADR:
×
106
    {
107
      const auto& locoAdr = static_cast<const LocoAdr&>(message);
×
108

109
      // find slot for address
110
      {
111
        auto it = std::find_if(m_locoSlots.begin(), m_locoSlots.end(), // NOLINT [readability-qualified-auto]
×
112
          [address=locoAdr.address()](const auto& locoSlot)
×
113
          {
114
            return locoSlot.address() == address;
×
115
          });
116

117
        if(it != m_locoSlots.end())
×
118
        {
119
          updateChecksum(*it);
×
120
          reply(*it);
×
121
          return true;
×
122
        }
123
      }
124

125
      // find a free slot
126
      {
127
        auto it = std::find_if(m_locoSlots.begin(), m_locoSlots.end(), // NOLINT [readability-qualified-auto]
×
128
          [](const auto& locoSlot)
×
129
          {
130
            return locoSlot.isFree();
×
131
          });
132

133
        if(it != m_locoSlots.end())
×
134
        {
135
          it->setBusy(true);
×
136
          it->setAddress(locoAdr.address());
×
137
          updateChecksum(*it);
×
138
          reply(*it);
×
139
          return true;
×
140
        }
141
      }
142

143
      // no free slot
144
      reply(LongAck(message.opCode, 0));
×
145

146
      break;
×
147
    }
148
    case OPC_IMM_PACKET:
×
149
    {
150
      if(Uhlenbrock::LNCVStart::check(message))
×
151
      {
152
        const auto lncvStart = static_cast<const Uhlenbrock::LNCVStart&>(message);
×
153

154
        for(auto& module : m_lncvModules)
×
155
        {
156
          if(lncvStart.moduleId() == module.id && (lncvStart.address() == module.address() || lncvStart.address() == LNCVModule::broadcastAddress))
×
157
          {
158
            module.programmingModeActive = true;
×
159
            reply(Uhlenbrock::LNCVReadResponse(lncvStart.moduleId(), LNCVModule::lncvAddress, module.address()));
×
160
          }
161
        }
162
      }
163
      else if(Uhlenbrock::LNCVRead::check(message))
×
164
      {
165
        const auto lncvRead = static_cast<const Uhlenbrock::LNCVRead&>(message);
×
166

167
        for(auto& module : m_lncvModules)
×
168
        {
169
          if(lncvRead.moduleId() == module.id && module.programmingModeActive)
×
170
          {
171
            if(auto it = module.lncvs.find(lncvRead.lncv()); it != module.lncvs.end())
×
172
            {
173
              reply(Uhlenbrock::LNCVReadResponse(lncvRead.moduleId(), lncvRead.lncv(), it->second));
×
174
            }
175
          }
176
        }
177
      }
178
      else if(Uhlenbrock::LNCVWrite::check(message))
×
179
      {
180
        const auto lncvWrite = static_cast<const Uhlenbrock::LNCVWrite&>(message);
×
181

182
        for(auto& module : m_lncvModules)
×
183
        {
184
          if(lncvWrite.moduleId() == module.id && module.programmingModeActive)
×
185
          {
186
            if(auto it = module.lncvs.find(lncvWrite.lncv()); it != module.lncvs.end())
×
187
            {
188
              it->second = lncvWrite.value();
×
189
              reply(LongAck(lncvWrite.opCode, 0x7F));
×
190
            }
191
          }
192
        }
193
      }
194
      else if(Uhlenbrock::LNCVStop::check(message))
×
195
      {
196
        const auto lncvStop = static_cast<const Uhlenbrock::LNCVStop&>(message);
×
197

198
        for(auto& module : m_lncvModules)
×
199
        {
200
          if(lncvStop.moduleId() == module.id)
×
201
          {
202
            module.programmingModeActive = false;
×
203
          }
204
        }
205
      }
206
      else if(isSignatureMatch<ImmPacket>(message))
×
207
      {
208
        reply(LongAck(message.opCode, 0x7F)); // accept, not limited
×
209
      }
210
      break;
×
211
    }
212
    case OPC_WR_SL_DATA:
×
213
      reply(LongAck(message.opCode, 0x7F)); // slot write successful
×
214
      break;
×
215

216
    // no response:
217
    case OPC_LOCO_SPD:
×
218
    {
219
      const auto& locoSpd = static_cast<const LocoSpd&>(message);
×
220
      if(isLocoSlot(locoSpd.slot))
×
221
      {
222
        auto& locoSlot = m_locoSlots[locoSpd.slot - SLOT_LOCO_MIN];
×
223
        locoSlot.spd = locoSpd.speed;
×
224
        updateActive(locoSlot);
×
225
      }
226
      break;
×
227
    }
228
    case OPC_LOCO_DIRF:
×
229
    {
230
      const auto& locoDirF = static_cast<const LocoDirF&>(message);
×
231
      if(isLocoSlot(locoDirF.slot))
×
232
      {
233
        auto& locoSlot = m_locoSlots[locoDirF.slot - SLOT_LOCO_MIN];
×
234
        locoSlot.dirf = locoDirF.dirf;
×
235
        updateActive(locoSlot);
×
236
      }
237
      break;
×
238
    }
239
    case OPC_LOCO_SND:
×
240
    {
241
      const auto& locoSnd = static_cast<const LocoSnd&>(message);
×
242
      if(isLocoSlot(locoSnd.slot))
×
243
      {
244
        auto& locoSlot = m_locoSlots[locoSnd.slot - SLOT_LOCO_MIN];
×
245
        locoSlot.snd = locoSnd.snd;
×
246
        updateActive(locoSlot);
×
247
      }
248
      break;
×
249
    }
250

251
    case OPC_BUSY:
×
252
    case OPC_GPOFF:
253
    case OPC_GPON:
254
    case OPC_IDLE:
255
    case OPC_LOCO_F9F12:
256
    case OPC_SW_REQ:
257
    case OPC_SW_REP:
258
    case OPC_INPUT_REP:
259
    case OPC_LONG_ACK:
260
    case OPC_SLOT_STAT1:
261
    case OPC_CONSIST_FUNC:
262
    case OPC_MULTI_SENSE:
263
    case OPC_D4:
264
    case OPC_MULTI_SENSE_LONG:
265
    case OPC_E4:
266
    case OPC_PEER_XFER:
267
    case OPC_SL_RD_DATA:
268
      assert(!hasResponse(message)); // no response
×
269
      break;
×
270
  }
271

272
  return true;
×
273
}
274

275
void SimulationIOHandler::reply(const Message& message)
×
276
{
277
  // post the reply, so it has some delay
278
  //! \todo better delay simulation? at least loconet message transfer time?
NEW
279
  boost::asio::post(m_kernel.ioContext(), 
×
280
    [this, data=copy(message)]()
×
281
    {
282
      m_kernel.receive(*reinterpret_cast<const Message*>(data.get()));
×
283
    });
×
284
}
×
285

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