diff --git a/src/dcc/Logon.hxx b/src/dcc/Logon.hxx new file mode 100644 index 000000000..a436b62aa --- /dev/null +++ b/src/dcc/Logon.hxx @@ -0,0 +1,88 @@ +/** \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 Logon.hxx + * Control flows for supporting DCC Automatic Logon. + * + * @author Balazs Racz + * @date 12 Aug 2021 + */ + +#ifndef _DCC_LOGON_HXX_ +#define _DCC_LOGON_HXX_ + +#include "dcc/PacketSource.hxx" +#include "dcc/TrackIf.hxx" +#include "dcc/UpdateLoop.hxx" +#include "dcc/LogonFeedback.hxx" +#include "executor/StateFlow.hxx" + +namespace dcc +{ + +/// This class needs to be a base class for the template argument of the Logon +/// Handler. +class LogonHandlerModule +{ + +}; // LogonHandlerModule + +/// Handles the automatic logon flow for DCC decoders. +template +class LogonHandler : public NonTrainPacketSource, + public StateFlowBase, + public RailcomHubPortInterface +{ +public: + /// Constructor + /// + /// @param service points to the executor to use. + /// @param track pointer to the track interface to send DCC packets to. + /// @param rcom_hub will register to this railcom hub to get feedback. + LogonHandler(Service *service, TrackIf *track, RailcomHubFlow *rcom_hub) + : StateFlowBase(service) + , trackIf_(track) + { + } + + /// Initiates a logon sequence at startup. + void startup_logon() + { + start_flow(STATE(startup_logon)); + } + +private: + Action startup_logon() + { + } + + /// If we need to send packets to the track, we can do it here directly. + TrackIf *trackIf_; +}; // LogonHandler + +} // namespace dcc + +#endif // _DCC_LOGON_HXX_ diff --git a/src/dcc/LogonFeedback.cxxtest b/src/dcc/LogonFeedback.cxxtest new file mode 100644 index 000000000..6736e266c --- /dev/null +++ b/src/dcc/LogonFeedback.cxxtest @@ -0,0 +1,220 @@ +#include "dcc/LogonFeedback.hxx" + +#include "utils/test_main.hxx" + +using ::testing::Return; + +namespace dcc +{ + +class MockFeedbackCallbacks : public LogonFeedbackCallbacks +{ +public: + MOCK_METHOD1( + classify_packet, LogonFeedbackCallbacks::PacketType(uintptr_t)); + MOCK_METHOD3(process_select_shortinfo, + void(uintptr_t feedback_key, bool error, uint64_t data)); + + MOCK_METHOD3(process_logon_assign, + void(uintptr_t feedback_key, bool error, uint64_t data)); + + MOCK_METHOD3(process_decoder_id, + void(uintptr_t feedback_key, bool error, uint64_t data)); +}; + +class ParseFeedbackTest : public ::testing::Test +{ +protected: + /// Creates a valid 8-byte railcom feedback in fb_. + void create_valid_code() + { + RailcomDefs::append12(15, 0x44, fb_.ch1Data); + fb_.ch1Size = 2; + + RailcomDefs::append36(0xa, 0x11223344, fb_.ch2Data); + fb_.ch2Size = 6; + } + + /// Sends the current value in fb_ to the railcom hub. + void send() + { + auto *b = hub_.alloc(); + b->data()->value() = fb_; + hub_.send(b); + wait_for_main_executor(); + } + + ::testing::StrictMock cb_; + dcc::Feedback fb_; + RailcomHubFlow hub_ {&g_service}; + LogonFeedbackParser parser_ {&cb_, &hub_}; +}; + +TEST_F(ParseFeedbackTest, valid_code) +{ + create_valid_code(); + uint64_t d = LogonFeedbackParser::parse_code(&fb_); + EXPECT_EQ(0u, d >> LogonFeedbackParser::ERROR_SHIFT); + EXPECT_EQ(8u, ((d >> LogonFeedbackParser::LENGTH_SHIFT) & 0xff)); + EXPECT_EQ(0xf44a11223344u, (d & LogonFeedbackParser::PAYLOAD_MASK)); +} + +TEST_F(ParseFeedbackTest, conflict_code) +{ + create_valid_code(); + fb_.ch1Data[1] |= 0xFF; + uint64_t d = LogonFeedbackParser::parse_code(&fb_); + EXPECT_EQ(LogonFeedbackParser::ERROR_GARBAGE | + LogonFeedbackParser::ERROR_OUT_OF_ORDER, + d & LogonFeedbackParser::ERROR_MASK); + EXPECT_EQ(7u, ((d >> LogonFeedbackParser::LENGTH_SHIFT) & 0xff)); + // Some bits are blanked out. + EXPECT_EQ(0xf40a11223344u, (d & LogonFeedbackParser::PAYLOAD_MASK)); +} + +TEST_F(ParseFeedbackTest, short_code) +{ + create_valid_code(); + fb_.ch2Size = 4; + uint64_t d = LogonFeedbackParser::parse_code(&fb_); + EXPECT_EQ(LogonFeedbackParser::ERROR_MISSING_DATA, + d & LogonFeedbackParser::ERROR_MASK); + EXPECT_EQ(6u, ((d >> LogonFeedbackParser::LENGTH_SHIFT) & 0xff)); + // Some bits are blanked out. + EXPECT_EQ(0xf44a11223000u, (d & LogonFeedbackParser::PAYLOAD_MASK)); +} + +TEST_F(ParseFeedbackTest, short_code_ack) +{ + create_valid_code(); + RailcomDefs::append36(0xa, 0x11220000, fb_.ch2Data); + fb_.ch2Data[5] = RailcomDefs::CODE_ACK; + fb_.ch2Data[4] = RailcomDefs::CODE_ACK2; + uint64_t d = LogonFeedbackParser::parse_code(&fb_); + EXPECT_EQ( + LogonFeedbackParser::ERROR_ACK, d & LogonFeedbackParser::ERROR_MASK); + EXPECT_EQ(6u, ((d >> LogonFeedbackParser::LENGTH_SHIFT) & 0xff)); + // Some bits are blanked out. + EXPECT_EQ(0xf44a11220000u, (d & LogonFeedbackParser::PAYLOAD_MASK)); +} + +TEST_F(ParseFeedbackTest, out_of_order_ack) +{ + create_valid_code(); + fb_.ch1Data[1] = RailcomDefs::CODE_ACK; + uint64_t d = LogonFeedbackParser::parse_code(&fb_); + EXPECT_EQ(LogonFeedbackParser::ERROR_ACK | + LogonFeedbackParser::ERROR_OUT_OF_ORDER, + d & LogonFeedbackParser::ERROR_MASK); + EXPECT_EQ(7u, ((d >> LogonFeedbackParser::LENGTH_SHIFT) & 0xff)); + // Some bits are blanked out. + EXPECT_EQ(0xf40a11223344u, (d & LogonFeedbackParser::PAYLOAD_MASK)); +} + +TEST_F(ParseFeedbackTest, one_ack) +{ + fb_.ch1Data[0] = RailcomDefs::CODE_ACK; + fb_.ch1Size = 1; + fb_.ch2Size = 0; + uint64_t d = LogonFeedbackParser::parse_code(&fb_); + EXPECT_EQ(LogonFeedbackParser::ERROR_ACK | + LogonFeedbackParser::ERROR_MISSING_DATA, + d & LogonFeedbackParser::ERROR_MASK); + EXPECT_EQ(0u, ((d >> LogonFeedbackParser::LENGTH_SHIFT) & 0xff)); + EXPECT_EQ(0u, (d & LogonFeedbackParser::PAYLOAD_MASK)); +} + +TEST_F(ParseFeedbackTest, unknown) +{ + create_valid_code(); + fb_.ch1Data[1] = RailcomDefs::CODE_BUSY; + uint64_t d = LogonFeedbackParser::parse_code(&fb_); + EXPECT_EQ(LogonFeedbackParser::ERROR_UNKNOWN | + LogonFeedbackParser::ERROR_OUT_OF_ORDER, + d & LogonFeedbackParser::ERROR_MASK); + EXPECT_EQ(7u, ((d >> LogonFeedbackParser::LENGTH_SHIFT) & 0xff)); + // Some bits are blanked out. + EXPECT_EQ(0xf40a11223344u, (d & LogonFeedbackParser::PAYLOAD_MASK)); +} + +TEST_F(ParseFeedbackTest, decoder_id_reply) +{ + constexpr uint64_t decoder_id = 0x79944332211ull; + constexpr uintptr_t key = 0x5a5a5a5a; + RailcomDefs::add_did_feedback(decoder_id, &fb_); + fb_.feedbackKey = key; + ::testing::InSequence sq; + EXPECT_CALL(cb_, classify_packet(key)) + .WillOnce(Return(LogonFeedbackCallbacks::LOGON_ENABLE)); + EXPECT_CALL( + cb_, process_decoder_id(key, false, decoder_id | (15ull << 44))); + send(); +} + +TEST_F(ParseFeedbackTest, short_info_reply) +{ + constexpr uintptr_t key = 0x5a5a5a5a; + RailcomDefs::add_shortinfo_feedback(0x1382, 0x55, 0xa7, 0x03, &fb_); + fb_.feedbackKey = key; + ::testing::InSequence sq; + EXPECT_CALL(cb_, classify_packet(key)) + .WillOnce(Return(LogonFeedbackCallbacks::SELECT_SHORTINFO)); + EXPECT_CALL(cb_, process_select_shortinfo(key, false, 0x938255a7030bull)); + send(); +} + +TEST_F(ParseFeedbackTest, short_info_crc_error) +{ + constexpr uintptr_t key = 0x5a5a5a5a; + RailcomDefs::add_shortinfo_feedback(0x1382, 0x55, 0xa7, 0x03, &fb_); + fb_.feedbackKey = key; + fb_.ch2Data[5] = railcom_encode[0x3f]; + ::testing::InSequence sq; + EXPECT_CALL(cb_, classify_packet(key)) + .WillOnce(Return(LogonFeedbackCallbacks::SELECT_SHORTINFO)); + // Reports error + EXPECT_CALL(cb_, process_select_shortinfo(key, true, 0x938255a7033full)); + send(); +} + +TEST_F(ParseFeedbackTest, short_info_garbage) +{ + constexpr uintptr_t key = 0x5a5a5a5a; + RailcomDefs::add_shortinfo_feedback(0x1382, 0x55, 0xa7, 0x03, &fb_); + fb_.feedbackKey = key; + fb_.ch2Data[5] = 0xF3; // invalid 4/8 code + ::testing::InSequence sq; + EXPECT_CALL(cb_, classify_packet(key)) + .WillOnce(Return(LogonFeedbackCallbacks::SELECT_SHORTINFO)); + // Reports error + EXPECT_CALL(cb_, process_select_shortinfo(key, true, 0x938255a70300ull)); + send(); +} + +TEST_F(ParseFeedbackTest, assign_response) +{ + constexpr uintptr_t key = 0x5a5a5a5a; + RailcomDefs::add_assign_feedback(0x5a, 0x327, 0x18, 0x22, &fb_); + fb_.feedbackKey = key; + ::testing::InSequence sq; + EXPECT_CALL(cb_, classify_packet(key)) + .WillOnce(Return(LogonFeedbackCallbacks::LOGON_ASSIGN)); + EXPECT_CALL(cb_, process_logon_assign(key, false, 0xd5a327182246ull)); + send(); +} + +TEST_F(ParseFeedbackTest, assign_response_crc_error) +{ + constexpr uintptr_t key = 0x5a5a5a5a; + RailcomDefs::add_assign_feedback(0x5a, 0x327, 0x18, 0x22, &fb_); + fb_.feedbackKey = key; + fb_.ch2Data[5] = railcom_encode[0x3f]; + ::testing::InSequence sq; + EXPECT_CALL(cb_, classify_packet(key)) + .WillOnce(Return(LogonFeedbackCallbacks::LOGON_ASSIGN)); + // Reports error + EXPECT_CALL(cb_, process_logon_assign(key, true, 0xd5a32718227full)); + send(); +} + +} // namespace dcc diff --git a/src/dcc/LogonFeedback.hxx b/src/dcc/LogonFeedback.hxx new file mode 100644 index 000000000..b8a90f85b --- /dev/null +++ b/src/dcc/LogonFeedback.hxx @@ -0,0 +1,278 @@ +/** \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 LogonFeedback.hxx + * Parser for RailCom feedback that recognizes logon messages. + * + * @author Balazs Racz + * @date 12 Aug 2021 + */ + +#ifndef _DCC_LOGONFEEDBACK_HXX_ +#define _DCC_LOGONFEEDBACK_HXX_ + +#include "dcc/RailcomHub.hxx" +#include "utils/Crc.hxx" + +namespace dcc +{ + +/// Abstract class to get callbacks for recognized feedback messages +class LogonFeedbackCallbacks +{ +public: + enum PacketType + { + /// Non-254 packet or not known (not relevant) 254 packet type + UNKNOWN = 0, + /// Get Data Start packet + GET_DATA_START, + /// Get Data Continue packet + GET_DATA_CONT, + /// Logon Enable packet + LOGON_ENABLE, + /// Select packet with Get Short Info command + SELECT_SHORTINFO, + /// Logon Assign packet + LOGON_ASSIGN, + /// Misc 254 packet (ID based responses) + MISC_254 + }; + + /// Determines based on feedback key what the given DCC packet was. + /// @param feedback_key from the railcom packet. + /// @return the packet classification wrt the logon feature. + virtual PacketType classify_packet(uintptr_t feedback_key) = 0; + + /// Handles a Select ShortInfo feedback message. + /// @param feedback_key refers to the packet it came from. + /// @param error true if there was a transmission error or the data came in + /// incorrect format. + /// @param data 48 bits of payload. + virtual void process_select_shortinfo( + uintptr_t feedback_key, bool error, uint64_t data) = 0; + + /// Handles a Logon Assign feedback message. + /// @param feedback_key refers to the packet it came from. + /// @param error true if there was a transmission error or the data came in + /// incorrect format. + /// @param data 48 bits of payload. + virtual void process_logon_assign( + uintptr_t feedback_key, bool error, uint64_t data) = 0; + + /// Handles a Decoder ID feedback message. + /// @param feedback_key refers to the packet it came from. + /// @param error true if there was a transmission error or the data came in + /// incorrect format. + /// @param data 48 bits of payload. The low 44 bits of this is a decoder ID. + virtual void process_decoder_id( + uintptr_t feedback_key, bool error, uint64_t data) = 0; +}; + +/// Parser for RailCom feedback that recognizes logon messages. +class LogonFeedbackParser : public RailcomHubPortInterface +{ +public: + using PacketType = LogonFeedbackCallbacks::PacketType; + /// Constructor. + /// @param cb recognized packets get calls on this object. + /// @param hub the railcom hub for gathering feedback. + LogonFeedbackParser(LogonFeedbackCallbacks *cb, RailcomHubFlow *hub) + : cb_(cb) + , hub_(hub) + { + hub_->register_port(this); + } + + ~LogonFeedbackParser() + { + hub_->unregister_port(this); + } + + /// Receives railcom feedback. + void send(Buffer *b, unsigned priority) override + { + auto rb = get_buffer_deleter(b); + PacketType type = cb_->classify_packet(b->data()->feedbackKey); + if (type == PacketType::UNKNOWN) + { + return; + } + uint64_t data = parse_code(b->data()); + bool any_error = data & ERROR_MASK; + auto key = b->data()->feedbackKey; + switch (type) + { + case PacketType::SELECT_SHORTINFO: + any_error |= has_crc_error(data); + any_error |= (((data >> 47) & 1) != 1); + cb_->process_select_shortinfo( + key, any_error, data & PAYLOAD_MASK); + break; + case PacketType::LOGON_ASSIGN: + any_error |= has_crc_error(data); + any_error |= + (((data >> 44) & 0xf) != RMOB_LOGON_ASSIGN_FEEDBACK); + cb_->process_logon_assign(key, any_error, data & PAYLOAD_MASK); + break; + case PacketType::LOGON_ENABLE: + any_error |= + (((data >> 44) & 0xf) != RMOB_LOGON_ENABLE_FEEDBACK); + cb_->process_decoder_id(key, any_error, data & PAYLOAD_MASK); + break; + case PacketType::GET_DATA_START: + case PacketType::GET_DATA_CONT: + case PacketType::MISC_254: + case PacketType::UNKNOWN: + default: + break; + } + } + +#ifndef GTEST +private: +#endif + enum Errors + { + /// Which bit offset do the error bits start. + ERROR_SHIFT = 56, + /// Mask where the error bits are. + ERROR_MASK = 0xffULL << ERROR_SHIFT, + /// Which bit offset do the error bits start. + LENGTH_SHIFT = 48, + /// Counts the number of valid 6-bit counts. + LENGTH_OFFSET = 1ULL << LENGTH_SHIFT, + /// Which bit offset do the error bits start. + LENGTH_MASK = 0xffULL << LENGTH_SHIFT, + /// Mask where the decoded payload is. + PAYLOAD_MASK = LENGTH_OFFSET - 1, + + /// Not enough bytes in the feedback response. + ERROR_MISSING_DATA = 1ULL << ERROR_SHIFT, + /// Found an ACK byte. + ERROR_ACK = 2ULL << ERROR_SHIFT, + /// Found valid data past an ACK byte or a missing byte. + ERROR_OUT_OF_ORDER = 4ULL << ERROR_SHIFT, + /// Found invalid 6/8 code. + ERROR_GARBAGE = 8ULL << ERROR_SHIFT, + /// Found unknown codepoint (e.g. NACK or BUSY). + ERROR_UNKNOWN = 16ULL << ERROR_SHIFT, + }; + + /// Appends 6 bits of incoming data from railcom. + /// @param data the 48-bit aggregated data. + /// @param shift where the next 6 bits should be at (0 to 42) + /// @param next_byte the next byte from the uart. + static void append_data(uint64_t &data, unsigned &shift, uint8_t next_byte) + { + uint8_t code = railcom_decode[next_byte]; + if (code < 64) + { + data |= ((uint64_t)code) << shift; + if (data >> ERROR_SHIFT) + { + // Valid data beyond some error. + data |= ERROR_OUT_OF_ORDER; + } + data += LENGTH_OFFSET; + } + else if (code == RailcomDefs::ACK) + { + data |= ERROR_ACK; + } + else if (code == RailcomDefs::INV) + { + data |= ERROR_GARBAGE; + } + else + { + data |= ERROR_UNKNOWN; + } + shift -= 6; + } + + /// Parses a concatenated ch1+ch2 response into a single uint64_t. It + /// contains error flags in the MSB, the number of valid 6-bit codepoints + /// in the second MSB , and 6 bytes data in the LSB, entered with the first + /// wire byte in the third MSB and last wire byte in the LSB. + /// + /// Any input byte that was missing or contained any other codepoint than a + /// valid 6-bit data codepoint (including garbage or ACK) is translated + /// into zero bits in the parse result. + /// @param fb feedback to parse + /// @return parse result. + static uint64_t parse_code(const Feedback *fb) + { + uint64_t data = 0; + unsigned shift = 48 - 6; + for (unsigned i = 0; i < 2; i++) + { + if (fb->ch1Size > i) + { + append_data(data, shift, fb->ch1Data[i]); + } + else + { + data |= ERROR_MISSING_DATA; + } + } + for (unsigned i = 0; i < 6; i++) + { + if (fb->ch2Size > i) + { + append_data(data, shift, fb->ch2Data[i]); + } + else + { + data |= ERROR_MISSING_DATA; + } + } + return data; + } + + /// Checks the CRC8 on a 6-byte payload in this feedback. + /// @param data the feedback bytes (right aligned, first is MSB). + /// @return true if there is a CRC error + static bool has_crc_error(uint64_t data) + { + Crc8DallasMaxim m; + for (int i = 48 - 8; i >= 0; i -= 8) + { + m.update16((data >> i) & 0xff); + } + return !m.check_ok(); + } + +private: + /// Callbacks object. + LogonFeedbackCallbacks *cb_; + /// The railcom hub we are registered to. + RailcomHubFlow *hub_; +}; + +} // namespace dcc + +#endif // _DCC_LOGONFEEDBACK_HXX_ diff --git a/src/dcc/RailCom.cxx b/src/dcc/RailCom.cxx index ef111539e..25db547ab 100644 --- a/src/dcc/RailCom.cxx +++ b/src/dcc/RailCom.cxx @@ -35,6 +35,7 @@ #include #include "dcc/RailCom.hxx" +#include "utils/Crc.hxx" namespace dcc { static constexpr uint8_t INV = RailcomDefs::INV; @@ -300,4 +301,60 @@ void parse_railcom_data( } } +// static +void RailcomDefs::add_did_feedback(uint64_t decoder_id, Feedback *fb) +{ + fb->ch1Size = 2; + fb->ch2Size = 6; + append12( + RMOB_LOGON_ENABLE_FEEDBACK, (decoder_id >> 36) & 0xff, fb->ch1Data); + append36((decoder_id >> 32) & 0xf, (decoder_id & 0xffffffffu), fb->ch2Data); +} + +// static +void RailcomDefs::add_shortinfo_feedback(uint16_t requested_address, + uint8_t max_fn, uint8_t psupp, uint8_t ssupp, Feedback *fb) +{ + Crc8DallasMaxim m; + requested_address &= 0x3FFF; + requested_address |= 0x8000; + m.update16(requested_address >> 8); + m.update16(requested_address & 0xff); + m.update16(max_fn); + m.update16(psupp); + m.update16(ssupp); + fb->ch1Size = 2; + fb->ch2Size = 6; + append12( + requested_address >> 12, (requested_address >> 4) & 0xff, fb->ch1Data); + uint32_t lp = (uint32_t(max_fn) << 24) | (uint32_t(psupp) << 16) | + (uint32_t(ssupp) << 8) | m.get(); + append36(requested_address & 0xf, lp, fb->ch2Data); +} + +// static +void RailcomDefs::add_assign_feedback(uint8_t changeflags, uint16_t changecount, + uint8_t supp2, uint8_t supp3, Feedback *fb) +{ + changecount &= 0xFFF; + fb->ch1Size = 2; + fb->ch2Size = 6; + + Crc8DallasMaxim m; + uint8_t h = (RMOB_LOGON_ASSIGN_FEEDBACK << 4) | (changeflags >> 4); + m.update16(h); + h = ((changeflags & 0xf) << 4) | (changecount >> 8); + m.update16(h); + append12(RMOB_LOGON_ASSIGN_FEEDBACK, changeflags, fb->ch1Data); + + h = changecount & 0xff; + m.update16(h); + m.update16(supp2); + m.update16(supp3); + + uint32_t lp = + ((changecount & 0xff) << 24) | (supp2 << 16) | (supp3 << 8) | m.get(); + append36(changecount >> 8, lp, fb->ch2Data); +} + } // namespace dcc diff --git a/src/dcc/RailCom.cxxtest b/src/dcc/RailCom.cxxtest index 0536bfb8c..de95134b2 100644 --- a/src/dcc/RailCom.cxxtest +++ b/src/dcc/RailCom.cxxtest @@ -193,4 +193,77 @@ TEST_F(RailcomDecodeTest, Append36) { ElementsAre(RailcomPacket(3, 2, RailcomPacket::MOB_XPOM2, 0xfedc5432u))); } +TEST_F(RailcomDecodeTest, DecoderId) +{ + uint64_t decoder_id = 0x19911223344ull; + RailcomDefs::add_did_feedback(decoder_id, &fb_); + + EXPECT_EQ(2u, fb_.ch1Size); + EXPECT_EQ(6u, fb_.ch2Size); + uint8_t d[2]; + RailcomDefs::append12(0xf, 0x19, d); + EXPECT_EQ(d[0], fb_.ch1Data[0]); + EXPECT_EQ(d[1], fb_.ch1Data[1]); + + RailcomDefs::append12(0x9, 0x11, d); + EXPECT_EQ(d[0], fb_.ch2Data[0]); + EXPECT_EQ(d[1], fb_.ch2Data[1]); + + RailcomDefs::append12(0x2, 0x23, d); + EXPECT_EQ(d[0], fb_.ch2Data[2]); + EXPECT_EQ(d[1], fb_.ch2Data[3]); + + RailcomDefs::append12(0x3, 0x44, d); + EXPECT_EQ(d[0], fb_.ch2Data[4]); + EXPECT_EQ(d[1], fb_.ch2Data[5]); +} + +TEST_F(RailcomDecodeTest, ShortInfo) +{ + RailcomDefs::add_shortinfo_feedback(0x1382, 0x55, 0xa7, 0x03, &fb_); + EXPECT_EQ(2u, fb_.ch1Size); + EXPECT_EQ(6u, fb_.ch2Size); + uint8_t d[2]; + RailcomDefs::append12(0x8 | 0x1, 0x38, d); + EXPECT_EQ(d[0], fb_.ch1Data[0]); + EXPECT_EQ(d[1], fb_.ch1Data[1]); + + RailcomDefs::append12(0x2, 0x55, d); + EXPECT_EQ(d[0], fb_.ch2Data[0]); + EXPECT_EQ(d[1], fb_.ch2Data[1]); + + RailcomDefs::append12(0xa, 0x70, d); + EXPECT_EQ(d[0], fb_.ch2Data[2]); + EXPECT_EQ(d[1], fb_.ch2Data[3]); + + // The CRC of this example is 0x0b. + RailcomDefs::append12(0x3, 0x0b, d); + EXPECT_EQ(d[0], fb_.ch2Data[4]); + EXPECT_EQ(d[1], fb_.ch2Data[5]); +} + +TEST_F(RailcomDecodeTest, AssignFeedback) +{ + RailcomDefs::add_assign_feedback(0x5a, 0x327, 0x18, 0x22, &fb_); + EXPECT_EQ(2u, fb_.ch1Size); + EXPECT_EQ(6u, fb_.ch2Size); + uint8_t d[2]; + RailcomDefs::append12(13, 0x5a, d); + EXPECT_EQ(d[0], fb_.ch1Data[0]); + EXPECT_EQ(d[1], fb_.ch1Data[1]); + + RailcomDefs::append12(0x3, 0x27, d); + EXPECT_EQ(d[0], fb_.ch2Data[0]); + EXPECT_EQ(d[1], fb_.ch2Data[1]); + + RailcomDefs::append12(0x1, 0x82, d); + EXPECT_EQ(d[0], fb_.ch2Data[2]); + EXPECT_EQ(d[1], fb_.ch2Data[3]); + + // The CRC of this example is 0x46. + RailcomDefs::append12(0x2, 0x46, d); + EXPECT_EQ(d[0], fb_.ch2Data[4]); + EXPECT_EQ(d[1], fb_.ch2Data[5]); +} + } // namespace dcc diff --git a/src/dcc/RailCom.hxx b/src/dcc/RailCom.hxx index bafe54b16..3ffd5ce56 100644 --- a/src/dcc/RailCom.hxx +++ b/src/dcc/RailCom.hxx @@ -161,7 +161,30 @@ struct RailcomDefs *dst++ = railcom_encode[(data >> 6) & 0x3F]; *dst++ = railcom_encode[data & 0x3F]; } - + + /// Creates a Logon Enable feedback with the decoder unique ID. + /// @param decoder_id the 44-bit decoder ID (justified to MSb). + /// @param fb the feedback packet to generate. + static void add_did_feedback(uint64_t decoder_id, Feedback *fb); + + /// Creates a ShortInfo feedback. + /// @param requested_address 14-bit encoding of the requested address. + /// @param max_fn maximum supported function (0-255) + /// @param psupp protocol support flags (capabilities[0]) + /// @param ssupp space support flags (capabilities[1]) + /// @param fb the feedback packet to generate. + static void add_shortinfo_feedback(uint16_t requested_address, + uint8_t max_fn, uint8_t psupp, uint8_t ssupp, Feedback *fb); + + /// Creates a Logon Assign feedback. + /// @param changeflags 8 bits of change flags + /// @param changecount 12 bits of changecount + /// @param supp2 protocol support flags (capabilities[2]) + /// @param supp3 protocol support flags (capabilities[3]) + /// @param fb the feedback packet to generate. + static void add_assign_feedback(uint8_t changeflags, uint16_t changecount, + uint8_t supp2, uint8_t supp3, Feedback *fb); + private: /// This struct cannot be instantiated. RailcomDefs(); @@ -181,6 +204,8 @@ enum RailcomMobilePacketId RMOB_XPOM2 = 10, RMOB_XPOM3 = 11, RMOB_SUBID = 12, + RMOB_LOGON_ASSIGN_FEEDBACK = 13, + RMOB_LOGON_ENABLE_FEEDBACK = 15, }; /// Represents a single Railcom datagram. There can be multiple railcom diff --git a/src/dcc/TrackIf.hxx b/src/dcc/TrackIf.hxx new file mode 100644 index 000000000..e013bc91f --- /dev/null +++ b/src/dcc/TrackIf.hxx @@ -0,0 +1,47 @@ +/** \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 TrackIf.hxx + * Helper definitions for classes using track interface directly. + * + * @author Balazs Racz + * @date 12 Aug 2021 + */ + +#ifndef _DCC_TRACKIF_HXX_ +#define _DCC_TRACKIF_HXX_ + +#include "dcc/Packet.hxx" +#include "executor/StateFlow.hxx" + +namespace dcc +{ + +using TrackIf = FlowInterface>; + +} // namespace dcc + +#endif // _DCC_TRACKIF_HXX_ diff --git a/src/utils/Crc.cxxtest b/src/utils/Crc.cxxtest index b96f25b0e..c21e7da50 100644 --- a/src/utils/Crc.cxxtest +++ b/src/utils/Crc.cxxtest @@ -175,3 +175,28 @@ TEST(Crc8Test, Collision) LOG(INFO, "s1=%s s2=%s", string_to_hex(s1).c_str(), string_to_hex(s2).c_str()); } + +// These examples contain specific messages from other unittests and print +// their CRC. This pattern can be used for developing other tests. There are no +// expectations in these tests. +TEST(Crc8Test, LogonExample) +{ + Crc8DallasMaxim m; + m.update256(0x93); + m.update256(0x82); + m.update256(0x55); + m.update256(0xa7); + m.update256(0x03); + LOG(INFO, "example: 0x%02x", m.get()); +} + +TEST(Crc8Test, LogonExample2) +{ + Crc8DallasMaxim m; + m.update256(0xD5); + m.update256(0xA3); + m.update256(0x27); + m.update256(0x18); + m.update256(0x22); + LOG(INFO, "example: 0x%02x", m.get()); +}