• 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/withrottle/kernel.cpp
1
/**
2
 * server/src/hardware/protocol/withrottle/kernel.cpp
3
 *
4
 * This file is part of the traintastic source code.
5
 *
6
 * Copyright (C) 2022-2023,2025 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 "kernel.hpp"
24
#include <traintastic/enum/decoderprotocol.hpp>
25
#include "messages.hpp"
26
#include "../../decoder/decoder.hpp" // TODO: remove when migrated to Train control
27
#include "../../interface/interface.hpp"
28
#include "../../../throttle/hardwarethrottle.hpp"
29
#include "../../../throttle/throttlecontroller.hpp"
30
#include "../../../core/eventloop.hpp"
31
#include "../../../core/method.tpp"
32
#include "../../../core/objectproperty.tpp"
33
#include "../../../clock/clock.hpp"
34
#include "../../../log/log.hpp"
35
#include "../../../log/logmessageexception.hpp"
36
#include "../../../train/train.hpp"
37
#include "../../../train/trainvehiclelist.hpp"
38
#include "../../../utils/fromchars.hpp"
39
#include "../../../utils/setthreadname.hpp"
40
#include "../../../utils/startswith.hpp"
41

42
namespace WiThrottle {
43

44
struct Address
45
{
46
  uint16_t address;
47
  bool isLong;
48
  bool isWildcard;
49

50
  constexpr bool operator !=(const Address& other) const noexcept
×
51
  {
52
    return
53
      (address != other.address) ||
×
54
      (isLong != other.isLong) ||
×
55
      (isWildcard != other.isWildcard);
×
56
  }
57
};
58

59
static bool parseAddress(std::string_view& sv, Address& address)
×
60
{
61
  if(sv.empty() || (sv[0] != 'S' && sv[0] != 'L' && sv[0] != '*'))
×
62
    return false;
×
63

64
  if(sv[0] == '*')
×
65
  {
66
    address.address = 0;
×
67
    address.isLong = false;
×
68
    address.isWildcard = true;
×
69
    sv = sv.substr(1);
×
70
    return true;
×
71
  }
72

73
  auto r = fromChars(sv.substr(1), address.address);
×
74
  if(r.ec != std::errc())
×
75
    return false;
×
76

77
  address.isLong = (sv[0] == 'L');
×
78
  address.isWildcard = false;
×
79
  sv = sv.substr(r.ptr - sv.data());
×
80

81
  return true;
×
82
}
83

84
static std::string buildName(std::string name, char multiThrottleId)
×
85
{
86
  if(multiThrottleId != Kernel::invalidMultiThrottleId)
×
87
  {
88
    name.append(" [");
×
89
    name.push_back(multiThrottleId);
×
90
    name.push_back(']');
×
91
  }
92
  return name;
×
93
}
94

95
Kernel::Kernel(std::string logId_, const Config& config)
×
96
  : KernelBase(std::move(logId_))
×
97
  , m_config{config}
×
98
{
99
  assert(isEventLoopThread());
×
100
}
×
101

102
void Kernel::setConfig(const Config& config)
×
103
{
NEW
104
  boost::asio::post(m_ioContext, 
×
105
    [this, newConfig=config]()
×
106
    {
107
      m_config = newConfig;
×
108
    });
×
109
}
×
110

111
void Kernel::setClock(std::shared_ptr<Clock> clock)
×
112
{
113
  assert(isEventLoopThread());
×
114
  assert(!m_running);
×
115
  assert(clock);
×
116
  m_clock = std::move(clock);
×
117
}
×
118

119
void Kernel::start()
×
120
{
121
  assert(isEventLoopThread());
×
122
  assert(m_ioHandler);
×
123
  assert(!m_running);
×
124

125
  m_running = true;
×
126

127
  m_powerOn = TriState::Undefined;
×
128

129
  m_thread = std::thread(
×
130
    [this]()
×
131
    {
132
      setThreadName("withrottle");
×
NEW
133
      boost::asio::executor_work_guard<decltype(m_ioContext.get_executor())> work{m_ioContext.get_executor()};
×
134
      m_ioContext.run();
×
135
    });
×
136

137
  if(m_clock)
×
138
    m_clockChangeConnection = m_clock->onChange.connect(
×
139
      [this](Clock::ClockEvent event, uint8_t multiplier, Time time)
×
140
      {
141
        postSendToAll(fastClock((time.hour() * 60U + time.minute()) * 60U, (event == Clock::ClockEvent::Freeze) ? 0 : multiplier));
×
142
      });
×
143

NEW
144
  boost::asio::post(m_ioContext, 
×
145
    [this]()
×
146
    {
147
      try
148
      {
149
        m_ioHandler->start();
×
150
      }
151
      catch(const LogMessageException& e)
×
152
      {
153
        EventLoop::call(
×
154
          [this, e]()
×
155
          {
156
            Log::log(logId, e.message(), e.args());
×
157
            error();
×
158
          });
×
159
        return;
×
160
      }
×
161

162
      started();
×
163
    });
164
}
×
165

166
void Kernel::stop()
×
167
{
168
  assert(isEventLoopThread());
×
169

170
  m_running = false;
×
171

172
  m_clockChangeConnection.disconnect();
×
173

174
  // remove all clients:
175
  auto itClient = m_clients.begin();
×
176
  while(itClient != m_clients.end())
×
177
  {
178
    auto& multiThrottles = itClient->second.multiThrottles;
×
179
    auto itMultiThrottle = multiThrottles.begin();
×
180
    while(itMultiThrottle != multiThrottles.end())
×
181
    {
182
      itMultiThrottle->second.address = 0; // don't try send a release to the client anymore
×
183
      itMultiThrottle->second.throttle->destroy();
×
184
      itMultiThrottle = multiThrottles.erase(itMultiThrottle);
×
185
    }
186
    itClient = m_clients.erase(itClient);
×
187
  }
188

189
  // stop iohandler and kernel thread:
NEW
190
  boost::asio::post(m_ioContext, 
×
191
    [this]()
×
192
    {
193
      m_ioHandler->stop();
×
194
      m_ioContext.stop();
×
195
    });
×
196

197
  m_thread.join();
×
198
}
×
199

200
void Kernel::setPowerOn(bool on)
×
201
{
202
  assert(isEventLoopThread());
×
203

NEW
204
  boost::asio::post(m_ioContext, 
×
205
    [this, on]()
×
206
    {
207
      if(m_powerOn != toTriState(on))
×
208
      {
209
        sendToAll(trackPower(on));
×
210
        m_powerOn = toTriState(on);
×
211
      }
212
    });
×
213
}
×
214

215
void Kernel::newClient(IOHandler::ClientId clientId)
×
216
{
217
  assert(isKernelThread());
×
218
  assert(m_running);
×
219

220
  sendTo(protocolVersion(), clientId);
×
221
  sendTo(serverType(), clientId);
×
222
  sendTo(serverVersion(), clientId);
×
223
  sendTo(rosterList({}), clientId);
×
224
  sendTo(trackPower(m_powerOn), clientId);
×
225

226
  EventLoop::call(
×
227
    [this, clientId]()
×
228
    {
229
      postSendTo(fastClock((m_clock->hour * 60U + m_clock->minute) * 60U, m_clock->running ? m_clock->multiplier : 0), clientId);
×
230

231
      if(auto* interface = dynamic_cast<Interface*>(m_throttleController))
×
232
      {
233
        auto throttle = HardwareThrottle::create(std::dynamic_pointer_cast<ThrottleController>(interface->shared_ptr<Interface>()), interface->world());
×
234
        auto [it, success] = m_clients.emplace(clientId, Client());
×
235
        assert(success);
×
236
        it->second.multiThrottles.emplace(invalidMultiThrottleId, MultiThrottle{0, false, std::move(throttle)});
×
237
      }
×
238
    });
×
239
}
×
240

241
void Kernel::clientGone(IOHandler::ClientId clientId)
×
242
{
243
  assert(isKernelThread());
×
244

245
  if(!m_running)
×
246
    return;
×
247

248
  EventLoop::call(
×
249
    [this, clientId]()
×
250
    {
251
      if(auto itClient = m_clients.find(clientId); itClient != m_clients.end())
×
252
      {
253
        for(auto& itMultiThrottle : itClient->second.multiThrottles)
×
254
        {
255
          const auto& throttle = itMultiThrottle.second.throttle;
×
256
          if(m_throttleController->removeThrottle(*throttle))
×
257
            throttle->destroy();
×
258
          else
259
            assert(false);
×
260
        }
261
        m_clients.erase(itClient);
×
262
      }
263
    });
×
264
}
265

266
void Kernel::receiveFrom(std::string_view message, IOHandler::ClientId clientId)
×
267
{
268
  assert(isKernelThread());
×
269
  assert(m_running);
×
270

271
  if(m_config.debugLogRXTX)
×
272
    EventLoop::call(
×
273
      [this, clientId, msg=std::string(message)]()
×
274
      {
275
        Log::log(logId, LogMessage::D2005_X_RX_X, clientId, msg);
×
276
      });
×
277

278
  if(message[0] == 'M') // Multi throttle command
×
279
  {
280
    if(message.size() >= 3)
×
281
    {
282
      static constexpr std::string_view seperator{"<;>"};
283

284
      [[maybe_unused]] const char multiThrottleId = message[1];
×
285
      const char command = message[2];
×
286
      message = message.substr(3);
×
287

288
      // address
289
      Address address;
290
      if(!parseAddress(message, address))
×
291
        return;
×
292

293
      // seperator
294
      if(!startsWith(message, seperator))
×
295
        return;
×
296
      message = message.substr(seperator.size());
×
297

298
      switch(command)
×
299
      {
300
        case 'A': // action
×
301
        {
302
          const auto throttleCommand = static_cast<ThrottleCommand>(message[0]);
×
303
          multiThrottleAction(clientId, multiThrottleId, address, throttleCommand, message.substr(1));
×
304
          break;
×
305
        }
306
        case '+': // add locomotive
×
307
        case 'S': // steal locomotive
308
        {
309
          if(address.isWildcard)
×
310
            return;
×
311

312
          Address addressRepeat;
313
          if(!parseAddress(message, addressRepeat) || !message.empty() || address != addressRepeat)
×
314
            return;
×
315

316
          EventLoop::call(
×
317
            [this, clientId, multiThrottleId, address, steal=(command == 'S')]()
×
318
            {
319
              const auto& throttle = getThottle(clientId, multiThrottleId);
×
320
              if(!throttle)
×
321
                return;
×
322

323
              const auto ec = throttle->acquire(address.isLong ? DecoderProtocol::DCCLong : DecoderProtocol::DCCShort, address.address, steal);
×
324
              if(!ec)
×
325
              {
326
                if(auto* multiThrottle = getMultiThrottle(clientId, multiThrottleId))
×
327
                {
328
                  multiThrottle->address = address.address;
×
329
                  multiThrottle->isLongAddress = address.isLong;
×
330
                }
331
                else
332
                  assert(false);
×
333

334
                postSendTo(throttleCommand(multiThrottleId, '+', address.address, address.isLong), clientId);
×
335

336
                for(const auto& vehicle : *throttle->train->vehicles)
×
337
                {
338
                  if(vehicle->decoder && vehicle->decoder->address == address.address)
×
339
                  {
340
                    const auto& functions = vehicle->decoder->functions;
×
341

342
                    std::unordered_map<uint32_t, std::string_view> functionNames;
×
343
                    for(const auto& f : *functions)
×
344
                      functionNames.emplace(f->number.value(), f->name.value());
×
345
                    postSendTo(throttleFuctionNames(multiThrottleId, address.address, address.isLong, functionNames), clientId);
×
346

347
                    for(const auto& f : *functions)
×
348
                      postSendTo(throttleFunction(multiThrottleId, address.address, address.isLong, f->number, f->value), clientId);
×
349

350
                    break;
×
351
                  }
×
352
                }
353

354
                if(throttle->train->emergencyStop)
×
355
                  postSendTo(throttleEstop(multiThrottleId, address.address, address.isLong), clientId);
×
356
                else
357
                  postSendTo(throttleSpeed(multiThrottleId, address.address, address.isLong, std::round(throttle->train->speed.value() / throttle->train->speedMax.getValue(throttle->train->speed.unit()) * speedMax)), clientId);
×
358

359
                postSendTo(throttleDirection(multiThrottleId, address.address, address.isLong, throttle->train->direction), clientId);
×
360

361
                postSendTo(throttleSpeedStepMode(multiThrottleId, address.address, address.isLong, 128), clientId);
×
362
              }
363
              else
364
              {
365
                postSendTo(alert(ec.message()), clientId);
×
366
              }
367
            });
368

369
          break;
×
370
        }
371
        case '-':
×
372
        {
373
          EventLoop::call(
×
374
            [this, clientId, multiThrottleId, address]()
×
375
            {
376
              auto* multiThrottle = getMultiThrottle(clientId, multiThrottleId);
×
377
              if(multiThrottle && (address.isWildcard || (address.address == multiThrottle->address && address.isLong == multiThrottle->isLongAddress)))
×
378
              {
379
                multiThrottle->address = 0; // set address to zero so the release callback doesn't sent a release to.
×
380
                assert(multiThrottle->throttle);
×
381
                multiThrottle->throttle->release(false);
×
382

383
                // confirm release:
384
                // - WiThrottle needs this, else it won't release it.
385
                postSendTo(throttleRelease(multiThrottleId, address.address, address.isLong), clientId);
×
386
              }
387
            });
×
388
          break;
×
389
        }
390
      }
391
    }
392
  }
393
  else if(message == trackPowerOff() || message == trackPowerOn())
×
394
  {
395
    sendTo(alert("Track power control isn't allowed."), clientId);
×
396
    sendTo(trackPower(m_powerOn), clientId); // notify about current state
×
397
  }
398
  else if(message[0] == 'N') // throttle name
×
399
  {
400
    EventLoop::call(
×
401
      [this, clientId, name=std::string(message.substr(1))]()
×
402
      {
403
        if(auto itClient = m_clients.find(clientId); itClient != m_clients.end())
×
404
        {
405
          itClient->second.name = name;
×
406
          for(auto& itMultiThrottle : itClient->second.multiThrottles)
×
407
            itMultiThrottle.second.throttle->name.setValueInternal(buildName(name, itMultiThrottle.first));
×
408
        }
409
      });
×
410
  }
411
  else if(startsWith(message, "HU")) // throttle id
×
412
  {
413
    EventLoop::call(
×
414
      [this, clientId, id=std::string(message.substr(2))]()
×
415
      {
416
        if(auto itClient = m_clients.find(clientId); itClient != m_clients.end())
×
417
        {
418
          itClient->second.id = id;
×
419
        }
420
      });
×
421
  }
422
  else if(message == quit())
×
423
  {
424
    // post disconnect to finish current callback
NEW
425
    boost::asio::post(m_ioContext, 
×
426
      [this, clientId]()
×
427
      {
428
        m_ioHandler->disconnect(clientId);
×
429
      });
×
430
  }
431
}
432

433
void Kernel::setIOHandler(std::unique_ptr<IOHandler> handler)
×
434
{
435
  assert(isEventLoopThread());
×
436
  assert(handler);
×
437
  assert(!m_ioHandler);
×
438
  m_ioHandler = std::move(handler);
×
439
}
×
440

441
void Kernel::sendTo(std::string_view message, IOHandler::ClientId clientId)
×
442
{
443
  assert(isKernelThread());
×
444

445
  if(m_ioHandler->sendTo(message, clientId))
×
446
  {
447
    if(m_config.debugLogRXTX)
×
448
      EventLoop::call(
×
449
        [this, clientId, msg=std::string(message)]()
×
450
        {
451
          Log::log(logId, LogMessage::D2004_X_TX_X, clientId, msg);
×
452
        });
×
453
  }
454
}
×
455

456
void Kernel::sendToAll(std::string_view message)
×
457
{
458
  assert(isKernelThread());
×
459

460
  if(m_ioHandler->hasClients() && m_ioHandler->sendToAll(message))
×
461
  {
462
    if(m_config.debugLogRXTX)
×
463
      EventLoop::call(
×
464
        [this, msg=std::string(message)]()
×
465
        {
466
          Log::log(logId, LogMessage::D2001_TX_X, msg);
×
467
        });
×
468
  }
469
}
×
470

471
Kernel::MultiThrottle* Kernel::getMultiThrottle(IOHandler::ClientId clientId, char multiThrottleId)
×
472
{
473
  assert(isEventLoopThread());
×
474

475
  if(auto itClient = m_clients.find(clientId); itClient != m_clients.end())
×
476
  {
477
    auto& multiThrottles = itClient->second.multiThrottles;
×
478
    if(auto itMultiThrottle = multiThrottles.find(multiThrottleId); itMultiThrottle != multiThrottles.end())
×
479
      return &itMultiThrottle->second;
×
480
  }
481

482
  return nullptr;
×
483
}
484

485
const std::shared_ptr<HardwareThrottle>& Kernel::getThottle(IOHandler::ClientId clientId, char multiThrottleId)
×
486
{
487
  assert(isEventLoopThread());
×
488

489
  static const std::shared_ptr<HardwareThrottle> noThrottle;
×
490

491
  if(auto itClient = m_clients.find(clientId); itClient != m_clients.end())
×
492
  {
493
    auto& multiThrottles = itClient->second.multiThrottles;
×
494

495
    // check for multi throttle id:
496
    auto itMultiThrottle = multiThrottles.find(multiThrottleId);
×
497
    if(itMultiThrottle != multiThrottles.end())
×
498
      return itMultiThrottle->second.throttle;
×
499

500
    // check for invalid multi throttle id (multi throttle id is unknown until first throttle command):
501
    itMultiThrottle = multiThrottles.find(invalidMultiThrottleId);
×
502
    if(itMultiThrottle != multiThrottles.end())
×
503
    {
504
      auto n = multiThrottles.extract(invalidMultiThrottleId);
×
505
      n.key() = multiThrottleId;
×
506
      auto it = multiThrottles.insert(std::move(n)).position;
×
507
      const auto& throttle = it->second.throttle;
×
508
      throttle->name.setValueInternal(buildName(itClient->second.name, multiThrottleId));
×
509
      throttle->onRelease.connect(std::bind_front(&Kernel::throttleReleased, this, clientId, multiThrottleId));
×
510
      return throttle;
×
511
    }
×
512

513
    // create a new throttle:
514
    if(auto* interface = dynamic_cast<Interface*>(m_throttleController))
×
515
    {
516
      auto newThrottle = HardwareThrottle::create(std::dynamic_pointer_cast<ThrottleController>(interface->shared_ptr<Interface>()), interface->world());
×
517
      auto [it, success] = multiThrottles.emplace(multiThrottleId, MultiThrottle{0, false, std::move(newThrottle)});
×
518
      assert(success);
×
519
      const auto& throttle = it->second.throttle;
×
520
      throttle->name.setValueInternal(buildName(itClient->second.name, multiThrottleId));
×
521
      throttle->onRelease.connect(std::bind_front(&Kernel::throttleReleased, this, clientId, multiThrottleId));
×
522
      return throttle;
×
523
    }
×
524
  }
525

526
  return noThrottle;
×
527
}
528

529
const std::shared_ptr<Decoder>& Kernel::getDecoder(IOHandler::ClientId clientId, char multiThrottleId)
×
530
{
531
  assert(isEventLoopThread());
×
532

533
  static const std::shared_ptr<Decoder> noDecoder;
×
534

535
  const auto* multiThrottle = getMultiThrottle(clientId, multiThrottleId);
×
536

537
  if(multiThrottle && multiThrottle->throttle->acquired())
×
538
  {
539
    for(const auto& vehicle : *multiThrottle->throttle->train->vehicles)
×
540
    {
541
      if(vehicle->decoder && vehicle->decoder->address == multiThrottle->address)
×
542
      {
543
        return vehicle->decoder.value();
×
544
      }
545
    }
546
  }
547

548
  return noDecoder;
×
549
}
550

551
void Kernel::multiThrottleAction(IOHandler::ClientId clientId, char multiThrottleId, const Address& /*address*/, ThrottleCommand throttleCommand, std::string_view message)
×
552
{
553
  assert(isKernelThread());
×
554

555
  switch(throttleCommand)
×
556
  {
557
    case ThrottleCommand::SetSpeed:
×
558
    {
559
      int value;
560
      auto r = fromChars(message, value);
×
561
      if(r.ec == std::errc() && value <= speedMax)
×
562
      {
563
        EventLoop::call(
×
564
          [this, clientId, multiThrottleId, value]()
×
565
          {
566
            if(const auto& throttle = getThottle(clientId, multiThrottleId); throttle && throttle->acquired())
×
567
            {
568
              if(value < 0)
×
569
              {
570
                throttle->emergencyStop();
×
571
              }
572
              else
573
              {
574
                throttle->setSpeed(throttle->train->speedMax.value() * value / speedMax, throttle->train->speedMax.unit());
×
575
              }
576
            }
577
          });
×
578
      }
579
      break;
×
580
    }
581
    case ThrottleCommand::SetDirection:
×
582
    {
583
      int value;
584
      auto r = fromChars(message, value);
×
585
      if(r.ec == std::errc())
×
586
      {
587
        EventLoop::call(
×
588
          [this, clientId, multiThrottleId, value]()
×
589
          {
590
            if(const auto& throttle = getThottle(clientId, multiThrottleId); throttle && throttle->acquired())
×
591
            {
592
              throttle->setDirection((value == 0) ? Direction::Reverse : Direction::Forward);
×
593
            }
594
          });
×
595
      }
596
      break;
×
597
    }
598
    case ThrottleCommand::Function:
×
599
    case ThrottleCommand::ForceFunction:
600
    {
601
      const bool value = message[0] != '0';
×
602
      uint32_t number;
603
      auto r = fromChars(message.substr(1), number);
×
604
      if(r.ec == std::errc())
×
605
      {
606
        EventLoop::call(
×
607
          [this, clientId, multiThrottleId, number, force=(throttleCommand == ThrottleCommand::ForceFunction), value]()
×
608
          {
609
            if(const auto& decoder = getDecoder(clientId, multiThrottleId))
×
610
            {
611
              if(const auto& function = decoder->getFunction(number))
×
612
              {
613
                if(force) // set
×
614
                {
615
                  function->value = value;
×
616
                }
617
                else if(value) // press
×
618
                {
619
                  switch(function->type.value())
×
620
                  {
621
                    case DecoderFunctionType::Hold:
×
622
                    case DecoderFunctionType::Momentary:
623
                      function->value = false;
×
624
                      break;
×
625

626
                    case DecoderFunctionType::OnOff:
×
627
                      // toggle when button is pushed, do nothing on release
628
                      function->value = !function->value;
×
629
                      break;
×
630

631
                    case DecoderFunctionType::AlwaysOff:
×
632
                    case DecoderFunctionType::AlwaysOn:
633
                      break; // do nothing
×
634
                  }
635
                }
636
                else // release
637
                {
638
                  switch(function->type.value())
×
639
                  {
640
                    case DecoderFunctionType::Hold:
×
641
                    case DecoderFunctionType::Momentary:
642
                      function->value = false;
×
643
                      break;
×
644

645
                    case DecoderFunctionType::OnOff:
×
646
                    case DecoderFunctionType::AlwaysOff:
647
                    case DecoderFunctionType::AlwaysOn:
648
                      break; // do nothing
×
649
                  }
650
                }
651
              }
652
            }
653
          });
×
654
      }
655
      break;
×
656
    }
657
    case ThrottleCommand::Idle:
×
658
      EventLoop::call(
×
659
        [this, clientId, multiThrottleId]()
×
660
        {
661
          if(const auto& throttle = getThottle(clientId, multiThrottleId); throttle && throttle->acquired())
×
662
          {
663
            throttle->setSpeed(0.0, throttle->train->speed.unit());
×
664
          }
665
        });
×
666
      break;
×
667

668
    case ThrottleCommand::EmergencyStop:
×
669
      EventLoop::call(
×
670
        [this, clientId, multiThrottleId]()
×
671
        {
672
          if(const auto& throttle = getThottle(clientId, multiThrottleId); throttle && throttle->acquired())
×
673
          {
674
            throttle->emergencyStop();
×
675
          }
676
        });
×
677
      break;
×
678

679
    case ThrottleCommand::Query:
×
680
      if(message.size() == 1)
×
681
      {
682
        switch(message[0])
×
683
        {
684
          case 'V': // speed
×
685
            EventLoop::call(
×
686
              [this, clientId, multiThrottleId]()
×
687
              {
688
                if(const auto* multiThrottle = getMultiThrottle(clientId, multiThrottleId))
×
689
                {
690
                  const auto& train = *multiThrottle->throttle->train;
×
691
                  if(train.emergencyStop)
×
692
                    postSendTo(throttleEstop(multiThrottleId, multiThrottle->address, multiThrottle->isLongAddress), clientId);
×
693
                  else
694
                    postSendTo(throttleSpeed(multiThrottleId, multiThrottle->address, multiThrottle->isLongAddress, std::round(train.speed.value() / train.speedMax.getValue(train.speed.unit()) * speedMax)), clientId);
×
695
                }
696
              });
×
697
            break;
×
698

699
          case 'R': // direction
×
700
            EventLoop::call(
×
701
              [this, clientId, multiThrottleId]()
×
702
              {
703
                if(const auto* multiThrottle = getMultiThrottle(clientId, multiThrottleId))
×
704
                {
705
                  postSendTo(throttleDirection(multiThrottleId, multiThrottle->address, multiThrottle->isLongAddress, multiThrottle->throttle->train->direction), clientId);
×
706
                }
707
              });
×
708
            break;
×
709
        }
710
      }
711
      break;
×
712

713
    case ThrottleCommand::Consist:
×
714
    case ThrottleCommand::ConsistLeadFromRosterEntry:
715
    case ThrottleCommand::Dispatch:
716
    case ThrottleCommand::SetAddressFromRosterEntry:
717
    case ThrottleCommand::SetLongAddress:
718
    case ThrottleCommand::MomentaryFunction:
719
    case ThrottleCommand::Quit:
720
    case ThrottleCommand::Release:
721
    case ThrottleCommand::SetShortAddress:
722
    case ThrottleCommand::SetSpeedStepMode:
723
      // unsupported
724
      break;
×
725
  }
726
}
×
727

728
void Kernel::throttleReleased(IOHandler::ClientId clientId, char multiThrottleId, const std::shared_ptr<Throttle>& /*throttle*/)
×
729
{
730
  assert(isEventLoopThread());
×
731

732
  const auto* multiThrottle = getMultiThrottle(clientId, multiThrottleId);
×
733
  if(multiThrottle && multiThrottle->address != 0)
×
734
    postSendTo(throttleRelease(multiThrottleId, multiThrottle->address, multiThrottle->isLongAddress), clientId);
×
735
}
×
736

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