Skip to content

Commit

Permalink
Adds example CS with railcom for auto login (#560)
Browse files Browse the repository at this point in the history
Adds an example application for the tiva 123:
- DCC packet generation
- RailCom receiver
- CV reads and writes using railcom

Misc updates:
- Adds support to the railcom debug flow to skip printing CH1 messages.
- Adds support to the railcom debug flow to skip printing CH2 messages that are only an ACK each.
- Fixes behavior of the POM CV read code where certain railcom responses (such as an ACK) incorrectly terminated the flow.

===

* Adds target application for DCC-A command station prototype.

* instantiates the command station components to generate a packet flow to the track.

* Adds railcom hub and debug output.

* Adds helper comments to the railcom decode table.

* Adds a test for a railcom broadcast address decode example.

* Adds support for filtering repeated data coming back from the decoders.

* Adds support for the POM reads and writes.

* POM read flow: fixes some problems

- repeat POM read packets twice to give a chance for decoders to reply
- do not stop listening for answers when an ACK comes.
- unlikely race condition when the flow gets registered

* emit channel2 debug messages.

* fix test

* rename the directory to dcc_cs_login

* Update application names.
  • Loading branch information
balazsracz authored Aug 11, 2021
1 parent 831e2d3 commit 055cbbd
Show file tree
Hide file tree
Showing 16 changed files with 312 additions and 10 deletions.
3 changes: 3 additions & 0 deletions applications/dcc_cs_login/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
SUBDIRS = targets
-include config.mk
include $(OPENMRNPATH)/etc/recurse.mk
1 change: 1 addition & 0 deletions applications/dcc_cs_login/config.mk
2 changes: 2 additions & 0 deletions applications/dcc_cs_login/subdirs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
SUBDIRS = \

2 changes: 2 additions & 0 deletions applications/dcc_cs_login/targets/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
compile_cdi

4 changes: 4 additions & 0 deletions applications/dcc_cs_login/targets/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
SUBDIRS = \
freertos.armv7m.ek-tm4c123gxl \

include $(OPENMRNPATH)/etc/recurse.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#ifndef _APPLICATIONS_DCC_CS_LOGIN_TARGET_CONFIG_HXX_
#define _APPLICATIONS_DCC_CS_LOGIN_TARGET_CONFIG_HXX_

#include "openlcb/ConfiguredConsumer.hxx"
#include "openlcb/ConfiguredProducer.hxx"
#include "openlcb/ConfigRepresentation.hxx"
#include "openlcb/MemoryConfig.hxx"

namespace openlcb
{

/// Defines the identification information for the node. The arguments are:
///
/// - 4 (version info, always 4 by the standard
/// - Manufacturer name
/// - Model name
/// - Hardware version
/// - Software version
///
/// This data will be used for all purposes of the identification:
///
/// - the generated cdi.xml will include this data
/// - the Simple Node Ident Info Protocol will return this data
/// - the ACDI memory space will contain this data.
extern const SimpleNodeStaticValues SNIP_STATIC_DATA = {
4, "OpenMRN", "DCC CS with Logon - Tiva Launchpad 123",
"ek-tm4c123gxl", "1.01"};

#define NUM_OUTPUTS 3
#define NUM_INPUTS 2

/// Declares a repeated group of a given base group and number of repeats. The
/// ProducerConfig and ConsumerConfig groups represent the configuration layout
/// needed by the ConfiguredProducer and ConfiguredConsumer classes, and come
/// from their respective hxx file.
using AllConsumers = RepeatedGroup<ConsumerConfig, NUM_OUTPUTS>;
using AllProducers = RepeatedGroup<ProducerConfig, NUM_INPUTS>;

/// Modify this value every time the EEPROM needs to be cleared on the node
/// after an update.
static constexpr uint16_t CANONICAL_VERSION = 0x1111;


/// Defines the main segment in the configuration CDI. This is laid out at
/// origin 128 to give space for the ACDI user data at the beginning.
CDI_GROUP(IoBoardSegment, Segment(MemoryConfigDefs::SPACE_CONFIG), Offset(128));
/// Each entry declares the name of the current entry, then the type and then
/// optional arguments list.
CDI_GROUP_ENTRY(internal_config, InternalConfigData);
CDI_GROUP_END();

/// The main structure of the CDI. ConfigDef is the symbol we use in main.cxx
/// to refer to the configuration defined here.
CDI_GROUP(ConfigDef, MainCdi());
/// Adds the <identification> tag with the values from SNIP_STATIC_DATA above.
CDI_GROUP_ENTRY(ident, Identification);
/// Adds an <acdi> tag.
CDI_GROUP_ENTRY(acdi, Acdi);
/// Adds a segment for changing the values in the ACDI user-defined
/// space. UserInfoSegment is defined in the system header.
CDI_GROUP_ENTRY(userinfo, UserInfoSegment);
/// Adds the main configuration segment.
CDI_GROUP_ENTRY(seg, IoBoardSegment);
CDI_GROUP_END();

} // namespace openlcb

#endif // _APPLICATIONS_DCC_CS_LOGIN_TARGET_CONFIG_HXX_
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
/** \copyright
* Copyright (c) 2013, Balazs Racz
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* \file main.cxx
*
* Main file for the DCC CS with Logon application on the Tiva Launchpad board.
*
* @author Balazs Racz
* @date 11 Aug 2021
*/

#include "os/os.h"
#include "nmranet_config.h"

#include "openlcb/SimpleStack.hxx"
#include "openlcb/TractionTrain.hxx"
#include "openlcb/EventHandlerTemplates.hxx"
#include "dcc/Loco.hxx"
#include "dcc/SimpleUpdateLoop.hxx"
#include "dcc/LocalTrackIf.hxx"
#include "dcc/RailcomHub.hxx"
#include "dcc/RailcomPortDebug.hxx"
#include "executor/PoolToQueueFlow.hxx"
#include "openlcb/TractionCvSpace.hxx"

#include "freertos_drivers/ti/TivaGPIO.hxx"
#include "freertos_drivers/common/BlinkerGPIO.hxx"
#include "freertos_drivers/common/PersistentGPIO.hxx"
#include "config.hxx"
#include "hardware.hxx"

// These preprocessor symbols are used to select which physical connections
// will be enabled in the main(). See @ref appl_main below.
#define SNIFF_ON_SERIAL
//#define SNIFF_ON_USB
//#define HAVE_PHYSICAL_CAN_PORT

// Changes the default behavior by adding a newline after each gridconnect
// packet. Makes it easier for debugging the raw device.
OVERRIDE_CONST(gc_generate_newlines, 1);
// Specifies how much RAM (in bytes) we allocate to the stack of the main
// thread. Useful tuning parameter in case the application runs out of memory.
OVERRIDE_CONST(main_thread_stack_size, 2500);

// Specifies the 48-bit OpenLCB node identifier. This must be unique for every
// hardware manufactured, so in production this should be replaced by some
// easily incrementable method.
extern const openlcb::NodeID NODE_ID = 0x0501010118DAULL;

// Sets up a comprehensive OpenLCB stack for a single virtual node. This stack
// contains everything needed for a usual peripheral node -- all
// CAN-bus-specific components, a virtual node, PIP, SNIP, Memory configuration
// protocol, ACDI, CDI, a bunch of memory spaces, etc.
openlcb::SimpleCanStack stack(NODE_ID);

// ConfigDef comes from config.hxx and is specific to the particular device and
// target. It defines the layout of the configuration memory space and is also
// used to generate the cdi.xml file. Here we instantiate the configuration
// layout. The argument of offset zero is ignored and will be removed later.
openlcb::ConfigDef cfg(0);
// Defines weak constants used by the stack to tell it which device contains
// the volatile configuration information. This device name appears in
// HwInit.cxx that creates the device drivers.
extern const char *const openlcb::CONFIG_FILENAME = "/dev/eeprom";
// The size of the memory space to export over the above device.
extern const size_t openlcb::CONFIG_FILE_SIZE =
cfg.seg().size() + cfg.seg().offset();
static_assert(openlcb::CONFIG_FILE_SIZE <= 300, "Need to adjust eeprom size");
// The SNIP user-changeable information in also stored in the above eeprom
// device. In general this could come from different eeprom segments, but it is
// simpler to keep them together.
extern const char *const openlcb::SNIP_DYNAMIC_FILENAME =
openlcb::CONFIG_FILENAME;

/// This timer checks the eeprom once a second and if the user has written
/// something, executes a reload of the configuration via the OpenLCB config
/// service.
class AutoUpdateTimer : public ::Timer
{
public:
AutoUpdateTimer()
: ::Timer(stack.executor()->active_timers())
{
start(SEC_TO_NSEC(1));
}

long long timeout() override
{
extern uint8_t eeprom_updated;
if (eeprom_updated)
{
needUpdate_ = true;
eeprom_updated = 0;
}
else
{
if (needUpdate_)
{
stack.config_service()->trigger_update();
needUpdate_ = false;
}
}
return RESTART;
}

bool needUpdate_ {false};
} update_timer;

// ====== Command Station components =======
OVERRIDE_CONST(num_memory_spaces, 10);

dcc::LocalTrackIf track(stack.service(), 2);
dcc::SimpleUpdateLoop updateLoop(stack.service(), &track);
PoolToQueueFlow<Buffer<dcc::Packet>> pool_translator(
stack.service(), track.pool(), &updateLoop);

openlcb::TrainService trainService(stack.iface());

dcc::Dcc28Train train3Impl(dcc::DccShortAddress(3));
openlcb::TrainNodeForProxy train3Node(&trainService, &train3Impl);
openlcb::FixedEventProducer<openlcb::TractionDefs::IS_TRAIN_EVENT>
trainEventProducer(&train3Node);

// ===== RailCom components ======
dcc::RailcomHubFlow railcom_hub(stack.service());
openlcb::RailcomToOpenLCBDebugProxy gRailcomProxy(
&railcom_hub, stack.node(), nullptr, false, true);

openlcb::TractionCvSpace traction_cv(stack.memory_config_handler(), &track,
&railcom_hub, openlcb::MemoryConfigDefs::SPACE_DCC_CV);

/** Entry point to application.
* @param argc number of command line arguments
* @param argv array of command line arguments
* @return 0, should never return
*/
int appl_main(int argc, char *argv[])
{
stack.check_version_and_factory_reset(
cfg.seg().internal_config(), openlcb::CANONICAL_VERSION, false);

int fd = ::open("/dev/mainline", O_WRONLY);
HASSERT(fd >= 0);
track.set_fd(fd);

// The necessary physical ports must be added to the stack.
//
// It is okay to enable multiple physical ports, in which case the stack
// will behave as a bridge between them. For example enabling both the
// physical CAN port and the USB port will make this firmware act as an
// USB-CAN adapter in addition to the producers/consumers created above.
//
// If a port is enabled, it must be functional or else the stack will
// freeze waiting for that port to send the packets out.
#if defined(HAVE_PHYSICAL_CAN_PORT)
stack.add_can_port_select("/dev/can0");
#endif
#if defined(SNIFF_ON_USB)
stack.add_gridconnect_port("/dev/serUSB0");
#endif
#if defined(SNIFF_ON_SERIAL)
stack.add_gridconnect_port("/dev/ser0");
#endif

HubDeviceNonBlock<dcc::RailcomHubFlow> railcom_port(&railcom_hub,
"/dev/railcom");

// This command donates the main thread to the operation of the
// stack. Alternatively the stack could be started in a separate stack and
// then application-specific business logic could be executed ion a busy
// loop in the main thread.
stack.loop_executor();
return 0;
}
16 changes: 13 additions & 3 deletions src/dcc/RailcomPortDebug.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -211,11 +211,14 @@ class RailcomToOpenLCBDebugProxy : public dcc::RailcomHubPort
{
public:
RailcomToOpenLCBDebugProxy(dcc::RailcomHubFlow *parent, Node *node,
dcc::RailcomHubPort *occupancy_port)
dcc::RailcomHubPort *occupancy_port, bool ch1_enabled = true,
bool ack_enabled = true)
: dcc::RailcomHubPort(parent->service())
, parent_(parent)
, node_(node)
, occupancyPort_(occupancy_port)
, ch1Enabled_(ch1_enabled)
, ackEnabled_(ack_enabled)
{
parent_->register_port(this);
}
Expand Down Expand Up @@ -251,7 +254,7 @@ public:
{
return release_and_exit();
}
if (message()->data()->ch1Size)
if (message()->data()->ch1Size && ch1Enabled_)
{
return allocate_and_call(
node_->iface()->global_message_write_flow(),
Expand Down Expand Up @@ -281,7 +284,10 @@ public:

Action maybe_send_ch2()
{
if (message()->data()->ch2Size)
if (message()->data()->ch2Size &&
(ackEnabled_ ||
dcc::railcom_decode[message()->data()->ch2Data[0]] !=
dcc::RailcomDefs::ACK))
{
return allocate_and_call(
node_->iface()->global_message_write_flow(),
Expand Down Expand Up @@ -312,6 +318,10 @@ public:
dcc::RailcomHubFlow *parent_{nullptr};
Node *node_;
dcc::RailcomHubPort *occupancyPort_;
/// True if we should transmit channel1 data.
uint8_t ch1Enabled_ : 1;
/// True if we should transmit data that starts with an ACK.
uint8_t ackEnabled_ : 1;
};

} // namespace openlcb
Expand Down
Loading

0 comments on commit 055cbbd

Please sign in to comment.