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

traintastic / traintastic / 22823086659

08 Mar 2026 02:30PM UTC coverage: 26.831% (+0.06%) from 26.774%
22823086659

push

github

reinder
[cbus] renamed CBUSAccessory(Short) to Long/Short event and added node setting for Long events.

0 of 15 new or added lines in 2 files covered. (0.0%)

1909 existing lines in 38 files now uncovered.

8230 of 30674 relevant lines covered (26.83%)

186.8 hits per line

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

0.0
/server/src/hardware/protocol/loconet/kernel.hpp
1
/**
2
 * This file is part of Traintastic,
3
 * see <https://github.com/traintastic/traintastic>.
4
 *
5
 * Copyright (C) 2019-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
#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_KERNEL_HPP
23
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_KERNEL_HPP
24

25
#include "../kernelbase.hpp"
26
#include <array>
27
#include <unordered_map>
28
#include <filesystem>
29
#include <queue>
30
#include <boost/asio/steady_timer.hpp>
31
#include <boost/signals2/connection.hpp>
32
#include <span>
33
#include <traintastic/enum/direction.hpp>
34
#include <traintastic/enum/tristate.hpp>
35
#include <traintastic/enum/outputchannel.hpp>
36
#include "config.hpp"
37
#include "iohandler/iohandler.hpp"
38
#include "../../output/outputtypes.hpp"
39

40
class Clock;
41
class Decoder;
42
enum class DecoderChangeFlags;
43
class DecoderController;
44
enum class SimulateInputAction;
45
class InputController;
46
class OutputController;
47
class IdentificationController;
48
class PCAP;
49

50
namespace LocoNet {
51

52
struct Message;
53

54
class Kernel : public ::KernelBase
55
{
56
  public:
57
    using OnLNCVReadResponse = std::function<void(bool, uint16_t, uint16_t)>;
58

59
    static constexpr uint16_t accessoryOutputAddressMin = 1;
60
    static constexpr uint16_t accessoryOutputAddressMax = 2048;
61

62
  private:
63
    enum Priority
64
    {
65
      HighPriority = 0,
66
      NormalPriority = 1,
67
      LowPriority = 2,
68
    };
69
    friend constexpr Priority& operator ++(Priority& value);
70

71
    class SendQueue
72
    {
73
      private:
74
        std::array<std::byte, 4000> m_buffer;
75
        std::byte* m_front;
76
        std::size_t m_bytes;
77

UNCOV
78
        constexpr std::size_t threshold() const noexcept { return m_buffer.size() / 2; }
×
79

80
      public:
UNCOV
81
        SendQueue()
×
82
          : m_buffer{}
×
83
          , m_front{m_buffer.data()}
×
84
          , m_bytes{0}
×
85
        {
UNCOV
86
        }
×
87

UNCOV
88
        inline bool empty() const
×
89
        {
UNCOV
90
          return m_bytes == 0;
×
91
        }
92

UNCOV
93
        inline const Message& front() const
×
94
        {
UNCOV
95
          return *reinterpret_cast<const Message*>(m_front);
×
96
        }
97

98
        bool append(const Message& message);
99

100
        void pop();
101

102
        void clear();
103
    };
104

105
    struct LocoSlot
106
    {
107
      static constexpr uint16_t invalidAddress = 0xFFFF;
108
      static constexpr uint8_t invalidSpeed = 0xFF;
109

110
      uint16_t address;
111
      uint8_t speed;
112
      Direction direction;
113
      std::array<TriState, 29> functions;
114

UNCOV
115
      LocoSlot()
×
116
      {
×
117
        invalidate();
×
118
      }
×
119

UNCOV
120
      bool isAddressValid() const
×
121
      {
UNCOV
122
        return address != invalidAddress;
×
123
      }
124

UNCOV
125
      void invalidate()
×
126
      {
UNCOV
127
        address = invalidAddress;
×
128
        speed = invalidSpeed;
×
129
        direction = Direction::Unknown;
×
130
        for(auto& f : functions)
×
131
          f = TriState::Undefined;
×
132
      }
×
133
    };
134

135
    struct FastClock
136
    {
137
      uint8_t multiplier;
138
      uint8_t hour;
139
      uint8_t minute;
140
      uint8_t : 8; // padding for std::atomic
141
    };
142
    static_assert(sizeof(FastClock) == 4);
143

144
    std::unique_ptr<IOHandler> m_ioHandler;
145
    const bool m_simulation;
146

147
    std::array<SendQueue, 3> m_sendQueue;
148
    Priority m_sentMessagePriority;
149
    bool m_waitingForEcho;
150
    boost::asio::steady_timer m_waitingForEchoTimer;
151
    bool m_waitingForResponse;
152
    bool m_waitingForLNCVReadResponse = false;
153
    struct
154
    {
155
      uint16_t moduleId = 0;
156
      uint16_t address = 0;
157
      uint16_t lncv = 0;
158

UNCOV
159
      void reset()
×
160
      {
UNCOV
161
        moduleId = 0;
×
162
        address = 0;
×
163
        lncv = 0;
×
164
      }
×
165
    } m_pendingLNCVRead;
166
    boost::asio::steady_timer m_waitingForResponseTimer;
167

168
    TriState m_globalPower;
169
    TriState m_emergencyStop;
170
    std::function<void(bool, bool)> m_onStateChanged;
171

172
    std::shared_ptr<Clock> m_clock;
173
    boost::signals2::connection m_clockChangeConnection;
174
    std::atomic<FastClock> m_fastClock;
175

176
    boost::asio::steady_timer m_fastClockSyncTimer;
177
    bool m_fastClockSupported = true;
178

179
    bool m_lncvActive = false;
180
    uint16_t m_lncvModuleId = 0;
181
    uint16_t m_lncvModuleAddress = 0;
182
    OnLNCVReadResponse m_onLNCVReadResponse;
183
    struct LNCVRead
184
    {
185
      uint16_t moduleId;
186
      uint16_t address;
187
      uint16_t lncv;
188
      std::function<void(uint16_t, std::error_code)> callback;
189
    };
190
    std::queue<LNCVRead> m_lncvReads;
191

192
    DecoderController* m_decoderController;
193
    std::unordered_map<uint16_t, uint8_t> m_addressToSlot;
194
    std::unordered_map<uint8_t, LocoSlot> m_slots;
195
    std::unordered_map<uint16_t, std::vector<std::byte>> m_pendingSlotMessages;
196

197
    InputController* m_inputController;
198
    std::array<TriState, 4096> m_inputValues;
199

200
    OutputController* m_outputController;
201
    std::array<OutputPairValue, accessoryOutputAddressMax - accessoryOutputAddressMin + 1> m_outputValues;
202

203
    IdentificationController* m_identificationController;
204

205
    const std::filesystem::path m_debugDir;
206
    std::unique_ptr<PCAP> m_pcap;
207

208
    Config m_config;
209

210
    Kernel(std::string logId_, const Config& config, bool simulation);
211

212
    LocoSlot* getLocoSlot(uint8_t slot, bool sendSlotDataRequestIfNew = true);
213
    LocoSlot* getLocoSlotByAddress(uint16_t address);
214
    void clearLocoSlot(uint8_t slot);
215

216
    std::shared_ptr<Decoder> getDecoder(uint16_t address);
217

218
    void setIOHandler(std::unique_ptr<IOHandler> handler);
219

UNCOV
220
    inline const Message& lastSentMessage() const
×
221
    {
UNCOV
222
      return m_sendQueue[m_sentMessagePriority].front();
×
223
    }
224

225
    void resume();
226

227
    void send(const Message& message, Priority priority = NormalPriority);
228
    template<class T>
UNCOV
229
    void postSend(const T& message)
×
230
    {
UNCOV
231
      assert(sizeof(message) == message.size());
×
232
      m_ioContext.post(
×
233
        [this, message]()
×
234
        {
UNCOV
235
          send(message);
×
236
        });
UNCOV
237
    }
×
238
    template<class T>
239
    void postSend(const T& message, Priority priority)
240
    {
241
      assert(sizeof(message) == message.size());
242
      m_ioContext.post(
243
        [this, message, priority]()
244
        {
245
          send(message, priority);
246
        });
247
    }
248
    void send(uint16_t address, Message& message, uint8_t& slot);
249
    template<class T>
250
    inline void send(uint16_t address, T& message)
251
    {
252
      send(address, message, message.slot);
253
    }
254
    template<class T>
UNCOV
255
    void postSend(uint16_t address, const T& message)
×
256
    {
UNCOV
257
      m_ioContext.post(
×
258
        [this, address, message]()
×
259
        {
UNCOV
260
          T msg(message);
×
261
          send(address, msg, msg.slot);
×
262
        });
UNCOV
263
    }
×
264
    void sendNextMessage();
265

266
    void waitingForEchoTimerExpired(const boost::system::error_code& ec);
267
    void waitingForResponseTimerExpired(const boost::system::error_code& ec);
268

269
    void setFastClockMaster(bool enable);
270
    void disableClockEvents();
271
    void enableClockEvents();
272

273
    void startFastClockSyncTimer();
274
    void stopFastClockSyncTimer();
275
    void fastClockSyncTimerExpired(const boost::system::error_code& ec);
276

277
    template<uint8_t First, uint8_t Last, class T>
278
    bool updateFunctions(LocoSlot& slot, const T& message);
279

280
    void startPCAP(PCAPOutput pcapOutput);
281

282
  public:
283
    static constexpr uint16_t inputAddressMin = 1;
284
    static constexpr uint16_t inputAddressMax = 4096;
285
    static constexpr uint16_t identificationAddressMin = 1;
286
    static constexpr uint16_t identificationAddressMax = 4096;
287

288
    Kernel(const Kernel&) = delete;
289
    Kernel& operator =(const Kernel&) = delete;
290
    ~Kernel();
291

292
#ifndef NDEBUG
UNCOV
293
    bool isKernelThread() const
×
294
    {
UNCOV
295
      return std::this_thread::get_id() == m_thread.get_id();
×
296
    }
297
#endif
298

299
    /**
300
     * @brief Create kernel and IO handler
301
     *
302
     * @param[in] config LocoNet configuration
303
     * @param[in] args IO handler arguments
304
     * @return The kernel instance
305
     */
306
    template<class IOHandlerType, class... Args>
UNCOV
307
    static std::unique_ptr<Kernel> create(std::string logId_, const Config& config, Args... args)
×
308
    {
309
      static_assert(std::is_base_of_v<IOHandler, IOHandlerType>);
UNCOV
310
      std::unique_ptr<Kernel> kernel{new Kernel(std::move(logId_), config, isSimulation<IOHandlerType>())};
×
311
      kernel->setIOHandler(std::make_unique<IOHandlerType>(*kernel, std::forward<Args>(args)...));
×
312
      return kernel;
×
313
    }
×
314

315
    /**
316
     * @brief Access the IO handler
317
     *
318
     * @return The IO handler
319
     * @note The IO handler runs in the kernel's IO context, not all functions can be called safely!
320
     */
321
    template<class T>
322
    T& ioHandler()
323
    {
324
      assert(dynamic_cast<T*>(m_ioHandler.get()));
325
      return static_cast<T&>(*m_ioHandler);
326
    }
327

328
    /**
329
     * @brief Set LocoNet configuration
330
     *
331
     * @param[in] config The LocoNet configuration
332
     */
333
    void setConfig(const Config& config);
334

335
    /**
336
     * @brief ...
337
     *
338
     * @param[in] callback ...
339
     * @note This function may not be called when the kernel is running.
340
     */
341
    void setOnStateChanged(std::function<void(bool, bool)> callback);
342

343
    /**
344
     * @brief Set clock for LocoNet fast clock
345
     *
346
     * @param[in] clock The clock
347
     * @note This function may not be called when the kernel is running.
348
     */
349
    void setClock(std::shared_ptr<Clock> clock);
350

351
    /**
352
     * @brief Set the decoder controller
353
     *
354
     * @param[in] decoderController The decoder controller
355
     * @note This function may not be called when the kernel is running.
356
     */
357
    void setDecoderController(DecoderController* decoderController);
358

359
    /**
360
     * @brief Set the input controller
361
     *
362
     * @param[in] inputController The input controller
363
     * @note This function may not be called when the kernel is running.
364
     */
365
    void setInputController(InputController* inputController);
366

367
    /**
368
     * @brief Set the output controller
369
     *
370
     * @param[in] outputController The output controller
371
     * @note This function may not be called when the kernel is running.
372
     */
373
    void setOutputController(OutputController* outputController);
374

375
    /**
376
     * @brief Set the identification controller
377
     *
378
     * @param[in] identificationController The identification controller
379
     * @note This function may not be called when the kernel is running.
380
     */
381
    void setIdentificationController(IdentificationController* identificationController);
382

383
    /**
384
     * @brief Start the kernel and IO handler
385
     */
386
    void start();
387

388
    /**
389
     * @brief Stop the kernel and IO handler
390
     */
391
    void stop();
392

393
    /**
394
     * \brief ...
395
     *
396
     * This must be called by the IO handler when the connection is ready.
397
     *
398
     * \note This function must run in the kernel's IO context
399
     */
400
    void started() final;
401

402
    /**
403
     * @brief ...
404
     *
405
     * This must be called by the IO handler whenever a LocoNet message is received.
406
     *
407
     * @param[in] message The received LocoNet message
408
     * @note This function must run in the kernel's IO context
409
     */
410
    void receive(const Message& message);
411

412
    void setState(bool powerOn, bool run);
413

414
    //! \brief Send LocoNet packet
415
    //! \param[in] packet LocoNet packet bytes, exluding checksum.
416
    //! \return \c true if send, \c false otherwise.
417
    bool send(std::span<uint8_t> packet);
418

419
    //! \brief Send immediate DCC packet
420
    //! \param[in] dccPacket DCC packet byte, exluding checksum. Length is limited to 5.
421
    //! \param[in] repeat DCC packet repeat count 0..7
422
    //! \return \c true if send to command station, \c false otherwise.
423
    bool immPacket(std::span<const uint8_t> dccPacket, uint8_t repeat);
424

425
    template<class T, std::enable_if_t<std::is_trivially_copyable_v<T> && sizeof(T) <= 5, bool> = true>
UNCOV
426
    bool immPacket(const T& dccPacket, uint8_t repeat)
×
427
    {
UNCOV
428
      return immPacket(std::span<const uint8_t>(reinterpret_cast<const uint8_t*>(&dccPacket), sizeof(T)), repeat);
×
429
    }
430

431
    void readLNCV(uint16_t moduleId, uint16_t address, uint16_t lncv, std::function<void(uint16_t, std::error_code)> callback);
432

433
    /**
434
     *
435
     *
436
     */
437
    void decoderChanged(const Decoder& decoder, DecoderChangeFlags changes, uint32_t functionNumber);
438

439
    /**
440
     *
441
     * \param[in] channel Output channel
442
     * @param[in] address Output address
443
     * @param[in] value Output value
444
     * @return \c true if send successful, \c false otherwise.
445
     */
446
    bool setOutput(OutputChannel channel, uint16_t address, OutputValue value);
447

448
    /**
449
     * \brief Simulate input change
450
     * \param[in] address Input address, 1..4096
451
     * \param[in] action Simulation action to perform
452
     */
453
    void simulateInputChange(uint16_t address, SimulateInputAction action);
454

455
    void lncvStart(uint16_t moduleId, uint16_t moduleAddress);
456
    void lncvRead(uint16_t lncv);
457
    void lncvWrite(uint16_t lncv, uint16_t value);
458
    void lncvStop();
459
    void setOnLNCVReadResponse(OnLNCVReadResponse callback);
460
};
461

462
}
463

464
#endif
465

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