From 2b4d35706825a1c1ef5bc50d306098b37be547b7 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 1 Feb 2024 05:26:10 -0800 Subject: [PATCH 1/7] Fix incorrect consumer identified message being emitted by dcc accy producer. The range was not set properly, we use two ranges of 4095, but we only identified two ranges of about 2048. --- src/openlcb/DccAccyConsumer.hxx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/openlcb/DccAccyConsumer.hxx b/src/openlcb/DccAccyConsumer.hxx index 795fa9e82..cf5d0809e 100644 --- a/src/openlcb/DccAccyConsumer.hxx +++ b/src/openlcb/DccAccyConsumer.hxx @@ -81,12 +81,12 @@ protected: event->event_write_helper<1>()->WriteAsync(node_, Defs::MTI_CONSUMER_IDENTIFIED_RANGE, WriteHelper::global(), eventid_to_buffer(EncodeRange( - TractionDefs::ACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE, 2044)), + TractionDefs::ACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE, 4095)), done->new_child()); event->event_write_helper<2>()->WriteAsync(node_, Defs::MTI_CONSUMER_IDENTIFIED_RANGE, WriteHelper::global(), eventid_to_buffer(EncodeRange( - TractionDefs::INACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE, 2044)), + TractionDefs::INACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE, 4095)), done->new_child()); done->notify(); } From 5787d3fa32f6ba0a4170f831c8d73d97381f1840 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 1 Feb 2024 05:36:58 -0800 Subject: [PATCH 2/7] Adds tests. --- src/openlcb/DccAccyConsumer.cxxtest | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/openlcb/DccAccyConsumer.cxxtest b/src/openlcb/DccAccyConsumer.cxxtest index d961e3878..23d3bade8 100644 --- a/src/openlcb/DccAccyConsumer.cxxtest +++ b/src/openlcb/DccAccyConsumer.cxxtest @@ -58,6 +58,11 @@ public: class DccAccyTest : public AsyncNodeTest { protected: + DccAccyTest() + { + wait(); + } + StrictMock trackSendQueue_; DccAccyConsumer consumer_{node_, &trackSendQueue_}; }; @@ -72,6 +77,19 @@ TEST_F(DccAccyTest, identify_boot_invalid) ":X198F4111N0101020000FF01F0;", ":X194C722AN0101020000FF01F0;"); } +TEST_F(DccAccyTest, global_identify) +{ + clear_expect(true); + // Consumer range for two 4096 long tanges. + expect_packet(":X194A422AN0101020000FF0FFF;"); + expect_packet(":X194A422AN0101020000FE0FFF;"); + + // identify events addressed. + send_packet(":X19968111N022A;"); + wait(); +} + + TEST_F(DccAccyTest, identify_throw_identify) { EXPECT_CALL(trackSendQueue_, arrived(_, _)).Times(2); From 50ef5306589e7028a6ecfc35f2381c091c4f9303 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 1 Feb 2024 05:37:27 -0800 Subject: [PATCH 3/7] Fixes global handler. It was sending each identify twice. --- src/openlcb/DccAccyConsumer.hxx | 34 +++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/openlcb/DccAccyConsumer.hxx b/src/openlcb/DccAccyConsumer.hxx index cf5d0809e..5bc716829 100644 --- a/src/openlcb/DccAccyConsumer.hxx +++ b/src/openlcb/DccAccyConsumer.hxx @@ -74,21 +74,31 @@ protected: void handle_identify_global(const EventRegistryEntry ®istry_entry, EventReport *event, BarrierNotifiable *done) OVERRIDE { + AutoNotify an(done); if (event->dst_node && event->dst_node != node_) { - return done->notify(); + return; + } + if (registry_entry.event == + TractionDefs::ACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE) + { + event->event_write_helper<1>()->WriteAsync(node_, + Defs::MTI_CONSUMER_IDENTIFIED_RANGE, WriteHelper::global(), + eventid_to_buffer(EncodeRange( + TractionDefs::ACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE, + 4095)), + done->new_child()); + } + if (registry_entry.event == + TractionDefs::INACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE) + { + event->event_write_helper<2>()->WriteAsync(node_, + Defs::MTI_CONSUMER_IDENTIFIED_RANGE, WriteHelper::global(), + eventid_to_buffer(EncodeRange( + TractionDefs::INACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE, + 4095)), + done->new_child()); } - event->event_write_helper<1>()->WriteAsync(node_, - Defs::MTI_CONSUMER_IDENTIFIED_RANGE, WriteHelper::global(), - eventid_to_buffer(EncodeRange( - TractionDefs::ACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE, 4095)), - done->new_child()); - event->event_write_helper<2>()->WriteAsync(node_, - Defs::MTI_CONSUMER_IDENTIFIED_RANGE, WriteHelper::global(), - eventid_to_buffer(EncodeRange( - TractionDefs::INACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE, 4095)), - done->new_child()); - done->notify(); } void handle_event_report(const EventRegistryEntry ®istry_entry, From bbfca00aa3d9ffdfd3561254c9d033552a38fdeb Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 1 Feb 2024 06:27:03 -0800 Subject: [PATCH 4/7] Fix typo --- src/openlcb/DccAccyConsumer.cxxtest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/openlcb/DccAccyConsumer.cxxtest b/src/openlcb/DccAccyConsumer.cxxtest index 23d3bade8..a09dbcb28 100644 --- a/src/openlcb/DccAccyConsumer.cxxtest +++ b/src/openlcb/DccAccyConsumer.cxxtest @@ -80,7 +80,7 @@ TEST_F(DccAccyTest, identify_boot_invalid) TEST_F(DccAccyTest, global_identify) { clear_expect(true); - // Consumer range for two 4096 long tanges. + // Consumer range for two 4096 long ranges. expect_packet(":X194A422AN0101020000FF0FFF;"); expect_packet(":X194A422AN0101020000FE0FFF;"); From c73257a2a6a78235e4086503928cf796a399a81f Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 1 Feb 2024 06:27:40 -0800 Subject: [PATCH 5/7] Adds command to generate dcc extended accessory packet. --- src/dcc/Packet.cxx | 8 ++++++++ src/dcc/Packet.cxxtest | 15 +++++++++++++++ src/dcc/Packet.hxx | 11 +++++++++++ 3 files changed, 34 insertions(+) diff --git a/src/dcc/Packet.cxx b/src/dcc/Packet.cxx index 8d51abb29..757a448d5 100644 --- a/src/dcc/Packet.cxx +++ b/src/dcc/Packet.cxx @@ -346,6 +346,14 @@ void Packet::add_dcc_basic_accessory(unsigned address, bool is_activate) add_dcc_checksum(); } +void Packet::add_dcc_ext_accessory(unsigned address, uint8_t aspect) +{ + add_dcc_accy_address(false, address); + payload[dlc++] = aspect; + add_dcc_checksum(); +} + + void Packet::set_dcc_logon_enable( Defs::LogonEnableParam param, uint16_t cid, uint8_t session_id) { diff --git a/src/dcc/Packet.cxxtest b/src/dcc/Packet.cxxtest index 8cae24eb5..530c51968 100644 --- a/src/dcc/Packet.cxxtest +++ b/src/dcc/Packet.cxxtest @@ -373,6 +373,21 @@ TEST_F(PacketTest, DccBasicAccyPom) 0b10011100)); } +TEST_F(PacketTest, DccExtAccySet) +{ + pkt_.add_dcc_ext_accessory(1733, 0x5a); + // 1733 = 0x6c5 = 0b 110 1100 0101 + uint8_t b1 = 0b10110001; + uint8_t b2 = 0b00010011; + uint8_t b3 = 0x5a; + EXPECT_THAT(get_packet(), ElementsAre(b1, b2, b3, b1 ^ b2 ^ b3)); + + pkt_.clear(); + pkt_.add_dcc_ext_accessory(1733, 0x80); + b3 = 0x80; + EXPECT_THAT(get_packet(), ElementsAre(b1, b2, b3, b1 ^ b2 ^ b3)); +} + TEST_F(PacketTest, SvcProgDirectByte) { diff --git a/src/dcc/Packet.hxx b/src/dcc/Packet.hxx index c7ca853ad..712191d98 100644 --- a/src/dcc/Packet.hxx +++ b/src/dcc/Packet.hxx @@ -310,6 +310,17 @@ struct Packet : public DCCPacket /// @param is_normal true for normal, false for reverse /// @param is_activate true for activate, false for deactivate void set_dcc_basic_accy_params(bool is_normal, bool is_activate); + + /// Adds a DCC extended accessory decoder command packet and the checksum + /// byte. + /// @param address is the 11-bit binary address, 0..2047. No bits have to be + /// inverted. This will be A10..A0 on the track. (To convert from a user + /// address, see accy_address_user_to_binary in dcc::Defs.) + /// @param aspect is the argument byte to the extended + /// accessory. Traditionally this was used as an aspect for a signal + /// decoder, but different accessories might have different interpretation + /// of it. + void add_dcc_ext_accessory(unsigned address, uint8_t aspect); /// Sets the packet to a logon enable packet. /// @param param defines which decoders should be requested to logon. From 6f70967160ae8f114bfef3637391baa835899e77 Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 1 Feb 2024 06:28:00 -0800 Subject: [PATCH 6/7] Adds well-known range for dcc extended accessories (according to the draft event identifiers standard). --- src/openlcb/TractionDefs.hxx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/openlcb/TractionDefs.hxx b/src/openlcb/TractionDefs.hxx index 2ab6dbdb6..4927b0d4b 100644 --- a/src/openlcb/TractionDefs.hxx +++ b/src/openlcb/TractionDefs.hxx @@ -73,6 +73,8 @@ struct TractionDefs { static constexpr uint64_t ACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE = 0x0101020000FF0000ULL; /// Base address of DCC accessory decoder well-known event range (inactive) static constexpr uint64_t INACTIVATE_BASIC_DCC_ACCESSORY_EVENT_BASE = 0x0101020000FE0000ULL; + /// Base address of DCC extended accessory decoder well-known event range + static constexpr uint64_t EXT_DCC_ACCESSORY_EVENT_BASE = 0x0101020001000000ULL; /// Node ID space allocated for DC blocks. static const uint64_t NODE_ID_DC_BLOCK = 0x060000000000ULL; /// Node ID space allocated for DCC locomotives. From 23683b5602352f7e07cc8a645770dc3851d1305d Mon Sep 17 00:00:00 2001 From: Balazs Racz Date: Thu, 1 Feb 2024 06:28:22 -0800 Subject: [PATCH 7/7] Adds accessory consumer for extended accessories. Generates the dcc packets on the rails. --- src/openlcb/DccAccyConsumer.cxxtest | 62 ++++++++++++ src/openlcb/DccAccyConsumer.hxx | 144 ++++++++++++++++++++++++++++ 2 files changed, 206 insertions(+) diff --git a/src/openlcb/DccAccyConsumer.cxxtest b/src/openlcb/DccAccyConsumer.cxxtest index a09dbcb28..13bbac1f1 100644 --- a/src/openlcb/DccAccyConsumer.cxxtest +++ b/src/openlcb/DccAccyConsumer.cxxtest @@ -140,4 +140,66 @@ TEST_F(DccAccyTest, packet_throw) wait(); } + +class DccExtAccyTest : public AsyncNodeTest +{ +protected: + DccExtAccyTest() + { + wait(); + } + + StrictMock trackSendQueue_; + DccExtAccyConsumer consumer_{node_, &trackSendQueue_}; +}; + +TEST_F(DccExtAccyTest, create) +{ +} + +TEST_F(DccExtAccyTest, global_identify) +{ + clear_expect(true); + // Consumer range for one very long range (11 bit plus 8 bit). + expect_packet(":X194A422AN010102000107FFFF;"); + + // identify events addressed. + send_packet(":X19968111N022A;"); + wait(); +} + + +TEST_F(DccExtAccyTest, identify_unknown) +{ + // unknown identify + send_packet_and_expect_response( + ":X198F4111N0101020001035a77;", ":X194C722AN0101020001035a77;"); +} + +TEST_F(DccExtAccyTest, packet_throw) +{ + uint8_t hdr = 0b01100100; + EXPECT_CALL(trackSendQueue_, + arrived(hdr, ElementsAre(0b10110001, 0b00010011, 0xa5, _))); + + // set to 0xa5 + send_packet(":X195B4111N010102000106c5a5;"); + + + EXPECT_CALL(trackSendQueue_, + arrived(hdr, ElementsAre(0b10110001, 0b00010011, 0x00, _))); + + // set to 0x00 + send_packet(":X195B4111N010102000106c500;"); + + EXPECT_CALL(trackSendQueue_, + arrived(hdr, ElementsAre(0b10110001, 0b00010011, 0xff, _))); + + // set to 0xff + send_packet(":X195B4111N010102000106c5ff;"); + + wait(); +} + + } // namespace openlcb diff --git a/src/openlcb/DccAccyConsumer.hxx b/src/openlcb/DccAccyConsumer.hxx index 5bc716829..f486ea614 100644 --- a/src/openlcb/DccAccyConsumer.hxx +++ b/src/openlcb/DccAccyConsumer.hxx @@ -260,6 +260,150 @@ private: dcc::TrackIf *track_; }; +/// Base (generic protocol) implementation of the DCC extended accessory +/// consumer. Unlike the basic accessory version, this one does not remember +/// the last set state. +class DccExtAccyConsumerBase : public SimpleEventHandler +{ +protected: + /// How may addresses are there for extended accessories. + static constexpr unsigned NUM_ADDRESS = 2048; + /// How may aspects are supported per accessory. + static constexpr unsigned NUM_ASPECT = 256; + /// Total number of events we are listening for. + static constexpr unsigned NUM_EVENT = NUM_ASPECT * NUM_ADDRESS; + + /// Constructs a listener for DCC extended accessory control. + /// @param node is the virtual node that will be listening for events and + /// responding to Identify messages. + DccExtAccyConsumerBase(Node *node) + : node_(node) + { + EventRegistry::instance()->register_handler( + EventRegistryEntry( + this, TractionDefs::EXT_DCC_ACCESSORY_EVENT_BASE), + 11+8 /*number of bits*/); + } + + /// Destructor. + ~DccExtAccyConsumerBase() + { + EventRegistry::instance()->unregister_handler(this); + } + + void handle_identify_global(const EventRegistryEntry ®istry_entry, + EventReport *event, BarrierNotifiable *done) OVERRIDE + { + AutoNotify an(done); + if (event->dst_node && event->dst_node != node_) + { + return; + } + event->event_write_helper<1>()->WriteAsync(node_, + Defs::MTI_CONSUMER_IDENTIFIED_RANGE, WriteHelper::global(), + eventid_to_buffer(EncodeRange( + TractionDefs::EXT_DCC_ACCESSORY_EVENT_BASE, NUM_EVENT - 1)), + done->new_child()); + } + + void handle_event_report(const EventRegistryEntry ®istry_entry, + EventReport *event, BarrierNotifiable *done) override + { + AutoNotify an(done); + if (!parse_event(event->event)) + { + return; + } + send_accy_command(); + } + + void handle_identify_consumer(const EventRegistryEntry &entry, + EventReport *event, BarrierNotifiable *done) override + { + AutoNotify an(done); + if (!parse_event(event->event)) + { + return; + } + event->event_write_helper<1>()->WriteAsync(node_, + Defs::MTI_CONSUMER_IDENTIFIED_UNKNOWN, WriteHelper::global(), + eventid_to_buffer(event->event), done->new_child()); + } + + /// Send the actual accessory command. + virtual void send_accy_command() = 0; + + /// Parses an event into an openlcb accessory offset. + /// @return true if the event is in the accessory range, false if this + /// event can be ignored. + /// @param on_off will be set to true if this is an activate event, false + /// if it is an inactivate event. + /// @param ofs will be set to the offset in the state_ arrays. + /// @param mask will be set to a single bit value that marks the location + /// in the state_ arrays. + bool parse_event(EventId event) + { + if (event >= TractionDefs::EXT_DCC_ACCESSORY_EVENT_BASE && + event < + TractionDefs::EXT_DCC_ACCESSORY_EVENT_BASE + NUM_EVENT) + { + aspect_ = event & 0xff; + dccAddress_ = (event >> 8) & (NUM_ADDRESS - 1); + return true; + } + else + { + return false; + } + } + + + /// Parsed event state: dcc address (0..2047) without inverting or encoding. + unsigned dccAddress_ : 11; + /// Parsed event state: the aspect commanded. + unsigned aspect_ : 8; + + /// OpenLCB node to export the consumer on. + Node *node_; +}; + +/// Specialized (DCC protocol) implementation of a DCC extended accessory +/// consumer. +class DccExtAccyConsumer : public DccExtAccyConsumerBase +{ +public: + /// Constructs a listener for DCC accessory control. + /// @param node is the virtual node that will be listening for events and + /// responding to Identify messages. + /// @param track is the interface through which we will be writing DCC + /// accessory packets. + DccExtAccyConsumer(Node *node, dcc::TrackIf *track) + : DccExtAccyConsumerBase(node) + , track_(track) + { + } + + /// Destructor. + ~DccExtAccyConsumer() + { + } + +private: + /// Send the actual accessory command. + void send_accy_command() override + { + dcc::TrackIf::message_type *pkt; + mainBufferPool->alloc(&pkt); + pkt->data()->add_dcc_ext_accessory(dccAddress_, aspect_); + pkt->data()->packet_header.rept_count = 3; + track_->send(pkt); + } + + /// Track to send DCC packets to. + dcc::TrackIf *track_; +}; + + } // namespace openlcb #endif // _OPENLCB_DCCACCYCONSUMER_HXX_