Skip to content

Commit

Permalink
Adds state flow for logon sequence (#564)
Browse files Browse the repository at this point in the history
- Implements logon feedback callbacks in the LogonHandler
- Implements sending logon enable in the LogonHandler 
- Adds a separate state flow that runs on the discovered locomotives to send addressed messages: select info and logon assign
- Adds constants for the address partitions according to S-9.2.1.1
- Adds testability to the API of LogonHandler
- Adds unit test exercising logon enable, select info and assign packets and their feedback
- Adds the API for the commandstation to implement in the LogonHandlerModule
- Adds a default implementation of this API with simple storage components.

Misc fixes:
- removes PacketFlowInterface as it seems to be duplicate to TrackIf, but less intuitive in the name.
- Moves mock TrackIf to a header file to be reusable.

===

* 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

* 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.
  • Loading branch information
balazsracz authored Aug 16, 2021
1 parent ddcb125 commit 4fec273
Show file tree
Hide file tree
Showing 14 changed files with 979 additions and 99 deletions.
25 changes: 25 additions & 0 deletions src/dcc/Defs.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,31 @@ enum
CMD_READ_BLOCK = 0b11111110,
CMD_READ_BACKGROUND = 0b11111101,
CMD_WRITE_BLOCK = 0b11111100,

// Address partitions as defined by S-9.2.1.1. These are 6-bit values for
// the first byte of the reported and assigned address.
/// 7-bit mobile decoders
ADR_MOBILE_SHORT = 0b00111000,
/// Mask for 7-bit mobile decoders
ADR_MOBILE_SHORT_MASK = 0xFF,
/// 14-bit mobile decoders
ADR_MOBILE_LONG = 0,
/// Maximum value of the first byte for a 14-bit mobile decoder.
MAX_MOBILE_LONG = 0b00100111,
/// 11-bit extended accessory decoder
ADR_ACC_EXT = 0b00101000,
/// Mask for 11-bit extended accessory decoder
MASK_ACC_EXT = 0b00111000,
/// 9-bit basic accessory decoder
ADR_ACC_BASIC = 0b00110000,
/// Mask for 9-bit basic accessory decoder
MASK_ACC_BASIC = 0b00111000,

/// This value, when given to a decoder, represents an invalid
/// (unassignable) address. This is a 2-byte value that can go to the wire
/// -- above we only have the constants for address partitions, which is
/// the first byte.
ADR_INVALID = (ADR_MOBILE_SHORT << 8),
};

/// Parameters for the Logon Enable command.
Expand Down
156 changes: 156 additions & 0 deletions src/dcc/Logon.cxxtest
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
#include "dcc/Logon.hxx"

#include "dcc/LogonModule.hxx"
#include "os/FakeClock.hxx"
#include "utils/async_traction_test_helper.hxx"

using ::testing::ElementsAre;

namespace dcc
{

class LogonTest : public openlcb::TractionTest
{
protected:
~LogonTest()
{
logonHandler_.shutdown();
twait();
}

DefaultLogonModule module_;
RailcomHubFlow railcomHub_ {&g_service};
StrictMock<MockTrackIf> track_;
LogonHandler<DefaultLogonModule> logonHandler_ {
&g_service, &track_, &railcomHub_, &module_};
};

// This function is never called, and thus is optimized away at linker
// stage. However, it ensures that the logon handler can be compiled with the
// interface as the module parameter.
void compile_test()
{
LogonHandler<LogonHandlerModule> *f = nullptr;
f->~LogonHandler();
}

TEST_F(LogonTest, create)
{
}

TEST_F(LogonTest, logon_per_300msec)
{
FakeClock clk;
EXPECT_CALL(
track_, packet(ElementsAre(254, 255, 0x22, 0x11, 0x5a), 0xFEFC0000ull));
logonHandler_.startup_logon(0x2211, 0x5a);
wait();
Mock::VerifyAndClear(&track_);
clk.advance(MSEC_TO_NSEC(20));
wait();

clk.advance(MSEC_TO_NSEC(250));

EXPECT_CALL(
track_, packet(ElementsAre(254, 255, 0x22, 0x11, 0x5a), 0xFEFC0000ull));
clk.advance(MSEC_TO_NSEC(50));
wait();

EXPECT_CALL(
track_, packet(ElementsAre(254, 255, 0x22, 0x11, 0x5a), 0xFEFC0000ull));
clk.advance(MSEC_TO_NSEC(300));
wait();
}

TEST_F(LogonTest, select_shortinfo)
{
FakeClock clk;
EXPECT_CALL(
track_, packet(ElementsAre(254, 255, 0x22, 0x11, 0x5a), 0xFEFC0000ull))
.Times(AtLeast(1));
logonHandler_.startup_logon(0x2211, 0x5a);
wait();

uint64_t decoder_id = 0x39944332211ull;
auto *b = railcomHub_.alloc();
RailcomDefs::add_did_feedback(decoder_id, b->data());
b->data()->feedbackKey = 0xFEFC0000ull;

EXPECT_CALL(track_,
packet(ElementsAre(254, 0xD3, 0x99, 0x44, 0x33, 0x22, 0x11, 0xFF, _),
0xFEDFF000ull));

railcomHub_.send(b);
wait();

clk.advance(MSEC_TO_NSEC(99));

// If there is no feedback for a while, the packet will get repeated.
EXPECT_CALL(track_,
packet(ElementsAre(254, 0xD3, 0x99, 0x44, 0x33, 0x22, 0x11, 0xFF, _),
0xFEDFF000ull));
clk.advance(MSEC_TO_NSEC(10));
wait();

// After one re-try no more packets are generated for this locomotive.
clk.advance(MSEC_TO_NSEC(500));
wait();
}

TEST_F(LogonTest, full_assign_sequence)
{
FakeClock clk;
EXPECT_CALL(
track_, packet(ElementsAre(254, 255, 0x22, 0x11, 0x5a), 0xFEFC0000ull))
.Times(AtLeast(1));
logonHandler_.startup_logon(0x2211, 0x5a);
wait();

uint64_t decoder_id = 0x39944332211ull;
auto *b = railcomHub_.alloc();
RailcomDefs::add_did_feedback(decoder_id, b->data());
b->data()->feedbackKey = 0xFEFC0000ull;

const uintptr_t SELECT_FB_KEY = 0xFEDFF000ull;
EXPECT_CALL(track_,
packet(ElementsAre(254, 0xD3, 0x99, 0x44, 0x33, 0x22, 0x11, 0xFF, _),
SELECT_FB_KEY));

railcomHub_.send(b);
wait();
EXPECT_TRUE(
module_.loco_flags(0) & LogonHandlerModule::FLAG_PENDING_GET_SHORTINFO);

// Select / shortinfo feedback.
b = railcomHub_.alloc();
RailcomDefs::add_shortinfo_feedback(
(Defs::ADR_MOBILE_SHORT << 8) | 3, 17, 0, 0, b->data());
b->data()->feedbackKey = SELECT_FB_KEY;

const uintptr_t ASSIGN_FB_KEY = 0xFEE00000ull;

// Assign packet
EXPECT_CALL(track_,
packet(ElementsAre(254, 0xE3, 0x99, 0x44, 0x33, 0x22, 0x11,
0xC0 | (10000 >> 8), 10000 & 0xFF, _),
ASSIGN_FB_KEY));

railcomHub_.send(b);
wait();

EXPECT_TRUE(
module_.loco_flags(0) & LogonHandlerModule::FLAG_PENDING_ASSIGN);

// Assign feedback.
b = railcomHub_.alloc();
RailcomDefs::add_assign_feedback(0xff, 0xfff, 0, 0, b->data());
b->data()->feedbackKey = ASSIGN_FB_KEY;

railcomHub_.send(b);
wait();

uint8_t &flags = module_.loco_flags(0);
EXPECT_EQ(LogonHandlerModule::FLAG_COMPLETE, flags);
}

} // namespace dcc
Loading

0 comments on commit 4fec273

Please sign in to comment.