Open Model Railroad Network (OpenMRN)
Loading...
Searching...
No Matches
SimpleStack.cxx
Go to the documentation of this file.
1
36#ifndef __FreeRTOS__
37#define LOGLEVEL INFO
38#endif
39
40#if defined(__linux__) || defined(__MACH__)
41#include <net/if.h>
42#include <termios.h> /* tc* functions */
43#endif
44
45#include <sys/stat.h>
46#include <sys/types.h>
47#include <unistd.h>
48
50
53#include "openlcb/NodeInitializeFlow.hxx"
56#include "openmrn_features.h"
57#include "utils/HubDeviceSelect.hxx"
58#include "utils/SocketCan.hxx"
59
60namespace openlcb
61{
62
63SimpleStackBase::SimpleStackBase(
64 std::function<std::unique_ptr<SimpleStackBase::PhysicalIf>()>
65 create_if_helper)
66 : ifaceHolder_(create_if_helper())
67{
68}
69
70SimpleCanStackBase::SimpleCanStackBase(const openlcb::NodeID node_id)
71 : SimpleStackBase(std::bind(&SimpleCanStackBase::create_if, this, node_id))
72{
73}
74
75std::unique_ptr<SimpleStackBase::PhysicalIf> SimpleCanStackBase::create_if(
76 const openlcb::NodeID node_id)
77{
78 return std::unique_ptr<PhysicalIf>(new CanPhysicalIf(node_id, service()));
79}
80
81SimpleTcpStackBase::SimpleTcpStackBase(const openlcb::NodeID node_id)
82 : SimpleStackBase(std::bind(&SimpleTcpStackBase::create_if, this, node_id))
83{
84}
85
86std::unique_ptr<SimpleStackBase::PhysicalIf> SimpleTcpStackBase::create_if(
87 const openlcb::NodeID node_id)
88{
89 return std::unique_ptr<PhysicalIf>(new TcpPhysicalIf(node_id, service()));
90}
91
92SimpleCanStack::SimpleCanStack(const openlcb::NodeID node_id)
93 : SimpleCanStackBase(node_id)
94 , node_(iface(), node_id, false)
95{
96}
97
98SimpleTcpStack::SimpleTcpStack(const openlcb::NodeID node_id)
99 : SimpleTcpStackBase(node_id)
100 , node_(iface(), node_id, false)
101{
102}
103
105{
106 Destructable *t =
107 new StreamTransportCan(if_can(), config_num_stream_senders());
108 additionalComponents_.emplace_back(t);
109 Destructable *mem_stream =
111 additionalComponents_.emplace_back(mem_stream);
112}
113
114void SimpleStackBase::start_stack(bool delay_start)
115{
116#if OPENMRN_HAVE_POSIX_FD
117 // Opens the eeprom file and sends configuration update commands to all
118 // listeners. We must only call ConfigUpdateFlow::open_file() once and it
119 // may have been done by an earlier call to create_config_file_if_needed()
120 // or check_version_and_factory_reset().
121 if (configUpdateFlow_.get_fd() < 0 && CONFIG_FILENAME != nullptr)
122 {
124 }
126#endif // have posix fd
127
128 if (!delay_start)
129 {
131 }
132
133 // Adds memory spaces.
134 if (config_enable_all_memory_space() == CONSTANT_TRUE)
135 {
136 auto *space = new ReadOnlyMemoryBlock(nullptr, 0xFFFFFFFFUL);
137 memoryConfigHandler_.registry()->insert(
138 nullptr, MemoryConfigDefs::SPACE_ALL_MEMORY, space);
139 additionalComponents_.emplace_back(space);
140 }
141
142 // Calls node-specific startup hook.
143 start_node();
144}
145
147{
148 {
149 auto *space = new ReadOnlyMemoryBlock(
150 reinterpret_cast<const uint8_t *>(&SNIP_STATIC_DATA),
151 sizeof(SNIP_STATIC_DATA));
152 memoryConfigHandler_.registry()->insert(
154 additionalComponents_.emplace_back(space);
155 }
156#if OPENMRN_HAVE_POSIX_FD
157 if (SNIP_DYNAMIC_FILENAME != nullptr)
158 {
159 FileMemorySpace* space = nullptr;
161 space = new FileMemorySpace(
163 } else {
164 space = new FileMemorySpace(
166 }
167 memoryConfigHandler_.registry()->insert(
169 additionalComponents_.emplace_back(space);
170 }
171#endif // OPENMRN_HAVE_POSIX_FD
172 size_t cdi_size = strlen(CDI_DATA);
173 if (cdi_size > 0)
174 {
175 auto *space = new ReadOnlyMemoryBlock(
176 reinterpret_cast<const uint8_t *>(&CDI_DATA), cdi_size + 1);
177 memoryConfigHandler_.registry()->insert(
179 additionalComponents_.emplace_back(space);
180 }
181#if OPENMRN_HAVE_POSIX_FD
182 if (CONFIG_FILENAME != nullptr)
183 {
184 auto *space =
186 memory_config_handler()->registry()->insert(
188 additionalComponents_.emplace_back(space);
189 }
190#endif // OPENMRN_HAVE_POSIX_FD
191}
192
194 openlcb::TrainImpl *train, const char *fdi_xml, NodeID node_id)
195 // Note: this code tries to predict what the node id of the trainNode_ will
196 // be. Unfortunately due to initialization order problems we cannot query
197 // it in advance.
198 : SimpleCanStackBase(node_id)
199 , trainNode_(&tractionService_, train, node_id)
200 , fdiBlock_(reinterpret_cast<const uint8_t *>(fdi_xml), strlen(fdi_xml))
201{
202}
203
205{
207 memoryConfigHandler_.registry()->insert(
209}
210
212{
213 start_iface(false);
214 for (Node *node = iface()->first_local_node();
215 node != nullptr;
216 node = iface()->next_local_node(node->node_id()))
217 {
218 node->initialize();
219 }
220}
221
223{
224}
225
227{
228 if (restart)
229 {
230 if_can()->alias_allocator()->reinit_seed();
231 if_can()->local_aliases()->clear();
232 if_can()->remote_aliases()->clear();
233 }
234
235 // Bootstraps the fresh alias allocation process.
236 if_can()->alias_allocator()->send(if_can()->alias_allocator()->alloc());
237}
238
240{
242 start_iface(true);
243
244 // Causes all nodes to grab a new alias and send out node initialization
245 // done messages. This object owns itself and will do `delete this;` at the
246 // end of the process.
247 new ReinitAllNodes(iface());
248}
249
250int SimpleStackBase::create_config_file_if_needed(const InternalConfigData &cfg,
251 uint16_t expected_version, unsigned file_size)
252{
255 struct stat statbuf;
256 bool reset = false;
257 bool extend = false;
258 int fd = ::open(CONFIG_FILENAME, O_RDONLY);
259 if (fd < 0)
260 {
261 // Create file.
262 LOG(INFO, "Creating config file %s", CONFIG_FILENAME);
263 reset = true;
264 fd = ::open(
265 CONFIG_FILENAME, O_CREAT | O_TRUNC | O_RDWR, S_IRUSR | S_IWUSR);
266 if (fd < 0)
267 {
268 printf("Failed to create config file: fd %d errno %d: %s\n", fd,
269 errno, strerror(errno));
270 DIE();
271 }
272 reset = true;
273 }
274 ::close(fd);
276 HASSERT(fstat(fd, &statbuf) == 0);
277 if (statbuf.st_size < (ssize_t)file_size)
278 {
279 extend = true;
280 }
281 // Handle the case where the file exists but is too short for the verison
282 // check. This was observed on the esp32 with SD storage which does not
283 // automatically flush to disk on write.
284 if ((long)statbuf.st_size < (long)cfg.version().end_offset())
285 {
286 LOG(VERBOSE, "%s is too short (%d vs %d), forcing reset.",
287 CONFIG_FILENAME, (int)statbuf.st_size, cfg.version().end_offset());
288 reset = true;
289 }
290 if (!reset && cfg.version().read(fd) != expected_version)
291 {
292 uint16_t current_version = cfg.version().read(fd);
293 if (current_version != expected_version)
294 {
295 LOG(VERBOSE, "config version mismatch (%d vs %d), forcing reset.",
296 current_version, expected_version);
297 reset = true;
298 }
299 }
300 if (!reset && !extend)
301 {
302 return fd;
303 }
304
305 // Clears the file, preserving the node name and desription if any.
306 if (extend && !reset)
307 {
308 auto ret = lseek(fd, statbuf.st_size, SEEK_SET);
309 HASSERT(ret == statbuf.st_size);
310 file_size -= statbuf.st_size; // Clears nothing, just extends with 0xFF.
311 }
312 else if (statbuf.st_size >= 128)
313 {
314 auto ret = lseek(fd, 128, SEEK_SET);
315 HASSERT(ret == 128);
316 file_size -= 128; // Clears less.
317 }
318 else
319 {
320 lseek(fd, 0, SEEK_SET);
321 }
322
323 static const unsigned bufsize = 128;
324 char *buf = (char *)malloc(bufsize);
325 HASSERT(buf);
326 memset(buf, 0xff, bufsize);
327 unsigned len = file_size;
328 while (len > 0)
329 {
330 ssize_t c = write(fd, buf, std::min(len, bufsize));
331 HASSERT(c >= 0);
332 len -= c;
333 }
334 free(buf);
335
336 // Initializes basic structures in the file.
337 cfg.version().write(fd, expected_version);
338 cfg.next_event().write(fd, 0);
339 // ACDI version byte. This is not very nice because we cannot be
340 // certain that the EEPROM starts with the ACDI data. We'll check it
341 // though.
343 Uint8ConfigEntry(0).write(fd, 2);
344 factory_reset_all_events(cfg, node()->node_id(), fd);
346 return fd;
347}
348
350 const InternalConfigData &cfg, uint16_t expected_version, bool force)
351{
353 int fd = configUpdateFlow_.get_fd();
354 if (fd < 0)
355 {
357 }
358
359 if (cfg.version().read(fd) != expected_version)
360 {
364 cfg.next_event().write(fd, 0);
365 // ACDI version byte. This is not very nice because we cannot be
366 // certain that the EEPROM starts with the ACDI data. We'll check it
367 // though.
369 Uint8ConfigEntry(0).write(fd, 2);
370 force = true;
371 }
372 if (force)
373 {
374 factory_reset_all_events(cfg, node()->node_id(), fd);
376 cfg.version().write(fd, expected_version);
377 }
378 return fd;
379}
380
386extern const uint16_t CDI_EVENT_OFFSETS[];
387const uint16_t *cdi_event_offsets_ptr = CDI_EVENT_OFFSETS;
388
389void SimpleStackBase::set_event_offsets(const vector<uint16_t> *offsets)
390{
391 cdi_event_offsets_ptr = &(*offsets)[0];
392}
393
395 const InternalConfigData &cfg, uint64_t node_id, int fd)
396{
397 // First we find the event count.
398 uint16_t new_next_event = cfg.next_event().read(fd);
399 uint16_t next_event = new_next_event;
400 for (unsigned i = 0; cdi_event_offsets_ptr[i]; ++i)
401 {
402 ++new_next_event;
403 }
404 // We block off the event IDs first.
405 cfg.next_event().write(fd, new_next_event);
406 // Then we write them to eeprom.
407 for (unsigned i = 0; cdi_event_offsets_ptr[i]; ++i)
408 {
409 EventId id = node_id;
410 id <<= 16;
411 id |= next_event++;
412 EventConfigEntry(cdi_event_offsets_ptr[i]).write(fd, id);
413 }
414}
415
417 const char *path, Notifiable *on_exit)
418{
419 int fd = ::open(path, O_RDWR);
420 HASSERT(fd >= 0);
421 LOG(INFO, "Adding device %s as fd %d", path, fd);
422 create_gc_port_for_can_hub(can_hub(), fd, on_exit);
423}
424
425#if defined(__linux__) || defined(__MACH__)
426void SimpleCanStackBase::add_gridconnect_tty(
427 const char *device, Notifiable *on_exit)
428{
429 int fd = ::open(device, O_RDWR);
430 HASSERT(fd >= 0);
431 LOG(INFO, "Adding device %s as fd %d", device, fd);
432 create_gc_port_for_can_hub(can_hub(), fd, on_exit);
433
434 HASSERT(!tcflush(fd, TCIOFLUSH));
435 struct termios settings;
436 HASSERT(!tcgetattr(fd, &settings));
437 cfmakeraw(&settings);
438 cfsetspeed(&settings, B115200);
439 HASSERT(!tcsetattr(fd, TCSANOW, &settings));
440}
441#endif
442#if defined(__linux__)
443void SimpleCanStackBase::add_socketcan_port_select(
444 const char *device, int loopback)
445{
446 int s = socketcan_open(device, loopback);
447 if (s >= 0)
448 {
449 auto *port = new HubDeviceSelect<CanHubFlow>(can_hub(), s);
450 additionalComponents_.emplace_back(port);
451 }
452}
453#endif
454extern Pool *const __attribute__((__weak__)) g_incoming_datagram_allocator =
456
457} // namespace openlcb
Pool * init_main_buffer_pool()
Initializes the main buffer pool.
Definition Buffer.cxx:40
void create_gc_port_for_can_hub(CanHubFlow *can_hub, int fd, Notifiable *on_exit, bool use_select)
Creates a new port on a CAN hub in gridconnect format for a select-compatible file descriptor.
int bind(int socket, const struct sockaddr *address, socklen_t address_len)
Bind a name to a socket.
Definition Socket.cxx:159
Base class of everything with a virtual destructor.
HubPort that connects a select-aware device to a strongly typed Hub.
An object that can schedule itself on an executor to run.
Pool of previously allocated, but currently unused, items.
Definition Buffer.hxx:278
void insert(Node *node, uint32_t id, Handler *handler)
Inserts a handler into the map.
void send(MessageType *msg, unsigned priority=UINT_MAX) OVERRIDE
Sends a message to the state flow for processing.
void reinit_seed()
Resets the alias allocator to the state it was at construction.
void clear()
Reinitializes the entire map.
int open_file(const char *path)
Must be called once (only) before calling anything else.
void init_flow()
Asynchronously invokes all update listeners with the config FD.
void factory_reset()
Synchronously invokes all update listeners to factory reset.
Implementation class for event ID configuration entries.
Memory space implementation that exports the contents of a file as a memory space.
AliasCache * local_aliases()
Definition IfCan.hxx:96
AliasAllocator * alias_allocator()
Definition IfCan.hxx:110
AliasCache * remote_aliases()
Definition IfCan.hxx:103
Node * next_local_node(NodeID previous)
Iterator helper on the local nodes map.
Definition If.hxx:304
Handler for the stream read/write commands in the memory config protocol (server side).
Base class for NMRAnet nodes conforming to the asynchronous interface.
Definition Node.hxx:52
virtual void clear_initialized()=0
Callback from the simple stack when the node has to return to uninitialized state.
void initialize()
Callback from the simple stack to start the initialization process.
void write(int fd, TR d) const
Writes the data to the configuration file.
Memory space implementation that exports a some memory-mapped data as a read-only memory space.
StateFlow that iterates through all local nodes and sends out node initialization complete for each o...
SimpleStack with a CAN-bus based interface and IO functions for CAN-bus.
void add_stream_support()
Enables stream transport in the interface and in the memory config protocol.
std::unique_ptr< PhysicalIf > create_if(const openlcb::NodeID node_id)
Constructor helper function.
void add_gridconnect_port(const char *path, Notifiable *on_exit=nullptr)
Adds a gridconnect port to the CAN bus.
void start_iface(bool restart) override
Helper function for start_stack et al.
virtual Node * node()=0
void default_start_node()
Exports the memory config spaces that are typically used for a complex node.
ConfigUpdateFlow configUpdateFlow_
Calls the config listeners with the configuration FD.
void start_after_delay()
Call this function when you used delay_start upon starting the executor.
int create_config_file_if_needed(const InternalConfigData &ofs, uint16_t expected_version, unsigned file_size)
Tries to open the config file; if not existant, the size too small, or the version number is mismatch...
void restart_stack()
Reinitializes the node.
virtual void start_iface(bool restart)=0
Hook for descendant classes to start the interface.
virtual void start_node()=0
Hook for clients to initialize the node-specific components.
std::vector< std::unique_ptr< Destructable > > additionalComponents_
Stores and keeps ownership of optional components.
int check_version_and_factory_reset(const InternalConfigData &ofs, uint16_t expected_version, bool force=false)
Checks the version information in the EEPROM and performs a factory reset if incorrect or if force is...
static void factory_reset_all_events(const InternalConfigData &ofs, uint64_t node_id, int fd)
Overwrites all events in the eeprom with a brand new event ID.
void start_stack(bool delay_start)
Call this function once after the actual IO ports are set up.
MemoryConfigHandler * memory_config_handler()
static void set_event_offsets(const vector< uint16_t > *offsets)
Call this function at the beginning of appl_main, just before {} or { create_config_file_if_needed} i...
void start_iface(bool restart) override
Helper function for start_stack et al.
std::unique_ptr< PhysicalIf > create_if(const openlcb::NodeID node_id)
Constructor helper function.
TrainNodeWithId trainNode_
The actual node.
void start_node() override
Hook for clients to initialize the node-specific components.
SimpleTrainCanStack(openlcb::TrainImpl *train, const char *fdi_xml, NodeID node_id)
Creates a train node OpenLCB stack.
CAN-specific implementation of the stream transport interface.
Abstract base class for train implementations.
#define CONSTANT_TRUE
We cannot compare constants to zero, so we use 1 and 2 as constant values for booleans.
#define LOG(level, message...)
Conditionally write a message to the logging output.
Definition logging.h:99
static const int VERBOSE
Loglevel that is usually not printed, reporting debugging information.
Definition logging.h:59
static const int INFO
Loglevel that is printed by default, reporting some status information.
Definition logging.h:57
#define HASSERT(x)
Checks that the value of expression x is true, else terminates the current process.
Definition macros.h:138
#define DIE(MSG)
Unconditionally terminates the current process with a message.
Definition macros.h:143
NumericConfigEntry< uint8_t > Uint8ConfigEntry
Unsigned numeric config entry with 1 byte width.
const SimpleNodeStaticValues SNIP_STATIC_DATA
This static data will be exported as the first block of SNIP.
uint64_t NodeID
48-bit NMRAnet Node ID type
const char CDI_DATA[]
This symbol contains the embedded text of the CDI xml file.
const uint16_t CDI_EVENT_OFFSETS[]
Contains an array describing each position in the Configuration space that is occupied by an Event ID...
const char *const SNIP_DYNAMIC_FILENAME
The SNIP dynamic data will be read from this file.
Pool *const g_incoming_datagram_allocator
Allocator to be used for Buffer<IncomingDatagram> objects.
const char *const CONFIG_FILENAME
This symbol must be defined by the application to tell which file to open for the configuration liste...
const size_t CONFIG_FILE_SIZE
This symbol must be defined by the application.
@ SPACE_FDI
read-only for function definition XML
@ SPACE_ALL_MEMORY
all memory space
@ SPACE_ACDI_USR
read-write ACDI space
@ SPACE_CONFIG
config memory space
@ SPACE_ACDI_SYS
read-only ACDI space
Structure representing the layout of the memory space for Simple Node Identification user-editable da...