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

traintastic / traintastic / 24288605255

11 Apr 2026 06:17PM UTC coverage: 25.599% (-2.4%) from 27.99%
24288605255

push

github

web-flow
Merge pull request #222 from traintastic/cbus

Added CBUS/VLCB hardware support

169 of 3369 new or added lines in 99 files covered. (5.02%)

5 existing lines in 4 files now uncovered.

8300 of 32423 relevant lines covered (25.6%)

178.31 hits per line

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

0.0
/server/src/hardware/protocol/cbus/simulator/module/cbuscancmd.cpp
1
/**
2
 * This file is part of Traintastic,
3
 * see <https://github.com/traintastic/traintastic>.
4
 *
5
 * Copyright (C) 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 "cbuscancmd.hpp"
23
#include <algorithm>
24
#include "../../cbuscanmessageutils.hpp"
25
#include "../../cbusmessages.hpp"
26

27
namespace CBUS::Module {
28

NEW
29
CANCMD::CANCMD(Simulator& simulator, uint16_t nodeNumber_, uint8_t canId_)
×
NEW
30
  : CANModule(simulator, nodeNumber_, canId_)
×
31
{
NEW
32
}
×
33

NEW
34
void CANCMD::receive(const CAN::Message& canMessage)
×
35
{
NEW
36
  const auto& message = asMessage(canMessage);
×
NEW
37
  switch(message.opCode)
×
38
  {
39
    using enum OpCode;
40

NEW
41
    case RTOF:
×
NEW
42
      m_trackOn = false;
×
NEW
43
      send(TrackOff());
×
NEW
44
      break;
×
45

NEW
46
    case RTON:
×
NEW
47
      m_trackOn = true;
×
NEW
48
      send(TrackOn());
×
NEW
49
      break;
×
50

NEW
51
    case RESTP:
×
NEW
52
      m_eStop = true;
×
NEW
53
      send(EmergencyStop());
×
NEW
54
      break;
×
55

NEW
56
    case RSTAT:
×
NEW
57
      send(CommandStationStatusReport(nodeNumber, false, false, m_trackOn, m_busOn, m_eStop, false, false, 0x04, 0x66, 0x0C));
×
NEW
58
      break;
×
59

NEW
60
    case QNN:
×
NEW
61
      send(PresenceOfNode(nodeNumber, 0xA5, 0x53, false, true, true, true, false, false, false));
×
NEW
62
      break;
×
63

NEW
64
    case RLOC:
×
65
    {
NEW
66
      const auto& rloc = static_cast<const RequestEngineSession&>(message);
×
NEW
67
      handleGetEngineSession(rloc.address(), rloc.isLongAddress(), GetEngineSession::Mode::Request);
×
NEW
68
      break;
×
69
    }
NEW
70
    case GLOC:
×
71
    {
NEW
72
      const auto& gloc = static_cast<const GetEngineSession&>(message);
×
NEW
73
      handleGetEngineSession(gloc.address(), gloc.isLongAddress(), gloc.mode());
×
NEW
74
      break;
×
75
    }
NEW
76
    case RQNPN:
×
77
    {
NEW
78
      const auto& rqnpn = static_cast<const ReadNodeParameter&>(message);
×
NEW
79
      if(rqnpn.nodeNumber() == nodeNumber)
×
80
      {
81
        static constexpr std::array<uint8_t, 21> nodeParameters{{20, 165, 102, 83, 255, 10, 216, 4, 14, 3, 1, 0, 8, 0, 0, 196, 26, 0, 0, 1, 12}};
NEW
82
        if(static_cast<uint8_t>(rqnpn.parameter) < nodeParameters.size())
×
83
        {
NEW
84
          send(NodeParameterResponse(nodeNumber, rqnpn.parameter, nodeParameters[static_cast<uint8_t>(rqnpn.parameter)]));
×
85
        }
86
        // else FIXME: send CMDERR(Invalid Parameter Index) and GRSP(Invalid Parameter Index)
87
      }
NEW
88
      break;
×
89
    }
NEW
90
    default:
×
NEW
91
      break;
×
92
  }
NEW
93
}
×
94

NEW
95
EngineReport* CANCMD::newEngineSession(const EngineReport& init)
×
96
{
NEW
97
  for(uint8_t session = 1;; ++session)
×
98
  {
NEW
99
    if(!m_sessions.contains(session))
×
100
    {
NEW
101
      auto& newSession = m_sessions.emplace(session, init).first->second;
×
NEW
102
      newSession.session = session;
×
NEW
103
      return &newSession;
×
104
    }
NEW
105
    if (session == 255)
×
106
    {
NEW
107
      break;
×
108
    }
NEW
109
  }
×
NEW
110
  return nullptr;
×
111
}
112

NEW
113
void CANCMD::handleGetEngineSession(uint16_t address, bool isLongAddress, GetEngineSession::Mode mode)
×
114
{
115
  using enum GetEngineSession::Mode;
116

NEW
117
  if(mode == static_cast<GetEngineSession::Mode>(0b11))
×
118
  {
NEW
119
    send(CommandStationInvalidRequestError(address, isLongAddress));
×
NEW
120
    return;
×
121
  }
122

NEW
123
  if(auto it = std::find_if(m_sessions.begin(), m_sessions.end(),
×
NEW
124
    [address, isLongAddress](const auto& item)
×
125
    {
NEW
126
      return item.second.address() == address && item.second.isLongAddress() == isLongAddress;
×
NEW
127
    }); it != m_sessions.end()) // existing session
×
128
  {
NEW
129
    if(mode == Request)
×
130
    {
NEW
131
      send(CommandStationLocoAddressTakenError(address, isLongAddress));
×
132
    }
NEW
133
    else if(mode == Share)
×
134
    {
NEW
135
      send(it->second);
×
136
    }
137
    else // Steal
138
    {
NEW
139
      if(auto* ploc = newEngineSession(it->second))
×
140
      {
NEW
141
        const auto oldSession = it->first;
×
NEW
142
        send(CommandStationSessionCancelled(oldSession));
×
NEW
143
        send(*ploc);
×
NEW
144
        m_sessions.erase(oldSession);
×
145
      }
146
      else
147
      {
NEW
148
        send(CommandStationLocoStackFullError(address, isLongAddress));
×
149
      }
150
    }
151
  }
152
  else // new session
153
  {
NEW
154
    if(mode == Share)
×
155
    {
156
      // FIXME: If there is no current session for a share then send an ERR(No session) message.
157
    }
158
    else // Release or Steal
159
    {
NEW
160
      if(const auto* ploc = newEngineSession(EngineReport(
×
161
          0,
162
          address, isLongAddress,
163
          0, true,
164
          false, false, false, false, false,
165
          false, false, false, false,
166
          false, false, false, false)))
167
      {
NEW
168
        send(*ploc);
×
169
      }
170
      else
171
      {
NEW
172
        send(CommandStationLocoStackFullError(address, isLongAddress));
×
173
      }
174
    }
175
  }
176
}
177

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