From c4c493a51e30bd700414a2971d7d0c18d5b1c7c3 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 2 Mar 2023 05:55:28 -0800 Subject: [PATCH 1/2] Virtual Train application: adds ability to spawn many train nodes. --- applications/train/targets/linux.x86/main.cxx | 142 +++++++++++++++--- 1 file changed, 121 insertions(+), 21 deletions(-) diff --git a/applications/train/targets/linux.x86/main.cxx b/applications/train/targets/linux.x86/main.cxx index 60e3b0aba..9c7855023 100644 --- a/applications/train/targets/linux.x86/main.cxx +++ b/applications/train/targets/linux.x86/main.cxx @@ -34,8 +34,8 @@ #define LOGLEVEL INFO -#include #include +#include #include @@ -47,6 +47,7 @@ #include "openlcb/TractionTestTrain.hxx" #include "openlcb/TractionTrain.hxx" #include "os/os.h" +#include "os/sleep.h" #include "utils/constants.hxx" static const openlcb::NodeID NODE_ID = 0x0501010100F5ULL; @@ -55,7 +56,8 @@ openlcb::SimpleCanStack stack(NODE_ID); openlcb::TrainService traction_service(stack.iface()); -const char *const openlcb::SNIP_DYNAMIC_FILENAME = openlcb::MockSNIPUserFile::snip_user_file_path; +const char *const openlcb::SNIP_DYNAMIC_FILENAME = + openlcb::MockSNIPUserFile::snip_user_file_path; const char *const openlcb::CONFIG_FILENAME = openlcb::SNIP_DYNAMIC_FILENAME; int port = 12021; @@ -64,6 +66,8 @@ const char *device_path = nullptr; const char *name = "Deadrail Train"; int address = 1732; OVERRIDE_CONST(num_memory_spaces, 4); +OVERRIDE_CONST(local_nodes_count, 250); +OVERRIDE_CONST(local_alias_cache_size, 251); namespace openlcb { @@ -74,17 +78,17 @@ const SimpleNodeStaticValues SNIP_STATIC_DATA = { void usage(const char *e) { fprintf(stderr, - "Usage: %s ([-i destination_host] [-p port] | [-d device_path]) " - "[-a address] [-n name]\n\n", - e); - fprintf(stderr, - "Connects to an openlcb bus and exports a virtual train.\n"); + "Usage: %s ([-i destination_host] [-p port] | [-d device_path]) " + "[-a address] [-n name]\n\n", + e); + fprintf( + stderr, "Connects to an openlcb bus and exports a virtual train.\n"); fprintf(stderr, - "The bus connection will be through an OpenLCB HUB on " - "destination_host:port with OpenLCB over TCP " - "(in GridConnect format) protocol, or through the CAN-USB device " - "(also in GridConnect protocol) found at device_path. Device takes " - "precedence over TCP host:port specification."); + "The bus connection will be through an OpenLCB HUB on " + "destination_host:port with OpenLCB over TCP " + "(in GridConnect format) protocol, or through the CAN-USB device " + "(also in GridConnect protocol) found at device_path. Device takes " + "precedence over TCP host:port specification."); fprintf(stderr, "The default target is localhost:12021.\n"); fprintf(stderr, "Address is the virtual loco address. Default 1726.\n"); exit(1); @@ -122,8 +126,92 @@ void parse_args(int argc, char *argv[]) } } + + +namespace openlcb +{ + +/// This struct encompasses all objects needed for a virtual train node. +struct TrainInstance : private IncomingMessageStateFlow +{ + /// Constructor + /// + /// @param addr Train (DCC) address. Will be used for generating the node + /// ID. + /// @param name Train Name. Will be used for SNIP. + /// @param desc Train User Description. Will be used for SNIP. + TrainInstance(unsigned addr, const string &name, const string &desc) + : IncomingMessageStateFlow(stack.iface()) + , address_(addr) + , nodeName_(name) + , nodeDescription_(desc) + , responseFlow_(stack.info_flow()) + { + stack.iface()->dispatcher()->register_handler( + this, Defs::MTI_IDENT_INFO_REQUEST, Defs::MTI_EXACT); + } + + /// DCC address + unsigned address_; + /// Text name for the train. Will appear on the throttle. + string nodeName_; + /// Text description of the node. Will be used in the SNIP reply. + string nodeDescription_; + /// Common helper flow for responding to SNIP requests. + SimpleInfoFlow *responseFlow_; + /// This implementtion object is supposed to drive the hardware. + LoggingTrain train_impl {address_}; + /// Virtual Train Node object. + TrainNodeWithId node_ {&traction_service, &train_impl, + TractionDefs::NODE_ID_DCC | 0xFF000000u | address_}; + /// Handles PIP requests for this node. + ProtocolIdentificationHandler pip {&node_, + Defs::EVENT_EXCHANGE | Defs::SIMPLE_NODE_INFORMATION | + Defs::TRACTION_CONTROL}; + /// Ensures that this train responds to the IS_TRAIN event requests. + FixedEventProducer isTrainEventHandler_ { + &node_}; + + /// What should be our response to SNIP requests. + const SimpleInfoDescriptor SNIP_CUSTOM_RESPONSE[9] = { + {SimpleInfoDescriptor::LITERAL_BYTE, 4, 0, nullptr}, + {SimpleInfoDescriptor::C_STRING, 0, 0, + SNIP_STATIC_DATA.manufacturer_name}, + {SimpleInfoDescriptor::C_STRING, 0, 0, SNIP_STATIC_DATA.model_name}, + {SimpleInfoDescriptor::C_STRING, 0, 0, + SNIP_STATIC_DATA.hardware_version}, + {SimpleInfoDescriptor::C_STRING, 0, 0, + SNIP_STATIC_DATA.software_version}, + {SimpleInfoDescriptor::LITERAL_BYTE, 2, 0, nullptr}, + {SimpleInfoDescriptor::C_STRING, 0, 0, nodeName_.c_str()}, + {SimpleInfoDescriptor::C_STRING, 0, 0, nodeDescription_.c_str()}, + {SimpleInfoDescriptor::END_OF_DATA, 0, 0, 0}}; + + /// Custom handler for SNIP reply. + Action entry() OVERRIDE + { + if (!nmsg()->dstNode) + return release_and_exit(); + if (nmsg()->dstNode != &node_) + return release_and_exit(); + auto *b = responseFlow_->alloc(); + b->data()->reset( + nmsg(), SNIP_CUSTOM_RESPONSE, Defs::MTI_IDENT_INFO_REPLY); + responseFlow_->send(b); + return release_and_exit(); + } +}; // struct TrainInstance + +} // namespace openlcb + int appl_main(int argc, char *argv[]) { + if (false) + { + // This will make it appear that the device is slow to respond to + // anything. + stack.iface()->set_tx_hook([]() { microsleep(50000); }); + } parse_args(argc, argv); LOG(INFO, "Train name: %s", name); openlcb::MockSNIPUserFile snip_user_file(name, "Deadrail--description"); @@ -137,15 +225,27 @@ int appl_main(int argc, char *argv[]) stack.connect_tcp_gridconnect_hub(host, port); } - openlcb::LoggingTrain train_impl(address); - openlcb::TrainNodeWithId train_node(&traction_service, &train_impl, openlcb::TractionDefs::NODE_ID_DCC | 0xFF000000u | address); - openlcb::FixedEventProducer - is_train_event_handler(&train_node); - openlcb::ProtocolIdentificationHandler pip( - &train_node, - openlcb::Defs::EVENT_EXCHANGE | openlcb::Defs::SIMPLE_NODE_INFORMATION | - openlcb::Defs::TRACTION_CONTROL); - openlcb::SNIPHandler snip_handler{stack.iface(), nullptr, stack.info_flow()}; + if (false) + { + // This is the simple way to create one train + openlcb::LoggingTrain train_impl(address); + openlcb::TrainNodeWithId train_node(&traction_service, &train_impl, + openlcb::TractionDefs::NODE_ID_DCC | 0xFF000000u | address); + openlcb::FixedEventProducer + is_train_event_handler(&train_node); + openlcb::ProtocolIdentificationHandler pip(&train_node, + openlcb::Defs::EVENT_EXCHANGE | + openlcb::Defs::SIMPLE_NODE_INFORMATION | + openlcb::Defs::TRACTION_CONTROL); + openlcb::SNIPHandler snip_handler { + stack.iface(), &train_node, stack.info_flow()}; + } + else + { + // This is how we can create multiple trains. + openlcb::TrainInstance some_train( + address, name, "Deadrail--description"); + } stack.loop_executor(); return 0; From aa74fdbe928b5de6f9d4a5916419606f560c98b0 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Mon, 6 Mar 2023 06:02:01 -0800 Subject: [PATCH 2/2] Fixes bug in variable scopes. --- applications/train/targets/linux.x86/main.cxx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/applications/train/targets/linux.x86/main.cxx b/applications/train/targets/linux.x86/main.cxx index 9c7855023..ddd2d40fe 100644 --- a/applications/train/targets/linux.x86/main.cxx +++ b/applications/train/targets/linux.x86/main.cxx @@ -239,14 +239,19 @@ int appl_main(int argc, char *argv[]) openlcb::Defs::TRACTION_CONTROL); openlcb::SNIPHandler snip_handler { stack.iface(), &train_node, stack.info_flow()}; + + // Have to loop before the objects above go out of scope. + stack.loop_executor(); } else { // This is how we can create multiple trains. openlcb::TrainInstance some_train( address, name, "Deadrail--description"); + + // Have to loop before the objects above go out of scope. + stack.loop_executor(); } - stack.loop_executor(); return 0; }