diff --git a/applications/dcc_cs_login/TrainStorage.hxx b/applications/dcc_cs_login/TrainStorage.hxx new file mode 100644 index 000000000..20e0dff4f --- /dev/null +++ b/applications/dcc_cs_login/TrainStorage.hxx @@ -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 impl_; + std::unique_ptr node_; + std::unique_ptr< + openlcb::FixedEventProducer> + eventProducer_; + }; +}; + +class TrainLogonModule : public dcc::ParameterizedLogonModule { +public: + TrainLogonModule(openlcb::TrainService *s) + : trainService_(s) + { + } + + using Base = ParameterizedLogonModule; + + 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_ diff --git a/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/main.cxx b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/main.cxx index 6df018f66..f9c6799e9 100644 --- a/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/main.cxx +++ b/applications/dcc_cs_login/targets/freertos.armv7m.ek-tm4c123gxl/main.cxx @@ -32,6 +32,8 @@ * @date 11 Aug 2021 */ +#define LOGLEVEL INFO + #include "os/os.h" #include "nmranet_config.h" @@ -39,6 +41,7 @@ #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" @@ -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 @@ -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. @@ -146,12 +155,17 @@ openlcb::FixedEventProducer // ===== 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 logonHandler{ + &trainService, &track, &railcom_hub, &module}; + /** Entry point to application. * @param argc number of command line arguments * @param argv array of command line arguments @@ -187,7 +201,16 @@ int appl_main(int argc, char *argv[]) HubDeviceNonBlock 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 diff --git a/applications/dcc_decoder/main.cxx b/applications/dcc_decoder/main.cxx index 44d38732d..e6dfc72ea 100644 --- a/applications/dcc_decoder/main.cxx +++ b/applications/dcc_decoder/main.cxx @@ -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); @@ -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. @@ -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(); @@ -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. @@ -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_) @@ -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); } @@ -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; diff --git a/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/HwInit.cxx b/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/HwInit.cxx index f1365b38d..71a782777 100644 --- a/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/HwInit.cxx +++ b/applications/dcc_decoder/targets/freertos.armv6m.st-stm32f091rc-nucleo/HwInit.cxx @@ -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; diff --git a/src/dcc/DccDebug.cxx b/src/dcc/DccDebug.cxx index 22f09f7c3..a405de3d4 100644 --- a/src/dcc/DccDebug.cxx +++ b/src/dcc/DccDebug.cxx @@ -87,6 +87,7 @@ string packet_to_string(const DCCPacket &pkt, bool bin_payload) unsigned ofs = 0; bool is_idle_packet = false; bool is_basic_accy_packet = false; + bool is_unknown_packet = false; unsigned accy_address = 0; if (pkt.payload[ofs] == 0xff) { @@ -124,9 +125,26 @@ string packet_to_string(const DCCPacket &pkt, bool bin_payload) addr |= pkt.payload[ofs]; ofs++; options += StringPrintf(" Long Address %u", addr); + } else if (pkt.payload[ofs] == 254) { + options += " Logon packet"; + is_unknown_packet = true; + while (ofs < pkt.dlc) + { + options += StringPrintf(" 0x%02x", pkt.payload[ofs++]); + } + } else if (pkt.payload[ofs] == 253) { + options += " Advanced extended packet"; + is_unknown_packet = true; + while (ofs < pkt.dlc) + { + options += StringPrintf(" 0x%02x", pkt.payload[ofs++]); + } } uint8_t cmd = pkt.payload[ofs]; - ofs++; + if (!is_unknown_packet) + { + ofs++; + } if (is_basic_accy_packet && ((cmd & 0x80) == 0x80)) { accy_address |= cmd & 0b111; @@ -137,6 +155,9 @@ string packet_to_string(const DCCPacket &pkt, bool bin_payload) options += StringPrintf(" Accy %u %s", accy_address, is_activate ? "activate" : "deactivate"); } + else if (is_unknown_packet) + { + } else if ((cmd & 0xC0) == 0x40) { // Speed and direction @@ -280,7 +301,7 @@ string packet_to_string(const DCCPacket &pkt, bool bin_payload) } // checksum of packet - if (ofs == pkt.dlc && pkt.packet_header.skip_ec == 0) + if (ofs == pkt.dlc && (pkt.packet_header.skip_ec == 0 || is_unknown_packet)) { // EC skipped. } diff --git a/src/dcc/Defs.hxx b/src/dcc/Defs.hxx index 732352899..a71b83d8b 100644 --- a/src/dcc/Defs.hxx +++ b/src/dcc/Defs.hxx @@ -120,15 +120,25 @@ enum // Logon commands /// Logon enable in the 254 address partition DCC_LOGON_ENABLE = 0b11111100, + DCC_LOGON_ENABLE_MASK = 0b11111100, /// Select command in the 254 address partition DCC_SELECT = 0b11010000, + DCC_SELECT_MASK = 0b11110000, /// Get Data Start command in the 254 address partition DCC_GET_DATA_START = 0, /// Get Data Continue command in the 254 address partition DCC_GET_DATA_CONT = 1, /// Logon Assign command the 254 address partition DCC_LOGON_ASSIGN = 0b11100000, - + DCC_LOGON_ASSIGN_MASK = 0b11110000, + + /// Minimum value of second byte for DID assigned packets. + DCC_DID_MIN = DCC_SELECT, + /// Maximum value of second byte for DID assigned packets. + DCC_DID_MAX = DCC_LOGON_ASSIGN + 0xF, + /// Mask used for the DCC_DID packets primary command byte. + DCC_DID_MASK = 0xF0, + // Commands in 254 Select and 253 addressed. CMD_READ_SHORT_INFO = 0b11111111, CMD_READ_BLOCK = 0b11111110, @@ -137,6 +147,10 @@ enum // 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. + + /// Mask for the address partition bits. + ADR_MASK = 0b111111, + /// 7-bit mobile decoders ADR_MOBILE_SHORT = 0b00111000, /// Mask for 7-bit mobile decoders diff --git a/src/dcc/Logon.hxx b/src/dcc/Logon.hxx index 7ca9b5e84..f5f86ef33 100644 --- a/src/dcc/Logon.hxx +++ b/src/dcc/Logon.hxx @@ -85,6 +85,10 @@ public: /// @return the address to be assigned to this locomotive. 14-bit. uint16_t assigned_address(unsigned loco_id); + /// Invoked when the address assignment completes for a decoder. + /// @param loco_id which decoder. + void assign_complete(unsigned loco_id); + /// Flags for the logon handler module. enum Flags { @@ -158,7 +162,10 @@ public: /// @return the packet classification wrt the logon feature. PacketType classify_packet(uintptr_t feedback_key) override { - LOG(INFO, "classify %x", (unsigned)feedback_key); + if (feedback_key >= 1 << 14) + { + LOG(INFO, "classify %x", (unsigned)feedback_key); + } if (is_logon_enable_key(feedback_key)) { return LOGON_ENABLE; @@ -197,6 +204,8 @@ public: return; } uint8_t &flags = module_->loco_flags(loco_id); + LOG(INFO, "Select shortinfo for loco ID %d, flags %02x error %d", + loco_id, flags, error); flags &= ~LogonHandlerModule::FLAG_PENDING_GET_SHORTINFO; if (error) { @@ -270,7 +279,7 @@ public: return; } } - flags |= LogonHandlerModule::FLAG_COMPLETE; + module_->assign_complete(loco_id); flags &= ~LogonHandlerModule::FLAG_PENDING_TICK; LOG(INFO, "Assign completed for loco %d address %d", loco_id, module_->assigned_address(loco_id)); @@ -281,7 +290,8 @@ public: /// @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. - void process_decoder_id(uintptr_t feedback_key, bool error, uint64_t data) + void process_decoder_id( + uintptr_t feedback_key, bool error, uint64_t data) override { timer_.ensure_triggered(); if (data) @@ -291,6 +301,12 @@ public: // No railcom feedback returned. return; } + if (LOGLEVEL >= INFO) + { + unsigned didh = (data >> 32) & 0xfff; + unsigned didl = data & 0xffffffffu; + LOG(INFO, "Decoder id %03x%08x error %d", didh, didl, error); + } if (error) { hasLogonEnableConflict_ = 1; diff --git a/src/dcc/LogonModule.hxx b/src/dcc/LogonModule.hxx index 66649a102..f7032486c 100644 --- a/src/dcc/LogonModule.hxx +++ b/src/dcc/LogonModule.hxx @@ -44,11 +44,12 @@ namespace dcc { /// Default implementation of the storage and policy module for trains. -class DefaultLogonModule : public LogonHandlerModule +template +class ParameterizedLogonModule : public LogonHandlerModule { public: /// We store this structure about each locomotive. - struct LocoInfo + struct LocoInfo : public Base::Storage { /// State machine flags about this loco. uint8_t flags_ {0}; @@ -140,9 +141,23 @@ public: return locos_[loco_id].assignedAddress_; } + /// Invoked when the address assignment completes for a decoder. + /// @param loco_id which decoder. + void assign_complete(unsigned loco_id) + { + loco_flags(loco_id) |= LogonHandlerModule::FLAG_COMPLETE; + } + uint16_t nextAddress_ {(Defs::ADR_MOBILE_LONG << 8) + 10000}; -}; // class DefaultLogonModule +}; // class ParameterizedLogonModule + +class DefaultBase { +public: + struct Storage {}; +}; + +using DefaultLogonModule = ParameterizedLogonModule; } // namespace dcc