• 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

60.12
/server/src/hardware/output/outputcontroller.cpp
1
/**
2
 * This file is part of Traintastic,
3
 * see <https://github.com/traintastic/traintastic>.
4
 *
5
 * Copyright (C) 2021-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 "outputcontroller.hpp"
23
#include "singleoutput.hpp"
24
#include "pairoutput.hpp"
25
#include "aspectoutput.hpp"
26
#include "ecosstateoutput.hpp"
27
#include "list/outputlist.hpp"
28
#include "list/outputlisttablemodel.hpp"
29
#include "keyboard/singleoutputkeyboard.hpp"
30
#include "keyboard/pairoutputkeyboard.hpp"
31
#include "../protocol/dcc/dcc.hpp"
32
#include "../protocol/motorola/motorola.hpp"
33
#include "../../core/attributes.hpp"
34
#include "../../core/controllerlist.hpp"
35
#include "../../core/objectproperty.tpp"
36
#include "../../utils/contains.hpp"
37
#include "../../utils/displayname.hpp"
38
#include "../../utils/inrange.hpp"
39
#include "../../world/getworld.hpp"
40
#include "../../world/world.hpp"
41

42
namespace {
43

44
template<typename T>
45
requires(std::is_base_of_v<Output, T>)
46
std::shared_ptr<Output> createOutput(const std::shared_ptr<OutputController>& controller, OutputChannel channel, const OutputLocation& location)
8✔
47
{
48
  return
49
    std::visit(
50
      [&controller, channel](auto&& v) -> std::shared_ptr<Output>
16✔
51
      {
52
        using V = std::decay_t<decltype(v)>;
53
        if constexpr(std::is_same_v<V, OutputAddress>)
54
        {
55
          return std::make_shared<T>(controller, channel, std::nullopt, v.address);
8✔
56
        }
57
        if constexpr(std::is_same_v<V, OutputNodeAddress>)
58
        {
UNCOV
59
          return std::make_shared<T>(controller, channel, v.node, v.address);
×
60
        }
UNCOV
61
        assert(false);
×
62
        return {};
63
      }, location);
16✔
64
}
65

66
}
67

68
OutputController::OutputController(IdObject& interface)
58✔
69
  : outputs{&interface, "outputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject}
58✔
70
{
71
  Attributes::addDisplayName(outputs, DisplayName::Hardware::outputs);
58✔
72
}
58✔
73

74
OutputType OutputController::outputType(OutputChannel channel) const
168✔
75
{
76
  switch(channel)
168✔
77
  {
78
    case OutputChannel::Output:
19✔
79
    case OutputChannel::Turnout:
80
    case OutputChannel::LongEvent:
81
    case OutputChannel::ShortEvent:
82
      return OutputType::Single;
19✔
83

84
    case OutputChannel::Accessory:
106✔
85
    case OutputChannel::AccessoryDCC:
86
    case OutputChannel::AccessoryMotorola:
87
      return OutputType::Pair;
106✔
88

89
    case OutputChannel::DCCext:
27✔
90
      return OutputType::Aspect;
27✔
91

92
    case OutputChannel::ECoSObject:
16✔
93
      return OutputType::ECoSState;
16✔
94
  }
UNCOV
95
  assert(false);
×
96
  return static_cast<OutputType>(0);
97
}
98

UNCOV
99
std::pair<uint32_t, uint32_t> OutputController::outputNodeMinMax(OutputChannel /*channel*/) const
×
100
{
UNCOV
101
  return {0, 0};
×
102
}
103

104
std::pair<uint32_t, uint32_t> OutputController::outputAddressMinMax(OutputChannel channel) const
18✔
105
{
106
  // Handle standard ranges, other or limited ranges must be implemented in the interface.
107
  switch(channel)
18✔
108
  {
109
    case OutputChannel::AccessoryDCC:
13✔
110
    case OutputChannel::DCCext:
111
      return {DCC::Accessory::addressMin, DCC::Accessory::addressMax};
13✔
112

113
    case OutputChannel::AccessoryMotorola:
5✔
114
      return {Motorola::Accessory::addressMin, Motorola::Accessory::addressMax};
5✔
115

UNCOV
116
    case OutputChannel::Output:
×
117
    case OutputChannel::Accessory:
118
    case OutputChannel::Turnout:
119
    case OutputChannel::LongEvent:
120
    case OutputChannel::ShortEvent:
UNCOV
121
      break;
×
122

UNCOV
123
    case OutputChannel::ECoSObject:
×
UNCOV
124
      return noAddressMinMax;
×
125
  }
126
  assert(false);
×
127
  return {0, 0};
128
}
129

UNCOV
130
std::pair<std::span<const uint16_t>, std::span<const std::string>> OutputController::getOutputECoSObjects(OutputChannel /*channel*/) const
×
131
{
132
  assert(false);
×
133
  return {{}, {}};
134
}
135

136
bool OutputController::isOutputChannel(OutputChannel channel) const
146✔
137
{
138
  return contains(outputChannels(), channel);
146✔
139
}
140

141
bool OutputController::isOutputLocation(OutputChannel channel, const OutputLocation& location) const
8✔
142
{
143
  switch(channel)
8✔
144
  {
145
    case OutputChannel::AccessoryDCC:
8✔
146
    case OutputChannel::DCCext:
147
    case OutputChannel::AccessoryMotorola:
148
    case OutputChannel::Output:
149
    case OutputChannel::Accessory:
150
    case OutputChannel::Turnout:
151
    case OutputChannel::LongEvent:
152
    case OutputChannel::ShortEvent:
153
      if(hasNode(channel))
8✔
154
      {
155
        return inRange(std::get<OutputNodeAddress>(location).address, outputAddressMinMax(channel));
×
156
      }
157
      return inRange(std::get<OutputAddress>(location).address, outputAddressMinMax(channel));
8✔
158

UNCOV
159
    case OutputChannel::ECoSObject:
×
UNCOV
160
      assert(false);
×
161
      break;
162
  }
UNCOV
163
  return false;
×
164
}
165

UNCOV
166
bool OutputController::isOutputAvailable(OutputChannel channel, const OutputLocation& location) const
×
167
{
UNCOV
168
  assert(isOutputChannel(channel));
×
169
  return
170
    isOutputLocation(channel, location) &&
×
171
    m_outputs.find({channel, location}) == m_outputs.end();
×
172
}
173

174
uint32_t OutputController::getUnusedOutputAddress(OutputChannel channel) const
4✔
175
{
176
  assert(isOutputChannel(channel));
4✔
177
  const auto end = m_outputs.cend();
4✔
178
  const auto range = outputAddressMinMax(channel);
4✔
179
  for(uint32_t address = range.first; address < range.second; address++)
4✔
180
    if(m_outputs.find({channel, OutputAddress(address)}) == end)
4✔
181
      return address;
4✔
UNCOV
182
  return AddressOutput::invalidAddress;
×
183
}
184

185
std::shared_ptr<Output> OutputController::getOutput(OutputChannel channel, const OutputLocation& location, Object& usedBy)
9✔
186
{
187
  if(!isOutputChannel(channel) || !isOutputLocation(channel, location))
9✔
188
  {
UNCOV
189
    return {};
×
190
  }
191

192
  // Check if already exists:
193
  if(auto it = m_outputs.find({channel, location}); it != m_outputs.end())
9✔
194
  {
UNCOV
195
    it->second->m_usedBy.emplace(usedBy.shared_from_this());
×
UNCOV
196
    return it->second;
×
197
  }
198

199
  // Create new output:
200
  std::shared_ptr<Output> output;
9✔
201
  switch(outputType(channel))
9✔
202
  {
UNCOV
203
    case OutputType::Single:
×
UNCOV
204
      output = createOutput<SingleOutput>(shared_ptr(), channel, location);
×
UNCOV
205
      break;
×
206

207
    case OutputType::Pair:
8✔
208
      output = createOutput<PairOutput>(shared_ptr(), channel, location);
8✔
209
      break;
8✔
210

UNCOV
211
    case OutputType::Aspect:
×
UNCOV
212
      output = createOutput<AspectOutput>(shared_ptr(), channel, location);
×
UNCOV
213
      break;
×
214

215
    case OutputType::ECoSState:
1✔
216
      output = std::make_shared<ECoSStateOutput>(shared_ptr(), channel, std::get<OutputECoSObject>(location).object);
1✔
217
      break;
1✔
218
  }
219
  assert(output);
9✔
220
  output->m_usedBy.emplace(usedBy.shared_from_this());
9✔
221
  m_outputs.emplace(OutputMapKey{channel, location}, output);
9✔
222
  outputs->addObject(output);
9✔
223
  getWorld(outputs.object()).outputs->addObject(output);
9✔
224

225
  if(auto keyboard = m_outputKeyboards[channel].lock())
9✔
226
  {
UNCOV
227
    keyboard->fireOutputUsedChanged(std::get<OutputAddress>(location).address, true);
×
228
  }
9✔
229

230
  return output;
9✔
231
}
9✔
232

233
void OutputController::releaseOutput(Output& output, Object& usedBy)
9✔
234
{
235
  auto outputShared = output.shared_ptr<Output>();
9✔
236
  output.m_usedBy.erase(usedBy.shared_from_this());
9✔
237
  if(output.m_usedBy.empty())
9✔
238
  {
239
    const auto channel = output.channel.value();
9✔
240
    const auto location = output.location();
9✔
241

242
    m_outputs.erase({channel, location});
9✔
243
    outputs->removeObject(outputShared);
9✔
244
    getWorld(outputs.object()).outputs->removeObject(outputShared);
9✔
245
    outputShared->destroy();
9✔
246
    outputShared.reset();
9✔
247

248
    if(auto keyboard = m_outputKeyboards[channel].lock())
9✔
249
    {
UNCOV
250
      assert(std::holds_alternative<OutputAddress>(location));
×
UNCOV
251
      keyboard->fireOutputUsedChanged(std::get<OutputAddress>(location).address, false);
×
252
    }
9✔
253
  }
254
}
9✔
255

UNCOV
256
void OutputController::updateOutputValue(OutputChannel channel, const OutputLocation& location, OutputValue value)
×
257
{
UNCOV
258
  assert(isOutputChannel(channel));
×
UNCOV
259
  if(auto it = m_outputs.find({channel, location}); it != m_outputs.end())
×
260
  {
UNCOV
261
    if(auto* single = dynamic_cast<SingleOutput*>(it->second.get()))
×
262
    {
263
      single->updateValue(std::get<TriState>(value));
×
264
    }
UNCOV
265
    else if(auto* pair = dynamic_cast<PairOutput*>(it->second.get()))
×
266
    {
267
      pair->updateValue(std::get<OutputPairValue>(value));
×
268
    }
269
    else if(auto* aspect = dynamic_cast<AspectOutput*>(it->second.get()))
×
270
    {
271
      aspect->updateValue(std::get<int16_t>(value));
×
272
    }
273
    else if(auto* ecosState = dynamic_cast<ECoSStateOutput*>(it->second.get()))
×
274
    {
275
      ecosState->updateValue(std::get<uint8_t>(value));
×
276
    }
277
  }
278

279
  if(auto keyboard = m_outputKeyboards[channel].lock())
×
280
  {
281
    assert(std::holds_alternative<OutputAddress>(location));
×
UNCOV
282
    keyboard->fireOutputValueChanged(std::get<OutputAddress>(location).address, value);
×
283
  }
×
UNCOV
284
}
×
285

286
bool OutputController::hasOutputKeyboard(OutputChannel channel) const
131✔
287
{
288
  assert(isOutputChannel(channel));
131✔
289
  switch(outputType(channel))
131✔
290
  {
291
    case OutputType::Single:
93✔
292
    case OutputType::Pair:
293
      return true;
93✔
294

295
    case OutputType::Aspect:
38✔
296
    case OutputType::ECoSState:
297
      return false;
38✔
298
  }
UNCOV
299
  assert(false);
×
300
  return false;
301
}
302

UNCOV
303
std::shared_ptr<OutputKeyboard> OutputController::outputKeyboard(OutputChannel channel)
×
304
{
UNCOV
305
  assert(isOutputChannel(channel));
×
UNCOV
306
  auto keyboard = m_outputKeyboards[channel].lock();
×
UNCOV
307
  if(!keyboard)
×
308
  {
UNCOV
309
    switch(outputType(channel))
×
310
    {
UNCOV
311
      case OutputType::Single:
×
UNCOV
312
        keyboard = std::make_shared<SingleOutputKeyboard>(*this, channel);
×
UNCOV
313
        break;
×
314

UNCOV
315
      case OutputType::Pair:
×
UNCOV
316
        keyboard = std::make_shared<PairOutputKeyboard>(*this, channel);
×
UNCOV
317
        break;
×
318

UNCOV
319
      case OutputType::Aspect: /*[[unlikely]]*/
×
320
      case OutputType::ECoSState: /*[[unlikely]]*/
UNCOV
321
        break; // not supported (yet)
×
322
    }
UNCOV
323
    assert(keyboard);
×
UNCOV
324
    m_outputKeyboards[channel] = keyboard;
×
325
  }
UNCOV
326
  return keyboard;
×
UNCOV
327
}
×
328

329
void OutputController::addToWorld(OutputListColumn columns)
58✔
330
{
331
  auto& object = interface();
58✔
332
  outputs.setValueInternal(std::make_shared<OutputList>(object, outputs.name(), columns));
58✔
333
  object.world().outputControllers->add(std::dynamic_pointer_cast<OutputController>(object.shared_from_this()));
58✔
334
}
58✔
335

336
void OutputController::destroying()
58✔
337
{
338
  auto& object = interface();
58✔
339
  while(!outputs->empty())
62✔
340
  {
341
    const auto& output = outputs->front();
4✔
342
    assert(output->interface.value() == std::dynamic_pointer_cast<OutputController>(object.shared_from_this()));
4✔
343
    output->interface.setValueInternal(nullptr);
4✔
344
    outputs->removeObject(output);
4✔
345
  }
346
  object.world().outputControllers->remove(std::dynamic_pointer_cast<OutputController>(object.shared_from_this()));
58✔
347
}
58✔
348

349
IdObject& OutputController::interface()
116✔
350
{
351
  auto* object = dynamic_cast<IdObject*>(this);
116✔
352
  assert(object);
116✔
353
  return *object;
116✔
354
}
355

356
std::shared_ptr<OutputController> OutputController::shared_ptr()
9✔
357
{
358
  auto self = std::dynamic_pointer_cast<OutputController>(outputs.object().shared_from_this());
9✔
359
  assert(self);
9✔
360
  return self;
9✔
361
}
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