Open Model Railroad Network (OpenMRN)
Loading...
Searching...
No Matches
MultiConfiguredPC.hxx
Go to the documentation of this file.
1
36#ifndef _OPENLCB_MULTICONFIGUREDPC_HXX_
37#define _OPENLCB_MULTICONFIGUREDPC_HXX_
38
44#include "utils/Debouncer.hxx"
46
47namespace openlcb
48{
49
50static const char PC_ACTION_MAP[] =
51 "<relation><property>0</property><value>Output</value></relation>"
52 "<relation><property>1</property><value>Input</value></relation>";
53
54CDI_GROUP(PCLineConfig);
57 Name("Description"), Description("User name of this line."));
58
61 Name("Event On"),
62 Description("This event ID will turn the output on / be produced when the "
63 "input goes on."));
66 Name("Event Off"),
67 Description("This event ID will turn the output off / be produced when the "
68 "input goes off."));
70
72CDI_GROUP(PCConfig);
73enum class ActionConfig : uint8_t
74{
75 DOUTPUT = 0,
76 DINPUT = 1
77};
78
79CDI_GROUP_ENTRY(action, Uint8ConfigEntry, Default(1), MapValues(PC_ACTION_MAP),
80 Name("Configuration"));
81
83CDI_GROUP_ENTRY(debounce, Uint8ConfigEntry, Name("Debounce parameter"),
84 Default(3),
85 Description("Used for inputs only. Amount of time to wait for the input to "
86 "stabilize before "
87 "producing the event. Unit is 30 msec of time. Usually a value "
88 "of 2-3 works well in a non-noisy environment. In high noise "
89 "(train wheels for example) a setting between 8 -- 15 makes "
90 "for a slower response time but a more stable "
91 "signal.\nFormally, the parameter tells how many times of "
92 "tries, each 30 msec apart, the input must have the same value "
93 "in order for that value to be accepted and the event "
94 "transition produced."),
95 Default(3));
96
98// We factor out the description, and event on/off to a separate group to give
99// JMRI a chance to render the make turnout/make sensor buttons.
100CDI_GROUP_ENTRY(pc, PCLineConfig);
102
104 private SimpleEventHandler,
105 private Polling,
106 private Notifiable
107{
108public:
109 typedef PCConfig config_entry_type;
111
128 template <unsigned N>
129 __attribute__((noinline))
130 MultiConfiguredPC(Node *node, const Gpio *const *pins, unsigned size,
132 : node_(node)
133 , pins_(pins)
134 , size_(N)
135 , offset_(config)
136 {
137 // Mismatched sizing of the GPIO array from the configuration array.
138 HASSERT(size == N);
140 producedEvents_ = new EventId[size * 2];
141 std::allocator<debouncer_type> alloc;
142 debouncers_ = alloc.allocate(size_);
143 for (unsigned i = 0; i < size_; ++i)
144 {
145 alloc_traits::construct(alloc, debouncers_ + i, 3);
146 }
147 }
148
150 {
151 do_unregister();
153 delete[] producedEvents_;
154 std::allocator<debouncer_type> alloc;
155 for (unsigned i = 0; i < size_; ++i)
156 {
157 alloc_traits::destroy(alloc, debouncers_ + i);
158 }
159 alloc.deallocate(debouncers_, size_);
160 }
161
163 Polling *polling()
164 {
165 return this;
166 }
167
169 void poll_33hz(WriteHelper *helper, Notifiable *done) override
170 {
171 nextPinToPoll_ = 0;
172 pollingHelper_ = helper;
173 pollingDone_ = done;
174 this->notify();
175 }
176
179 void notify() override
180 {
181 for (; nextPinToPoll_ < size_; ++nextPinToPoll_)
182 {
183 auto i = nextPinToPoll_;
184 if (pins_[i]->direction() == Gpio::Direction::DOUTPUT)
185 {
186 continue;
187 }
188 if (debouncers_[i].update_state(pins_[i]->is_set()))
189 {
190 // Pin flipped.
191 ++nextPinToPoll_; // avoid infinite loop.
192 auto event = producedEvents_[2 * i +
193 (debouncers_[i].current_state() ? 1 : 0)];
194 pollingHelper_->WriteAsync(node_, Defs::MTI_EVENT_REPORT,
195 WriteHelper::global(), eventid_to_buffer(event), this);
196 return;
197 }
198 }
199 pollingDone_->notify();
200 }
201
203 int fd, bool initial_load, BarrierNotifiable *done) OVERRIDE
204 {
205 AutoNotify n(done);
206
207 if (!initial_load)
208 {
209 // There is no way to figure out what the previously registered
210 // eventid values were for the individual pins. Therefore we always
211 // unregister everything and register them anew. It also causes us
212 // to identify all. This is not a problem since apply_configuration
213 // is coming from a user action.
214 do_unregister();
215 }
216 RepeatedGroup<config_entry_type, UINT_MAX> grp_ref(offset_.offset());
217 for (unsigned i = 0; i < size_; ++i)
218 {
219 const config_entry_type cfg_ref(grp_ref.entry(i));
220 EventId cfg_event_on = cfg_ref.pc().event_on().read(fd);
221 EventId cfg_event_off = cfg_ref.pc().event_off().read(fd);
222 EventRegistry::instance()->register_handler(
223 EventRegistryEntry(this, cfg_event_off, i * 2), 0);
224 EventRegistry::instance()->register_handler(
225 EventRegistryEntry(this, cfg_event_on, i * 2 + 1), 0);
226 uint8_t action = cfg_ref.action().read(fd);
227 if (action == (uint8_t)PCConfig::ActionConfig::DOUTPUT)
228 {
229 pins_[i]->set_direction(Gpio::Direction::DOUTPUT);
230 producedEvents_[i * 2] = 0;
231 producedEvents_[i * 2 + 1] = 0;
232 }
233 else
234 {
235 uint8_t param = cfg_ref.debounce().read(fd);
236 pins_[i]->set_direction(Gpio::Direction::DINPUT);
237 debouncers_[i].reset_options(param);
238 debouncers_[i].initialize(pins_[i]->read());
239 producedEvents_[i * 2] = cfg_event_off;
240 producedEvents_[i * 2 + 1] = cfg_event_on;
241 }
242 }
243 return REINIT_NEEDED; // Causes events identify.
244 }
245
246 void factory_reset(int fd) OVERRIDE
247 {
248 RepeatedGroup<config_entry_type, UINT_MAX> grp_ref(offset_.offset());
249 for (unsigned i = 0; i < size_; ++i)
250 {
251 grp_ref.entry(i).pc().description().write(fd, "");
252 CDI_FACTORY_RESET(grp_ref.entry(i).action);
253 CDI_FACTORY_RESET(grp_ref.entry(i).debounce);
254 }
255 }
256
260 void factory_reset_names(int fd, const char *basename)
261 {
262 RepeatedGroup<config_entry_type, UINT_MAX> grp_ref(offset_.offset());
263 for (unsigned i = 0; i < size_; ++i)
264 {
265 string v(basename);
266 v.push_back(' ');
267 char buf[10];
268 unsigned_integer_to_buffer(i + 1, buf);
269 v += buf;
270 grp_ref.entry(i).pc().description().write(fd, v);
271 }
272 }
273
274 void handle_identify_global(const EventRegistryEntry &registry_entry,
275 EventReport *event, BarrierNotifiable *done) override
276 {
277 AutoNotify an(done);
278 if (event->dst_node && event->dst_node != node_)
279 {
280 return;
281 }
282 unsigned pin = registry_entry.user_arg >> 1;
283 if (pins_[pin]->direction() == Gpio::Direction::DINPUT)
284 {
285 SendProducerIdentified(registry_entry, event, done);
286 }
287 else
288 {
289 SendConsumerIdentified(registry_entry, event, done);
290 }
291 }
292
293 void handle_identify_consumer(const EventRegistryEntry &registry_entry,
294 EventReport *event, BarrierNotifiable *done) override
295 {
296 AutoNotify an(done);
297 if (event->event != registry_entry.event)
298 {
299 return;
300 }
301 unsigned pin = registry_entry.user_arg >> 1;
302 if (pins_[pin]->direction() == Gpio::Direction::DOUTPUT)
303 {
304 SendConsumerIdentified(registry_entry, event, done);
305 }
306 }
307
308 void handle_identify_producer(const EventRegistryEntry &registry_entry,
309 EventReport *event, BarrierNotifiable *done) override
310 {
311 AutoNotify an(done);
312 if (event->event != registry_entry.event)
313 {
314 return;
315 }
316 unsigned pin = registry_entry.user_arg >> 1;
317 if (pins_[pin]->direction() == Gpio::Direction::DINPUT)
318 {
319 SendProducerIdentified(registry_entry, event, done);
320 }
321 }
322
323 void handle_event_report(const EventRegistryEntry &registry_entry,
324 EventReport *event, BarrierNotifiable *done) override
325 {
326 AutoNotify an(done);
327 if (event->event != registry_entry.event)
328 {
329 return;
330 }
331 const Gpio *pin = pins_[registry_entry.user_arg >> 1];
332 if (pin->direction() == Gpio::Direction::DOUTPUT)
333 {
334 const bool is_on = (registry_entry.user_arg & 1);
335 pin->write(is_on);
336 }
337 }
338
339private:
340 using alloc_traits = std::allocator_traits<std::allocator<debouncer_type>>;
341
344 void do_unregister()
345 {
346 EventRegistry::instance()->unregister_handler(this);
347 }
348
351 void SendConsumerIdentified(const EventRegistryEntry &registry_entry,
352 EventReport *event, BarrierNotifiable *done)
353 {
355 unsigned b1 = pins_[registry_entry.user_arg >> 1]->is_set() ? 1 : 0;
356 unsigned b2 = registry_entry.user_arg & 1; // on or off event?
357 if (b1 ^ b2)
358 {
359 mti++; // INVALID
360 }
361 event->event_write_helper<3>()->WriteAsync(node_, mti,
362 WriteHelper::global(), eventid_to_buffer(registry_entry.event),
363 done->new_child());
364 }
365
368 void SendProducerIdentified(const EventRegistryEntry &registry_entry,
369 EventReport *event, BarrierNotifiable *done)
370 {
372 unsigned b1 = pins_[registry_entry.user_arg >> 1]->is_set() ? 1 : 0;
373 unsigned b2 = registry_entry.user_arg & 1; // on or off event?
374 if (b1 ^ b2)
375 {
376 mti++; // INVALID
377 }
378 event->event_write_helper<4>()->WriteAsync(node_, mti,
379 WriteHelper::global(), eventid_to_buffer(registry_entry.event),
380 done->new_child());
381 }
382
383 // Variables used for asynchronous state during the polling loop.
385 unsigned nextPinToPoll_;
387 WriteHelper *pollingHelper_;
389 Notifiable *pollingDone_;
390
392 Node *node_;
394 const Gpio *const *pins_;
396 size_t size_;
398 ConfigReference offset_;
401 EventId *producedEvents_;
403 debouncer_type *debouncers_;
404};
405}
406
407#endif // _OPENLCB_MULTICONFIGUREDPC_HXX_
#define CDI_GROUP(GroupName, ARGS...)
Starts a CDI group.
#define CDI_GROUP_ENTRY(NAME, TYPE, ARGS...)
Adds an entry to a CDI group.
#define CDI_FACTORY_RESET(PATH)
Performs factory reset on a CDI variable.
This class sends a notification in its destructor.
A BarrierNotifiable allows to create a number of child Notifiable and wait for all of them to finish.
BarrierNotifiable * new_child()
Call this for each child task.
Abstract class for components that need to receive configuration from EEPROM.
virtual void factory_reset(int fd)=0
Clears configuration file and resets the configuration settings to factory value.
UpdateAction
Specifies what additional steps are needed to apply the new configuration.
@ REINIT_NEEDED
Need to perform application-level reinitialization.
virtual UpdateAction apply_configuration(int fd, bool initial_load, BarrierNotifiable *done)=0
Notifies the component that there is new configuration available for loading.
virtual void register_update_listener(ConfigUpdateListener *listener)=0
Adds a config update listener to be called upon configuration updates.
virtual void unregister_update_listener(ConfigUpdateListener *listener)=0
Removes a config update listener.
OS-independent abstraction for GPIO.
Definition Gpio.hxx:43
virtual void write(Value new_state) const =0
Writes a GPIO output pin (set or clear to a specific state).
virtual Direction direction() const =0
Gets the GPIO direction.
An object that can schedule itself on an executor to run.
virtual void notify()=0
Generic callback.
This debouncer will update state if for N consecutive attempts the input value is the same.
Definition Debouncer.hxx:67
static ConfigUpdateService * instance()
Definition Singleton.hxx:77
Class representing a particular location in the configuration space.
Defines an empty group with no members, but blocking a certain amount of space in the rendered config...
Implementation class for event ID configuration entries.
virtual void handle_identify_global(const EventRegistryEntry &registry_entry, EventReport *event, BarrierNotifiable *done)=0
Called on the need of sending out identification messages.
virtual void handle_event_report(const EventRegistryEntry &registry_entry, EventReport *event, BarrierNotifiable *done)=0
Called on incoming EventReport messages.
virtual void handle_identify_consumer(const EventRegistryEntry &registry_entry, EventReport *event, BarrierNotifiable *done)=0
Called on another node sending IdentifyConsumer.
virtual void handle_identify_producer(const EventRegistryEntry &registry_entry, EventReport *event, BarrierNotifiable *done)=0
Called on another node sending IdentifyProducer.
Structure used in registering event handlers.
uint32_t user_arg
Opaque user argument.
EventId event
Stores the event ID or beginning of range for which to register the given handler.
Base class for NMRAnet nodes conforming to the asynchronous interface.
Definition Node.hxx:52
Implementation class for numeric configuration entries, templated by the integer type.
Abstract base class for components that need repeated execution (with a specified frequency,...
virtual void poll_33hz(WriteHelper *helper, Notifiable *done)=0
This function will be called approximately 33 times per second by the refresh loop.
Defines a repeated group of a given type and a given number of repeats.
SimpleEventHandler ignores all non-essential callbacks.
Implementation class for string configuration entries.
A statically allocated buffer for sending one message to the OpenLCB bus.
#define OVERRIDE
Function attribute for virtual functions declaring that this funciton is overriding a funciton that s...
Definition macros.h:180
#define HASSERT(x)
Checks that the value of expression x is true, else terminates the current process.
Definition macros.h:138
NumericConfigEntry< uint8_t > Uint8ConfigEntry
Unsigned numeric config entry with 1 byte width.
CDI_GROUP_END()
Signals termination of the group.
Payload eventid_to_buffer(uint64_t eventid)
Converts an Event ID to a Payload suitable to be sent as an event report.
Definition If.cxx:72
MTI
Known Message type indicators.
@ MTI_PRODUCER_IDENTIFIED_VALID
producer broadcast, valid state
@ MTI_CONSUMER_IDENTIFIED_VALID
consumer broadcast, valid state
Shared notification structure that is assembled for each incoming event-related message,...
EventId event
The event ID from the incoming message.
Node * dst_node
nullptr for global messages; points to the specific virtual node for addressed events identify messag...