Skip to content

Commit

Permalink
Adds example CS and decoder with partial logon implementation (bakers…
Browse files Browse the repository at this point in the history
…tu#565)

CS example app: Adds logon components to the command station with logon example application:
- Adds an extension to the LogonModule that stores the openlcb train node and similar components.
- Instantiates the logon component in the CS main.
- Makes the railcom pin weak pull-up.
- Adds logging via stdout and moves openlcb packets to ttyUSB

Decoder example app:
- Makes the railcom pin open-drain to allow parallel connecting multiple decoders.
- Makes it parametrizable whether ch1 broadcast and ch2 acks are generated or not. These are useful to keep the scope plot clean and logs sparse.
- Adds a piece of code that generates a deterministic decoder ID.
- Fills in the logon decoder feedback packets during initialization
- Recognizes the logon requests during the IRQ processing and assigns the logon feedback railcom packets.

Logon core implementation changes:
- Makes the default logon modules templated so that command stations can define their own storage fields.
- Adds a hook to the logon module to allow custom code to be run for the command station at login.

Misc logon-related changes:
- Updates the DCC debug parser code to output logon and S-9.2.1.1 packets in some form (hex for now)
- Adds some helper constants for packet decoding code.
- Makes INFO logs more targeted and useful for following what's happening for login.

===


* Merges PacketFlowInterface and TrackIf as these are the same definitions.
Moves the mock track IF to a header file to be reused.

* Starts implementing the state flow for sending logon packets.

* Fixes compile errors.

* Adds feedback callbacks to the logon class.
Instantiates it in a test.
Checks that logon messages are sent every 300 msec.

* Adds the state flow that sends out addressed packets to the decoders found.
Adds a default (inefficient) implementation of the storage module.

* Adds a test that the logon class compiles with the empty logon module.

* Wires up the logon feedback to the storage module.
Adds a test for checking the select / get short info.

* Adds an error state into which locomotives end up when the logon flow fails for some reason.

* Completes the assignment sequence.

* Fixes style.

* update comment

* Adds a storage module for the cs example login.
Instantiates the login handler flow.
Creates the trains for decoders that logged in.

* Makes the decoder run UART in open drain mode.

* Adds a minimum support for the 254 and 253 address partitions.

* Starts up the logon flow in the CS.

* Adds a partial implementation of the logon feedback to the
example decoder.
The IRQ level parsing is completed and the railcom feedback should be genrated.

* replace the use of uninitialized with std::unique_ptr because the structure gets copied around all the time.

* Fixes incorrect constant value.

* Adds some log statements.

* Makes space for virtual train nodes.

* Fixes logon bugs:
- Having no railcom response (empty feedback) should not signal the flow that there was a decoder logging on.
- Correctly define the packet repetition count.

* Adjusts info logging related to logon process.

* Fixes incorrect constant value.

* Fixes logon bugs:
- Having no railcom response (empty feedback) should not signal the flow that there was a decoder logging on.
- Correctly define the packet repetition count.

* Fixes comments.

* Fixes comments.
  • Loading branch information
balazsracz authored Mar 20, 2022
1 parent 7c8e61b commit 0cc5749
Show file tree
Hide file tree
Showing 8 changed files with 343 additions and 18 deletions.
102 changes: 102 additions & 0 deletions applications/dcc_cs_login/TrainStorage.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/** \copyright
* Copyright (c) 2021, 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 TrainStorage.hxx
*
* Storage policies for trains in the automatic logon example.
*
* @author Balazs Racz
* @date 14 Aug 2021
*/

#ifndef _APPLICATION_CS_LOGON_TRAINSTORAGE_HXX_
#define _APPLICATION_CS_LOGON_TRAINSTORAGE_HXX_


#include "dcc/Defs.hxx"
#include "dcc/Loco.hxx"
#include "dcc/LogonModule.hxx"
#include "openlcb/EventHandlerTemplates.hxx"
#include "openlcb/TractionTrain.hxx"
#include "utils/macros.h"

class ModuleBase {
public:
struct Storage
{
std::unique_ptr<dcc::Dcc28Train> impl_;
std::unique_ptr<openlcb::TrainNodeForProxy> node_;
std::unique_ptr<
openlcb::FixedEventProducer<openlcb::TractionDefs::IS_TRAIN_EVENT>>
eventProducer_;
};
};

class TrainLogonModule : public dcc::ParameterizedLogonModule<ModuleBase> {
public:
TrainLogonModule(openlcb::TrainService *s)
: trainService_(s)
{
}

using Base = ParameterizedLogonModule<ModuleBase>;

void assign_complete(unsigned loco_id)
{
Base::assign_complete(loco_id);
auto& t = locos_[loco_id];
uint8_t part = (t.assignedAddress_ >> 8) & dcc::Defs::ADR_MASK;

if (part == dcc::Defs::ADR_MOBILE_SHORT) {
LOG(INFO, "started a short address decoder");
t.impl_.reset(new dcc::Dcc28Train(
dcc::DccShortAddress(t.assignedAddress_ & 0x7f)));
}
else if (part >= dcc::Defs::ADR_MOBILE_LONG &&
part <= dcc::Defs::MAX_MOBILE_LONG)
{
LOG(INFO, "started a long address decoder");
t.impl_.reset(new dcc::Dcc28Train(dcc::DccLongAddress(
t.assignedAddress_ - (dcc::Defs::ADR_MOBILE_LONG << 8))));
} else {
// Not a mobile decoder. We don't have an implementation for those
// yet.
LOG(INFO, "started a non-mobile decoder %04x %x",
t.assignedAddress_, part);
return;
}
t.node_.reset(new openlcb::TrainNodeForProxy(
trainService_, t.impl_.get()));
t.eventProducer_.reset(new openlcb::FixedEventProducer<
openlcb::TractionDefs::IS_TRAIN_EVENT>(t.node_.get()));
}

private:
openlcb::TrainService* trainService_;
};


#endif // _APPLICATION_CS_LOGON_TRAINSTORAGE_HXX_
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,16 @@
* @date 11 Aug 2021
*/

#define LOGLEVEL INFO

#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/Logon.hxx"
#include "dcc/SimpleUpdateLoop.hxx"
#include "dcc/LocalTrackIf.hxx"
#include "dcc/RailcomHub.hxx"
Expand All @@ -51,11 +54,14 @@
#include "freertos_drivers/common/PersistentGPIO.hxx"
#include "config.hxx"
#include "hardware.hxx"
#include "TrainStorage.hxx"

#include "utils/stdio_logging.h"

// 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 SNIFF_ON_SERIAL
#define SNIFF_ON_USB
//#define HAVE_PHYSICAL_CAN_PORT

// Changes the default behavior by adding a newline after each gridconnect
Expand All @@ -65,6 +71,9 @@ OVERRIDE_CONST(gc_generate_newlines, 1);
// thread. Useful tuning parameter in case the application runs out of memory.
OVERRIDE_CONST(main_thread_stack_size, 2500);

OVERRIDE_CONST(local_nodes_count, 30);
OVERRIDE_CONST(local_alias_cache_size, 32);

// 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.
Expand Down Expand Up @@ -146,12 +155,17 @@ openlcb::FixedEventProducer<openlcb::TractionDefs::IS_TRAIN_EVENT>

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

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

// ===== Logon components =====
TrainLogonModule module{&trainService};
dcc::LogonHandler<TrainLogonModule> logonHandler{
&trainService, &track, &railcom_hub, &module};

/** Entry point to application.
* @param argc number of command line arguments
* @param argv array of command line arguments
Expand Down Expand Up @@ -187,7 +201,16 @@ int appl_main(int argc, char *argv[])

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


// Enables weak pull-up on the UART input pin.
MAP_GPIOPadConfigSet(RAILCOM_CH1_Pin::GPIO_BASE, RAILCOM_CH1_Pin::GPIO_PIN,
GPIO_STRENGTH_2MA, GPIO_PIN_TYPE_STD_WPU);

/// @todo store the session ID and generate a new one every startup.
logonHandler.startup_logon(0x3344, 25);

LOG(ALWAYS, "hello world");

// 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
Expand Down
138 changes: 136 additions & 2 deletions applications/dcc_decoder/main.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,18 @@
#include "os/os.h"
#include "utils/StringPrintf.hxx"
#include "utils/constants.hxx"
#include "utils/Crc.hxx"

#include "hardware.hxx"

Executor<1> executor("executor", 0, 2048);


/// If true, sends channel1 broadcast on mobile decoder addresses.
static const bool SEND_CH1_BROADCAST = false;
/// If true, sends ack to all correctly received regular addressed packet.
static const bool SEND_CH2_ACK = false;

// We reserve a lot of buffer for transmit to cover for small hiccups in the
// host reading data.
OVERRIDE_CONST(serial_tx_buffer_size, 2048);
Expand All @@ -61,6 +68,18 @@ uint16_t dcc_address_wire = 3 << 8;

uint8_t f0 = 0;

/// DIY / public domain decoder.
const uint16_t manufacturer_id = 0x0D;
uint64_t decoder_id = 0;

/// Last known command station ID.
uint16_t cid;
/// Last known session ID.
uint8_t session_id;
/// 1 if we have been selected in this session.
uint8_t is_selected = 0;


/// Stores a programming packet which is conditional on a repetition.
DCCPacket progStored;
/// Feedback for the stored programming packet.
Expand Down Expand Up @@ -127,6 +146,35 @@ class IrqProcessor : public dcc::PacketProcessor
/// Called in the main to prepare the railcom feedback packets.
void init()
{
// Figures out the decoder ID.
Crc8DallasMaxim m;
uint8_t *p = (uint8_t*)0x1FFFF7AC;
uint32_t d = 0;

// Computes the decoder ID using a hash of the chip serial number.
m.update16(*p++);
m.update16(*p++);
d |= m.get();
d <<= 8;
m.update16(*p++);
m.update16(*p++);
d |= m.get();
d <<= 8;
m.update16(*p++);
m.update16(*p++);
m.update16(*p++);
m.update16(*p++);
d |= m.get();
d <<= 8;
m.update16(*p++);
m.update16(*p++);
m.update16(*p++);
m.update16(*p++);
d |= m.get();
decoder_id = manufacturer_id;
decoder_id <<= 32;
decoder_id |= d;

update_address();
// Programming response packet
((dcc::Packet*)&progStored)->clear();
Expand All @@ -136,6 +184,31 @@ class IrqProcessor : public dcc::PacketProcessor
{
ack_.add_ch2_data(dcc::RailcomDefs::CODE_ACK);
}
// Also usable for 8-byte ack packet
ack_.add_ch1_data(dcc::RailcomDefs::CODE_ACK);
ack_.add_ch1_data(dcc::RailcomDefs::CODE_ACK);
// Decoder ID feedback packet.
dcc::RailcomDefs::add_did_feedback(decoder_id, &did_);

// ShortInfo feedback packet
uint16_t addr;
if (dcc_address_wire & 0x8000) {
// 14 bit address
addr = dcc_address_wire - 0xC000 + (dcc::Defs::ADR_MOBILE_LONG << 8);
} else {
addr = (dcc::Defs::ADR_MOBILE_SHORT << 8) |
((dcc_address_wire >> 8) & 0x7f);
}
dcc::RailcomDefs::add_shortinfo_feedback(addr, 0 /*max fn*/,
0 /* capabilities */, 0 /*spaces*/, &shortInfo_);
is_selected = 0;
cid = 0xFFFF;
session_id = 0xFF;

// Logon assign feedback
dcc::RailcomDefs::add_assign_feedback(0xff /* changeflags */,
0xfff /*changecount*/, 0 /* capabilities 2 */, 0 /*capabilties 3*/,
&assignInfo_);
}

/// Updates the broadcast datagrams based on the active DCC address.
Expand Down Expand Up @@ -169,13 +242,15 @@ class IrqProcessor : public dcc::PacketProcessor
/// needs to be sent.
void packet_arrived(const DCCPacket *pkt, RailcomDriver *railcom) override
{
using namespace dcc::Defs;
DEBUG1_Pin::set(true);
if (pkt->packet_header.csum_error)
{
return;
}
uint8_t adrhi = pkt->payload[0];
if (adrhi && (adrhi < 232) && ((adrhi & 0xC0) != 0x80))
if (adrhi && (adrhi < 232) && ((adrhi & 0xC0) != 0x80) &&
SEND_CH1_BROADCAST)
{
// Mobile decoder addressed. Send back address.
if (bcastAtHi_)
Expand Down Expand Up @@ -203,13 +278,63 @@ class IrqProcessor : public dcc::PacketProcessor
prog_.feedbackKey = pkt->feedback_key;
railcom->send_ch2(&prog_);
}
else
else if (SEND_CH2_ACK)
{
// No specific reply prepared -- send just some acks.
ack_.feedbackKey = pkt->feedback_key;
railcom->send_ch2(&ack_);
}
}
if (adrhi == dcc::Defs::ADDRESS_LOGON)
{
if (pkt->dlc == 6 &&
((pkt->payload[1] & DCC_LOGON_ENABLE_MASK) ==
dcc::Defs::DCC_LOGON_ENABLE) &&
!is_selected &&
((pkt->payload[1] & ~DCC_LOGON_ENABLE_MASK) !=
(uint8_t)LogonEnableParam::ACC))
{
/// @todo add code for backoff algorithm.

/// @todo recognize whether we are talking to the same command
/// station.

did_.feedbackKey = pkt->feedback_key;
railcom->send_ch1(&did_);
railcom->send_ch2(&did_);
}
if (pkt->payload[1] >= DCC_DID_MIN &&
pkt->payload[1] <= DCC_DID_MAX && pkt->dlc >= 7 &&
((pkt->payload[1] & 0x0F) == ((decoder_id >> 40) & 0x0f)) &&
(pkt->payload[2] == ((decoder_id >> 32) & 0xff)) &&
(pkt->payload[3] == ((decoder_id >> 24) & 0xff)) &&
(pkt->payload[4] == ((decoder_id >> 16) & 0xff)) &&
(pkt->payload[5] == ((decoder_id >> 8) & 0xff)) &&
(pkt->payload[6] == ((decoder_id)&0xff)))
{
// Correctly addressed UID packet.
if ((pkt->payload[1] & DCC_DID_MASK) == DCC_SELECT &&
pkt->dlc > 7 && pkt->payload[7] == CMD_READ_SHORT_INFO)
{
shortInfo_.feedbackKey = pkt->feedback_key;
railcom->send_ch1(&shortInfo_);
railcom->send_ch2(&shortInfo_);
is_selected = 1;
}
else if ((pkt->payload[1] & DCC_DID_MASK) == DCC_LOGON_ASSIGN)
{
assignInfo_.feedbackKey = pkt->feedback_key;
railcom->send_ch1(&assignInfo_);
railcom->send_ch2(&assignInfo_);
}
else
{
ack_.feedbackKey = pkt->feedback_key;
railcom->send_ch1(&ack_);
railcom->send_ch2(&ack_);
}
}
}
DEBUG1_Pin::set(false);
}

Expand All @@ -225,6 +350,15 @@ class IrqProcessor : public dcc::PacketProcessor
/// RailCom packet with all ACKs in channel2.
dcc::Feedback ack_;

/// Railcom packet with decoder ID for logon.
dcc::Feedback did_;

/// Railcom packet with ShortInfo for select.
dcc::Feedback shortInfo_;

/// Railcom packet with decoder state for Logon Assign.
dcc::Feedback assignInfo_;

/// 1 if the next broadcast packet should be adrhi, 0 if adrlo.
uint8_t bcastAtHi_ : 1;
} irqProc;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,8 @@ void hw_preinit(void)
gpio_init.Pin = GPIO_PIN_3;
HAL_GPIO_Init(GPIOA, &gpio_init);

/* USART1 pinmux on railCom TX pin PB6 */
gpio_init.Mode = GPIO_MODE_AF_PP;
/* USART1 pinmux on railCom TX pin PB6 with open drain and pullup */
gpio_init.Mode = GPIO_MODE_AF_OD;
gpio_init.Pull = GPIO_PULLUP;
gpio_init.Speed = GPIO_SPEED_FREQ_HIGH;
gpio_init.Alternate = GPIO_AF0_USART1;
Expand Down
Loading

0 comments on commit 0cc5749

Please sign in to comment.