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

traintastic / traintastic / 23769213849

30 Mar 2026 09:43PM UTC coverage: 25.799% (-0.07%) from 25.87%
23769213849

push

github

reinder
[cbus] request all used short/long event during interface initialization

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

11 existing lines in 3 files now uncovered.

8256 of 32001 relevant lines covered (25.8%)

179.77 hits per line

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

46.01
/server/src/hardware/input/inputconsumer.cpp
1
/**
2
 * This file is part of Traintastic,
3
 * see <https://github.com/traintastic/traintastic>.
4
 *
5
 * Copyright (C) 2025-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 "inputconsumer.hpp"
23
#include "input.hpp"
24
#include "inputcontroller.hpp"
25
#include "../../core/attributes.hpp"
26
#include "../../core/eventloop.hpp"
27
#include "../../core/objectproperty.tpp"
28
#include "../../log/log.hpp"
29
#include "../../utils/category.hpp"
30
#include "../../utils/displayname.hpp"
31
#include "../../utils/inrange.hpp"
32
#include "../../utils/valuestep.hpp"
33
#include "../../world/world.hpp"
34

35
namespace {
36

37
bool roundToDelayStep(uint16_t& value)
×
38
{
39
  value = valueStepRound(value, InputConsumer::delayStep);
×
40
  return true;
×
41
}
42

43
}
44

45
InputConsumer::InputConsumer(Object& object, const World& world)
16✔
46
  : m_object{object}
16✔
47
  , m_inputFilterTimer{EventLoop::ioContext()}
16✔
48
  , interface{&object, "interface", nullptr, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript,
32✔
49
      [this](const std::shared_ptr<InputController>& /*newValue*/)
16✔
50
      {
51
        interfaceChanged();
×
52
      },
×
53
      [this](const std::shared_ptr<InputController>& newValue)
32✔
54
      {
55
        if(interface.value())
×
56
        {
57
          releaseInput();
×
58
        }
59

60
        if(newValue)
×
61
        {
62
          if(!newValue->isInputChannel(channel))
×
63
          {
64
            channel.setValueInternal(newValue->inputChannels().front());
×
65
          }
66

67
          if(auto addressMinMax = newValue->inputAddressMinMax(channel); !inRange(address.value(), addressMinMax))
×
68
          {
69
            const auto addr = newValue->getUnusedInputAddress(channel);
×
70
            address.setValueInternal(addr ? *addr : addressMinMax.first);
×
71
          }
72

73
          setInput(newValue->getInput(channel, inputLocation(channel, node, address), m_object));
×
74
        }
75

76
        return true;
×
77
      }}
78
  , channel{&object, "channel", InputChannel::Input, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript,
32✔
79
      [this](const InputChannel& /*newValue*/)
16✔
80
      {
81
        channelChanged();
×
82
      },
×
83
      [this](const InputChannel& newValue)
32✔
84
      {
85
        if(auto obj = interface->getInput(newValue, inputLocation(newValue, node, address), m_object))
×
86
        {
87
          setInput(obj);
×
88
          return true;
×
89
        }
×
90
        return false;
×
91
      }}
92
  , node{&object, "node", 0, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript,
32✔
93
      nullptr,
94
      [this](uint32_t& newValue)
16✔
95
      {
96
        if(auto obj = interface->getInput(channel, inputLocation(channel, newValue, address), m_object))
×
97
        {
98
          setInput(obj);
×
99
          return true;
×
100
        }
×
101
        return false;
×
102
      }}
103
  , address{&object, "address", 0, PropertyFlags::ReadWrite | PropertyFlags::Store | PropertyFlags::NoScript, nullptr,
32✔
104
      [this](const uint32_t& newValue)
16✔
105
      {
106
        if(auto obj = interface->getInput(channel, inputLocation(channel, node, newValue), m_object))
×
107
        {
108
          setInput(obj);
×
109
          return true;
×
110
        }
×
111
        return false;
×
112
      }}
113
  , onDelay{&object, "on_delay", delayMin, PropertyFlags::ReadWrite | PropertyFlags::Store, nullptr, roundToDelayStep}
16✔
114
  , offDelay{&object, "off_delay", delayMin, PropertyFlags::ReadWrite | PropertyFlags::Store, nullptr, roundToDelayStep}
48✔
115
{
116
  const auto worldState = world.state.value();
16✔
117
  const bool editable = contains(worldState, WorldState::Edit);
16✔
118
  const bool editableAndStopped = editable && !contains(worldState, WorldState::Run);
16✔
119

120
  Attributes::addCategory(interface, Category::input);
16✔
121
  Attributes::addDisplayName(interface, DisplayName::Hardware::interface);
16✔
122
  Attributes::addEnabled(interface, editableAndStopped);
16✔
123
  Attributes::addObjectList(interface, world.inputControllers);
16✔
124

125
  Attributes::addCategory(channel, Category::input);
16✔
126
  Attributes::addDisplayName(channel, DisplayName::Hardware::channel);
16✔
127
  Attributes::addEnabled(channel, editableAndStopped);
16✔
128
  Attributes::addValues(channel, std::span<const InputChannel>());
16✔
129
  Attributes::addVisible(channel, false);
16✔
130

131
  Attributes::addCategory(node, Category::input);
16✔
132
  Attributes::addDisplayName(node, DisplayName::Hardware::node);
16✔
133
  Attributes::addEnabled(node, editableAndStopped);
16✔
134
  Attributes::addVisible(node, false);
16✔
135

136
  Attributes::addCategory(address, Category::input);
16✔
137
  Attributes::addDisplayName(address, DisplayName::Hardware::address);
16✔
138
  Attributes::addEnabled(address, editableAndStopped);
16✔
139
  Attributes::addVisible(address, false);
16✔
140
  Attributes::addMinMax<uint32_t>(address, Input::addressMinDefault, Input::addressMaxDefault);
16✔
141

142
  Attributes::addCategory(onDelay, Category::input);
16✔
143
  Attributes::addDisplayName(onDelay, "input:on_delay");
16✔
144
  Attributes::addEnabled(onDelay, editable);
16✔
145
  Attributes::addMinMax(onDelay, delayMin, delayMax);
16✔
146
  Attributes::addStep(onDelay, delayStep);
16✔
147
  Attributes::addUnit(onDelay, delayUnit);
16✔
148
  Attributes::addVisible(onDelay, false);
16✔
149

150
  Attributes::addCategory(offDelay, Category::input);
16✔
151
  Attributes::addDisplayName(offDelay, "input:off_delay");
16✔
152
  Attributes::addEnabled(offDelay, editable);
16✔
153
  Attributes::addMinMax(offDelay, delayMin, delayMax);
16✔
154
  Attributes::addStep(offDelay, delayStep);
16✔
155
  Attributes::addUnit(offDelay, delayUnit);
16✔
156
  Attributes::addVisible(offDelay, false);
16✔
157
}
16✔
158

159
InputConsumer::~InputConsumer()
16✔
160
{
161
  releaseInput();
16✔
162
}
16✔
163

164
void InputConsumer::loaded()
×
165
{
166
  if(interface)
×
167
  {
168
    if(auto object = interface->getInput(channel, inputLocation(channel, node, address), m_object))
×
169
    {
170
      setInput(object);
×
171
      interfaceChanged();
×
172
    }
173
    else
174
    {
175
      interface.setValueInternal(nullptr);
×
176
      //! \todo log warning
177
    }
×
178
  }
179
}
×
180

181
void InputConsumer::worldEvent(WorldState worldState, WorldEvent /*worldEvent*/)
14✔
182
{
183
  const bool editable = contains(worldState, WorldState::Edit);
14✔
184
  const bool editableAndStopped = editable && !contains(worldState, WorldState::Run);
14✔
185

186
  Attributes::setEnabled(interface, editableAndStopped);
14✔
187
  Attributes::setEnabled(channel, editableAndStopped);
14✔
188
  Attributes::setEnabled(node, editableAndStopped);
14✔
189
  Attributes::setEnabled(address, editableAndStopped);
14✔
190
  Attributes::setEnabled(onDelay, editable);
14✔
191
  Attributes::setEnabled(offDelay, editable);
14✔
192
}
14✔
193

194
void InputConsumer::addInterfaceItems(InterfaceItems& items)
16✔
195
{
196
  items.add(interface);
16✔
197
  items.add(channel);
16✔
198
  items.add(node);
16✔
199
  items.add(address);
16✔
200
  items.add(onDelay);
16✔
201
  items.add(offDelay);
16✔
202
}
16✔
203

204
void InputConsumer::setInput(std::shared_ptr<Input> value)
×
205
{
206
  releaseInput();
×
207
  assert(!m_input);
×
208
  m_input = value;
×
209
  if(m_input)
×
210
  {
211
    m_inputDestroying = m_input->onDestroying.connect(
×
212
      [this](Object& object)
×
213
      {
214
        (void)object; // silence unused warning
215
        assert(m_input.get() == &object);
×
216
        interface.setValue(nullptr);
×
217
      });
×
218
    m_inputValueChanged = m_input->onValueChanged.connect(
×
219
      [this](bool inputValue, const std::shared_ptr<Input>& /*input*/)
×
220
      {
221
        try
222
        {
223
          m_inputFilterTimer.cancel();
×
224
        }
225
        catch(...)
×
226
        {
227
        }
×
228
        m_inputFilterTimer.expires_after(std::chrono::milliseconds(inputValue ? onDelay.value() : offDelay.value()));
×
229
        m_inputFilterTimer.async_wait(
×
230
          [this, inputValue](const boost::system::error_code& ec)
×
231
          {
232
            if(ec == boost::asio::error::operation_aborted)
×
233
              return;
×
234

235
            inputValueChanged(inputValue, m_input);
×
236
          });
237
      });
×
238

239
    if(m_input->value != TriState::Undefined)
×
240
    {
241
      inputValueChanged(m_input->value == TriState::True, m_input);
×
242
    }
243
  }
244
}
×
245

246
void InputConsumer::releaseInput()
16✔
247
{
248
  if(m_input)
16✔
249
  {
250
    try
251
    {
252
      m_inputFilterTimer.cancel();
×
253
    }
254
    catch(...)
×
255
    {
256
    }
×
257
    m_inputDestroying.disconnect();
×
258
    m_inputValueChanged.disconnect();
×
259
    if(m_input->interface)
×
260
    {
261
      m_input->interface->releaseInput(*m_input, m_object);
×
262
    }
263
    m_input.reset();
×
264
  }
265
}
16✔
266

267
void InputConsumer::interfaceChanged()
×
268
{
269
  Attributes::setValues(channel, interface ? interface->inputChannels() : std::span<const InputChannel>());
×
270
  Attributes::setVisible(channel, interface && interface->inputChannels().size() > 1);
×
271
  Attributes::setVisible({address, offDelay, onDelay}, interface);
×
272

273
  channelChanged();
×
274
}
×
275

276
void InputConsumer::channelChanged()
×
277
{
278
  if(interface)
×
279
  {
280
    Attributes::setVisible(node, hasNodeAddressLocation(channel));
×
281
    const auto limits = interface->inputAddressMinMax(channel);
×
282
    Attributes::setDisplayName(address, addressIsEvent(channel) ? DisplayName::Hardware::event : DisplayName::Hardware::address);
×
UNCOV
283
    Attributes::setMinMax(address, limits.first, limits.second);
×
284
  }
285
  else
286
  {
287
    Attributes::setVisible(node, false);
×
UNCOV
288
    Attributes::setMinMax(address, Input::addressMinDefault, Input::addressMaxDefault);
×
289
  }
UNCOV
290
}
×
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