diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index b0f897cc..5dd7112b 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -35,6 +35,8 @@ file(GLOB SOURCES "src/hardware/input/*.cpp" "src/hardware/protocol/*.hpp" "src/hardware/protocol/*.cpp" + "src/hardware/protocol/loconet/*.hpp" + "src/hardware/protocol/loconet/*.cpp" "src/utils/*.hpp" "src/utils/*.cpp" "thirdparty/boost/libs/program_options/src/*.cpp") diff --git a/server/src/hardware/commandstation/loconetserial.cpp b/server/src/hardware/commandstation/loconetserial.cpp index 86f33703..482135c7 100644 --- a/server/src/hardware/commandstation/loconetserial.cpp +++ b/server/src/hardware/commandstation/loconetserial.cpp @@ -25,6 +25,8 @@ #include "../../core/traintastic.hpp" #include "../../core/eventloop.hpp" +using namespace Protocol::LocoNet; + namespace Hardware::CommandStation { LocoNetSerial::LocoNetSerial(const std::weak_ptr& world, std::string_view _id) : @@ -64,7 +66,7 @@ LocoNetSerial::LocoNetSerial(const std::weak_ptr& world, std::string_view loconet{this, "loconet", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject} { name = "LocoNet (serial)"; - loconet.setValueInternal(std::make_shared<::Protocol::LocoNet>(*this, loconet.name(), std::bind(&LocoNetSerial::send, this, std::placeholders::_1))); + loconet.setValueInternal(std::make_shared(*this, loconet.name(), std::bind(&LocoNetSerial::send, this, std::placeholders::_1))); port.addAttributeEnabled(!online); interface.addAttributeEnabled(!online); @@ -103,9 +105,9 @@ void LocoNetSerial::emergencyStopChanged(bool value) CommandStation::emergencyStopChanged(value); if(value) - send(Protocol::LocoNet::Idle()); + send(Idle()); else if(!trackVoltageOff) - send(Protocol::LocoNet::GlobalPowerOn()); + send(GlobalPowerOn()); } void LocoNetSerial::trackVoltageOffChanged(bool value) @@ -113,9 +115,9 @@ void LocoNetSerial::trackVoltageOffChanged(bool value) CommandStation::trackVoltageOffChanged(value); if(!value) - send(Protocol::LocoNet::GlobalPowerOn()); + send(GlobalPowerOn()); else - send(Protocol::LocoNet::GlobalPowerOff()); + send(GlobalPowerOff()); } void LocoNetSerial::decoderChanged(const Hardware::Decoder& decoder, Hardware::DecoderChangeFlags changes, uint32_t functionNumber) diff --git a/server/src/hardware/commandstation/loconetserial.hpp b/server/src/hardware/commandstation/loconetserial.hpp index 27ade21b..e8136f8a 100644 --- a/server/src/hardware/commandstation/loconetserial.hpp +++ b/server/src/hardware/commandstation/loconetserial.hpp @@ -24,7 +24,7 @@ #define TRAINTASTIC_SERVER_HARDWARE_COMMANDSTATION_LOCONETSERIAL_HPP #include "commandstation.hpp" -#include "../protocol/loconet.hpp" +#include "../protocol/loconet/loconet.hpp" #include "../../enum/loconetserialinterface.hpp" #include "../../enum/serialflowcontrol.hpp" #include @@ -56,7 +56,7 @@ class LocoNetSerial : public CommandStation Property interface; Property baudrate; Property flowControl; - ObjectProperty<::Protocol::LocoNet> loconet; + ObjectProperty<::Protocol::LocoNet::LocoNet> loconet; LocoNetSerial(const std::weak_ptr& world, std::string_view _id); }; diff --git a/server/src/hardware/commandstation/z21.cpp b/server/src/hardware/commandstation/z21.cpp index 8dbe4d9a..2d81e3c8 100644 --- a/server/src/hardware/commandstation/z21.cpp +++ b/server/src/hardware/commandstation/z21.cpp @@ -82,7 +82,7 @@ Z21::Z21(const std::weak_ptr& world, std::string_view _id) : shortCircutExternal{this, "short_circut_external", false, PropertyFlags::ReadOnly} { name = "Z21"; - loconet.setValueInternal(std::make_shared<::Protocol::LocoNet>(*this, loconet.name(), + loconet.setValueInternal(std::make_shared<::Protocol::LocoNet::LocoNet>(*this, loconet.name(), [/*this*/](const ::Protocol::LocoNet::Message& /*msg*/) { return false; diff --git a/server/src/hardware/commandstation/z21.hpp b/server/src/hardware/commandstation/z21.hpp index f7dc23f2..9fb69b12 100644 --- a/server/src/hardware/commandstation/z21.hpp +++ b/server/src/hardware/commandstation/z21.hpp @@ -27,7 +27,7 @@ #include #include "../../core/objectproperty.hpp" //#include "protocol/xpressnet.hpp" -#include "../protocol/loconet.hpp" +#include "../protocol/loconet/loconet.hpp" struct z21_lan_header; @@ -64,7 +64,7 @@ class Z21 : public CommandStation Property hostname; Property port; - ObjectProperty<::Protocol::LocoNet> loconet; + ObjectProperty<::Protocol::LocoNet::LocoNet> loconet; Property serialNumber; Property hardwareType; Property firmwareVersion; diff --git a/server/src/hardware/input/loconetinput.cpp b/server/src/hardware/input/loconetinput.cpp index e7fc8d48..df659956 100644 --- a/server/src/hardware/input/loconetinput.cpp +++ b/server/src/hardware/input/loconetinput.cpp @@ -22,10 +22,12 @@ #include "loconetinput.hpp" +using namespace Protocol::LocoNet; + LocoNetInput::LocoNetInput(const std::weak_ptr world, std::string_view _id) : Input(world, _id), loconet{this, "loconet", nullptr, PropertyFlags::ReadWrite | PropertyFlags::Store, - [this](const std::shared_ptr& value) + [this](const std::shared_ptr& value) { if(!value || value->addInput(shared_ptr())) { diff --git a/server/src/hardware/input/loconetinput.hpp b/server/src/hardware/input/loconetinput.hpp index 61cb44ec..68dc3a86 100644 --- a/server/src/hardware/input/loconetinput.hpp +++ b/server/src/hardware/input/loconetinput.hpp @@ -25,11 +25,11 @@ #include "input.hpp" #include "../../core/objectproperty.hpp" -#include "../protocol/loconet.hpp" +#include "../protocol/loconet/loconet.hpp" class LocoNetInput : public Input { - friend class Protocol::LocoNet; + friend class Protocol::LocoNet::LocoNet; protected: void worldEvent(WorldState state, WorldEvent event) final; @@ -40,7 +40,7 @@ class LocoNetInput : public Input CLASS_ID("input.loconet") CREATE(LocoNetInput) - ObjectProperty loconet; + ObjectProperty loconet; Property address; LocoNetInput(const std::weak_ptr world, std::string_view _id); diff --git a/server/src/hardware/protocol/loconet.hpp b/server/src/hardware/protocol/loconet.hpp deleted file mode 100644 index 208321df..00000000 --- a/server/src/hardware/protocol/loconet.hpp +++ /dev/null @@ -1,1141 +0,0 @@ -/** - * server/src/hardware/protocol/loconet.hpp - * - * This file is part of the traintastic source code. - * - * Copyright (C) 2019-2020 Reinder Feenstra - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -/** - * Portions Copyright (C) Digitrax Inc. - * - * LocoNet is a registered trademark of DigiTrax, Inc. - */ - -#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_HPP -#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_HPP - -#include -#include "../../core/subobject.hpp" -#include "../../core/property.hpp" -#include -#include "../../enum/loconetcommandstation.hpp" -//#include -//#include -#include "../../hardware/decoder/decoderchangeflags.hpp" - -namespace Hardware { - class Decoder; -} - -class LocoNetInput; - -namespace Protocol { - -class LocoNet : public SubObject -{ - //friend class LocoNetInput; - - protected: - static constexpr bool isLongAddress(uint16_t address) - { - return address > 127; - } - - public: - struct Message; - - static uint8_t calcChecksum(const Message& msg); - static void updateChecksum(Message& msg); - static bool isChecksumValid(const Message& msg); - - static constexpr uint8_t SLOT_LOCO_MIN = 1; - static constexpr uint8_t SLOT_LOCO_MAX = 119; - static constexpr uint8_t SLOT_FAST_CLOCK = 123; - static constexpr uint8_t SLOT_PROGRAMMING_TRACK = 124; - static constexpr uint8_t SLOT_UNKNOWN = 255; //!< placeholder to indicate invalid slot - - static constexpr uint8_t SPEED_STOP = 0; - static constexpr uint8_t SPEED_ESTOP = 1; - static constexpr uint8_t SPEED_MAX = 127; - - static constexpr uint8_t SL_CONUP = 0x40; - static constexpr uint8_t SL_BUSY = 0x20; - static constexpr uint8_t SL_ACTIVE = 0x10; - static constexpr uint8_t SL_CONDN = 0x08; - - static constexpr uint8_t SL_DIR = 0x20; - static constexpr uint8_t SL_F0 = 0x10; - static constexpr uint8_t SL_F4 = 0x08; - static constexpr uint8_t SL_F3 = 0x04; - static constexpr uint8_t SL_F2 = 0x02; - static constexpr uint8_t SL_F1 = 0x01; - - static constexpr uint8_t SL_F5 = 0x01; - static constexpr uint8_t SL_F6 = 0x02; - static constexpr uint8_t SL_F7 = 0x04; - static constexpr uint8_t SL_F8 = 0x08; - - static constexpr uint8_t SL_F9 = 0x01; - static constexpr uint8_t SL_F10 = 0x02; - static constexpr uint8_t SL_F11 = 0x04; - static constexpr uint8_t SL_F12 = 0x08; - - static constexpr uint8_t SL_F13 = 0x01; - static constexpr uint8_t SL_F14 = 0x02; - static constexpr uint8_t SL_F15 = 0x04; - static constexpr uint8_t SL_F16 = 0x08; - static constexpr uint8_t SL_F17 = 0x10; - static constexpr uint8_t SL_F18 = 0x20; - static constexpr uint8_t SL_F19 = 0x40; - static constexpr uint8_t SL_F20 = 0x80; - - static constexpr uint8_t MULTI_SENSE_TYPE_MASK = 0xE0; - static constexpr uint8_t MULTI_SENSE_TYPE_TRANSPONDER_GONE = 0x00; - static constexpr uint8_t MULTI_SENSE_TYPE_TRANSPONDER_PRESENT = 0x20; - static constexpr uint8_t MULTI_SENSE_TRANSPONDER_ADDRESS_SHORT = 0xFD; - - enum OpCode : uint8_t - { - // 2 byte message opcodes: - OPC_BUSY = 0x81, - OPC_GPOFF = 0x82, - OPC_GPON = 0x83, - OPC_IDLE = 0x85, - - // 4 byte message opcodes: - OPC_LOCO_SPD = 0xA0, - OPC_LOCO_DIRF = 0xA1, - OPC_LOCO_SND = 0xA2, - OPC_LOCO_F9F12 = 0xA3, // based on reverse engineering, see loconet.md - OPC_SW_REQ = 0xB0, - OPC_SW_REP = 0xB1, - OPC_INPUT_REP = 0xB2, - OPC_LONG_ACK = 0xB4, - OPC_SLOT_STAT1 = 0xB5, - OPC_CONSIST_FUNC = 0xB6, - OPC_UNLINK_SLOTS = 0xB8, - OPC_LINK_SLOTS = 0xB9, - OPC_MOVE_SLOTS = 0xBA, - OPC_RQ_SL_DATA = 0xBB, - OPC_SW_STATE = 0xBC, - OPC_SW_ACK = 0xBD, - OPC_LOCO_ADR = 0xBF, - - // 6 byte message opcodes: - OPC_MULTI_SENSE = 0xD0, // based on reverse engineering, see loconet.md - OPC_D4 = 0xD4,// based on reverse engineering, probably used for multiple sub commands, see loconet.md - - // variable byte message opcodes: - OPC_MULTI_SENSE_LONG = 0XE0, // based on reverse engineering, see loconet.md - OPC_PEER_XFER = 0xE5, - OPC_SL_RD_DATA = 0xE7, - OPC_IMM_PACKET = 0xED, - OPC_WR_SL_DATA = 0xEF, - }; - - struct Message - { - OpCode opCode; - - Message() - { - } - - Message(OpCode _opCode) : - opCode{_opCode} - { - } - - uint8_t size() const - { - switch(opCode & 0xE0) - { - case 0x80: // 1 0 0 F D C B A - return 2; - - case 0xA0: // 1 0 1 F D C B A - return 4; - - case 0xC0: // 1 1 0 F D C B A - return 6; - - case 0xE0: // 1 1 1 F D C B A => length in next byte - return reinterpret_cast(this)[1]; - - default: - return 0; // invalid opcode - } - } - }; - - struct SlotMessage : Message - { - uint8_t slot; - - SlotMessage(OpCode _opCode, uint8_t _slot) : - Message{_opCode}, - slot{_slot} - { - } - }; - - struct Idle : Message - { - uint8_t checksum; - - Idle() : - Message{OPC_IDLE}, - checksum{0x7A} - { - } - }; - static_assert(sizeof(Idle) == 2); - - struct GlobalPowerOn : Message - { - uint8_t checksum; - - GlobalPowerOn() : - Message{OPC_GPON}, - checksum{0x7C} - { - } - }; - static_assert(sizeof(GlobalPowerOn) == 2); - - struct GlobalPowerOff : Message - { - uint8_t checksum; - - GlobalPowerOff() : - Message{OPC_GPOFF}, - checksum{0x7D} - { - } - }; - static_assert(sizeof(GlobalPowerOff) == 2); - - struct Busy : Message - { - uint8_t checksum; - - Busy() : - Message{OPC_BUSY}, - checksum{0x7E} - { - } - }; - static_assert(sizeof(Busy) == 2); - - - - struct LocoAdr : Message - { - uint8_t addressHigh; - uint8_t addressLow; - uint8_t checksum; - - LocoAdr(uint16_t address) : - Message{OPC_LOCO_ADR}, - addressHigh{static_cast(address >> 7)}, - addressLow{static_cast(address & 0x7F)} - { - checksum = calcChecksum(*this); - } - }; - static_assert(sizeof(LocoAdr) == 4); - - struct LocoSpd : SlotMessage - { - uint8_t speed; - uint8_t checksum; - - LocoSpd(uint8_t _speed) : - SlotMessage{OPC_LOCO_SPD, SLOT_UNKNOWN}, - speed{_speed} - { - checksum = calcChecksum(*this); - } - }; - static_assert(sizeof(LocoSpd) == 4); - - struct LocoDirF : SlotMessage - { - uint8_t dirf; - uint8_t checksum; - - LocoDirF(Direction direction, bool f0, bool f1, bool f2, bool f3, bool f4) : - SlotMessage{OPC_LOCO_DIRF, SLOT_UNKNOWN}, - dirf{0} - { - if(direction == Direction::Forward) - dirf |= SL_DIR; - if(f0) - dirf |= SL_F0; - if(f1) - dirf |= SL_F1; - if(f2) - dirf |= SL_F2; - if(f3) - dirf |= SL_F3; - if(f4) - dirf |= SL_F4; - } - - inline Direction direction() const - { - return (dirf & SL_DIR) ? Direction::Forward : Direction::Reverse; - } - - inline void setDirection(Direction value) - { - if(value == Direction::Forward) - dirf |= SL_DIR; - else - dirf &= ~SL_DIR; - } - - inline bool f0() const - { - return dirf & SL_F0; - } - - inline void setF0(bool value) - { - if(value) - dirf |= SL_F0; - else - dirf &= ~SL_F0; - } - - inline bool f1() const - { - return dirf & SL_F1; - } - - inline void setF1(bool value) - { - if(value) - dirf |= SL_F1; - else - dirf &= ~SL_F1; - } - - inline bool f2() const - { - return dirf & SL_F2; - } - - inline void setF2(bool value) - { - if(value) - dirf |= SL_F2; - else - dirf &= ~SL_F2; - } - - inline bool f3() const - { - return dirf & SL_F3; - } - - inline void setF3(bool value) - { - if(value) - dirf |= SL_F3; - else - dirf &= ~SL_F3; - } - - inline bool f4() const - { - return dirf & SL_F4; - } - - inline void setF4(bool value) - { - if(value) - dirf |= SL_F4; - else - dirf &= ~SL_F4; - } - }; - static_assert(sizeof(LocoDirF) == 4); - - struct LocoSnd : SlotMessage - { - uint8_t snd; - uint8_t checksum; - - LocoSnd(bool f5, bool f6, bool f7, bool f8) : - SlotMessage{OPC_LOCO_SND, SLOT_UNKNOWN}, - snd{0} - { - if(f5) - snd |= SL_F5; - if(f6) - snd |= SL_F6; - if(f7) - snd |= SL_F7; - if(f8) - snd |= SL_F8; - - checksum = calcChecksum(*this); - } - - inline bool f5() const - { - return snd & SL_F5; - } - - inline void setF5(bool value) - { - if(value) - snd |= SL_F5; - else - snd &= ~SL_F5; - } - - inline bool f6() const - { - return snd & SL_F6; - } - - inline void setF6(bool value) - { - if(value) - snd |= SL_F6; - else - snd &= ~SL_F6; - } - - inline bool f7() const - { - return snd & SL_F7; - } - - inline void setF7(bool value) - { - if(value) - snd |= SL_F7; - else - snd &= ~SL_F7; - } - - inline bool f8() const - { - return snd & SL_F8; - } - - inline void setF8(bool value) - { - if(value) - snd |= SL_F8; - else - snd &= ~SL_F8; - } - }; - static_assert(sizeof(LocoSnd) == 4); - - struct LocoF9F12 : SlotMessage - { - uint8_t function; - uint8_t checksum; - - LocoF9F12(bool f9, bool f10, bool f11, bool f12) : - SlotMessage{OPC_LOCO_F9F12, SLOT_UNKNOWN}, - function{0} - { - if(f9) - function |= SL_F9; - if(f10) - function |= SL_F10; - if(f11) - function |= SL_F11; - if(f12) - function |= SL_F12; - - checksum = calcChecksum(*this); - } - - inline bool f9() const - { - return function & SL_F9; - } - - inline void setF9(bool value) - { - if(value) - function |= SL_F9; - else - function &= ~SL_F9; - } - - inline bool f10() const - { - return function & SL_F10; - } - - inline void setF10(bool value) - { - if(value) - function |= SL_F10; - else - function &= ~SL_F10; - } - - inline bool f11() const - { - return function & SL_F7; - } - - inline void setF11(bool value) - { - if(value) - function |= SL_F11; - else - function &= ~SL_F11; - } - - inline bool f12() const - { - return function & SL_F8; - } - - inline void setF12(bool value) - { - if(value) - function |= SL_F12; - else - function &= ~SL_F12; - } - }; - static_assert(sizeof(LocoF9F12) == 4); - - /* - - 2020-02-04 21:27:59.123954 [debug] cs1: unknown message: dataLen=0x0008, header=0x00a0, data=b2 11 70 2c - 2020-02-04 21:27:59.413558 [debug] cs1: unknown message: dataLen=0x0008, header=0x00a0, data=b2 19 40 14 - 2020-02-04 21:28:00.046282 [debug] cs1: unknown message: dataLen=0x0008, header=0x00a0, data=b2 11 50 0c - 2020-02-04 21:28:00.433662 [debug] cs1: unknown message: dataLen=0x0008, header=0x00a0, data=b2 11 60 3c - 2020-02-04 21:28:02.502012 [debug] cs1: unknown message: dataLen=0x0008, header=0x00a0, data=b2 09 70 34 - 2020-02-04 21:28:02.913595 [debug] cs1: unknown message: dataLen=0x0008, header=0x00a0, data=b2 11 40 1c - 2020-02-04 21:28:03.629638 [debug] cs1: unknown message: dataLen=0x0008, header=0x00a0, data=b2 09 50 14 - 2020-02-04 21:28:03.937476 [debug] cs1: unknown message: dataLen=0x0008, header=0x00a0, data=b2 09 60 24 - - */ - - - - struct InputRep : Message - { - uint8_t in1; - uint8_t in2; - uint8_t checksum; - - InputRep() : - Message{OPC_INPUT_REP} - { - } - - inline uint16_t address() const - { - return (in1 & 0x7F) | (static_cast(in2 & 0x0F) << 7); - } - - inline bool isSwitchInput() const - { - return in2 & 0x20; - } - - inline bool isAuxInput() const - { - return !isSwitchInput(); - } - - inline bool value() const - { - return in2 & 0x10; - } - }; - static_assert(sizeof(InputRep) == 4); - - - - struct RequestSlotData : Message - { - uint8_t slot; - uint8_t data2; - uint8_t checksum; - - RequestSlotData(uint8_t _slot) : - Message(OPC_RQ_SL_DATA), - slot{_slot}, - data2{0} - { - checksum = calcChecksum(*this); - } - }; - static_assert(sizeof(RequestSlotData) == 4); - - - // d0 00 42 20 13 5e - - struct MultiSense : Message - { - uint8_t data1; - uint8_t data2; - uint8_t data3; - uint8_t data4; - uint8_t checksum; - - MultiSense() : - Message(OPC_MULTI_SENSE) - { - } - - bool isTransponder() const - { - return - ((data1 & MULTI_SENSE_TYPE_MASK) == MULTI_SENSE_TYPE_TRANSPONDER_GONE) || - ((data1 & MULTI_SENSE_TYPE_MASK) == MULTI_SENSE_TYPE_TRANSPONDER_PRESENT); - } - }; - static_assert(sizeof(MultiSense) == 6); - - struct LocoF13F20 : Message - { - uint8_t data1; - uint8_t slot; - uint8_t data3; - uint8_t function; - uint8_t checksum; - - LocoF13F20(bool f13, bool f14, bool f15, bool f16, bool f17, bool f18, bool f19, bool f20) : - Message(OPC_D4), - data1{0x20}, - slot{SLOT_UNKNOWN}, - data3{0x08}, - function{0} - { - if(f13) - function |= SL_F13; - if(f14) - function |= SL_F14; - if(f15) - function |= SL_F15; - if(f16) - function |= SL_F16; - if(f17) - function |= SL_F17; - if(f18) - function |= SL_F18; - if(f19) - function |= SL_F19; - if(f20) - function |= SL_F20; - - checksum = calcChecksum(*this); - } - }; - static_assert(sizeof(LocoF13F20) == 6); - - struct MultiSenseTransponder : MultiSense - { - bool isPresent() const - { - return (data1 & MULTI_SENSE_TYPE_MASK) == MULTI_SENSE_TYPE_TRANSPONDER_PRESENT; - } - - uint16_t sensorAddress() const - { - return (static_cast(data1 & 0x1F) << 7) | (data2 & 0x7F); - } - - uint16_t transponderAddress() const - { - if(isTransponderAddressLong()) - return (static_cast(data3 & 0x7F) << 7) | (data4 & 0x7F); - else - return (data4 & 0x7F); - } - - bool isTransponderAddressLong() const - { - return data3 != MULTI_SENSE_TRANSPONDER_ADDRESS_SHORT; - } - }; - static_assert(sizeof(MultiSenseTransponder) == 6); - - struct MultiSenseLong : Message - { - uint8_t len; - uint8_t data1; - uint8_t data2; - uint8_t data3; - uint8_t data4; - uint8_t data5; - uint8_t data6; - uint8_t checksum; - - MultiSenseLong() : - Message(OPC_MULTI_SENSE_LONG), - len{9} - { - } - - bool isTransponder() const - { - return - ((data1 & MULTI_SENSE_TYPE_MASK) == MULTI_SENSE_TYPE_TRANSPONDER_GONE) || - ((data1 & MULTI_SENSE_TYPE_MASK) == MULTI_SENSE_TYPE_TRANSPONDER_PRESENT); - } - }; - static_assert(sizeof(MultiSenseLong) == 9); - - struct MultiSenseLongTransponder : MultiSenseLong - { - bool isPresent() const - { - return (data1 & MULTI_SENSE_TYPE_MASK) == MULTI_SENSE_TYPE_TRANSPONDER_PRESENT; - } - - uint16_t sensorAddress() const - { - return (static_cast(data1 & 0x1F) << 7) | (data2 & 0x7F); - } - - uint16_t transponderAddress() const - { - if(isTransponderAddressLong()) - return (static_cast(data3 & 0x7F) << 7) | (data4 & 0x7F); - else - return (data4 & 0x7F); - } - - bool isTransponderAddressLong() const - { - return data3 != MULTI_SENSE_TRANSPONDER_ADDRESS_SHORT; - } - - Direction transponderDirection() const - { - return (data5 & 0x40) ? Direction::Forward : Direction::Reverse; - } - }; - static_assert(sizeof(MultiSenseLongTransponder) == 9); - - // OPC_SL_RD_DATA [E7 0E 1F 13 6F 01 30 07 08 19 00 00 00 52] - struct SlotReadData : Message - { - uint8_t len; - uint8_t slot; - uint8_t stat; - uint8_t adr; - uint8_t spd; - uint8_t dirf; - uint8_t trk; - uint8_t ss2; - uint8_t adr2; - uint8_t snd; - uint8_t id1; - uint8_t id2; - uint8_t checksum; - - SlotReadData() : - Message(OPC_SL_RD_DATA), - len{14} - { - } - - bool isBusy() const - { - return stat & SL_BUSY; - } - - bool isActive() const - { - return stat & SL_ACTIVE; - } - - uint16_t address() const - { - return (static_cast(adr2) << 7) | adr; - } - - bool isEmergencyStop() const - { - return spd == 0x01; - } - - uint8_t speed() const - { - return spd > 1 ? spd - 1 : 0; - } - - inline Direction direction() const - { - return (dirf & SL_DIR) ? Direction::Forward : Direction::Reverse; - } - - inline void setDirection(Direction value) - { - if(value == Direction::Forward) - dirf |= SL_DIR; - else - dirf &= ~SL_DIR; - } - - inline bool f0() const - { - return dirf & SL_F0; - } - - inline void setF0(bool value) - { - if(value) - dirf |= SL_F0; - else - dirf &= ~SL_F0; - } - - inline bool f1() const - { - return dirf & SL_F1; - } - - inline void setF1(bool value) - { - if(value) - dirf |= SL_F1; - else - dirf &= ~SL_F1; - } - - inline bool f2() const - { - return dirf & SL_F2; - } - - inline void setF2(bool value) - { - if(value) - dirf |= SL_F2; - else - dirf &= ~SL_F2; - } - - inline bool f3() const - { - return dirf & SL_F3; - } - - inline void setF3(bool value) - { - if(value) - dirf |= SL_F3; - else - dirf &= ~SL_F3; - } - - inline bool f4() const - { - return dirf & SL_F4; - } - - inline void setF4(bool value) - { - if(value) - dirf |= SL_F4; - else - dirf &= ~SL_F4; - } - - inline bool f5() const - { - return snd & SL_F5; - } - - inline void setF5(bool value) - { - if(value) - snd |= SL_F5; - else - snd &= ~SL_F5; - } - - inline bool f6() const - { - return snd & SL_F6; - } - - inline void setF6(bool value) - { - if(value) - snd |= SL_F6; - else - snd &= ~SL_F6; - } - - inline bool f7() const - { - return snd & SL_F7; - } - - inline void setF7(bool value) - { - if(value) - snd |= SL_F7; - else - snd &= ~SL_F7; - } - - inline bool f8() const - { - return snd & SL_F8; - } - - inline void setF8(bool value) - { - if(value) - snd |= SL_F8; - else - snd &= ~SL_F8; - } - }; - static_assert(sizeof(SlotReadData) == 14); -/* - struct ImmediatePacket : Message - { - uint8_t len; - uint8_t header; - uint8_t reps; - uint8_t dhi; - uint8_t im[5]; - uint8_t checksum; - - ImmediatePacket() : - Message(OPC_IMM_PACKET), - len{11}, - header{0x7F}, - reps{0}, - dhi{0}, - im{0, 0, 0, 0, 0} - { - } - - void setIMCount(uint8_t value) - { - assert(value <= 5); - reps = (reps & 0x8F) | ((value & 0x07) << 4); - } - - void updateDHI() - { - dhi = 0x20 | - (im[0] & 0x40) >> 7 | - (im[1] & 0x40) >> 6 | - (im[2] & 0x40) >> 5 | - (im[3] & 0x40) >> 4 | - (im[4] & 0x40) >> 3; - } - }; - static_assert(sizeof(ImmediatePacket) == 11); - - struct ImmediatePacketLoco : ImmediatePacket - { - ImmediatePacketLoco(uint16_t address, uint8_t repeatCount) : - ImmediatePacket() - { - assert(repeatCount <= 7); - reps = repeatCount & 0x07; - - if(isLongAddress(address)) - { - im[0] = 0xC0 | ((address >> 8) & 0x3F); - im[1] = address & 0xFF; - } - else - im[0] = address & 0x7F; - } - }; - static_assert(sizeof(ImmediatePacketLoco) == sizeof(ImmediatePacket)); - - struct ImmediatePacketF9F12 : ImmediatePacketLoco - { - ImmediatePacketF9F12(uint16_t address, bool f9, bool f10, bool f11, bool f12, uint8_t repeatCount = 2) : - ImmediatePacketLoco(address, repeatCount) - { - const uint8_t offset = (im[0] & 0x80) ? 2 : 1; - - im[offset] = 0xB0; // Function group two instruction: F9-F12 - if(f9) - im[offset] |= 0x01; - if(f10) - im[offset] |= 0x02; - if(f11) - im[offset] |= 0x04; - if(f12) - im[offset] |= 0x08; - - setIMCount(offset + 1); - updateDHI(); - checksum = calcChecksum(*this); - } - }; - static_assert(sizeof(ImmediatePacketF9F12) == sizeof(ImmediatePacketLoco)); - - struct ImmediatePacketF13F20 : ImmediatePacketLoco - { - ImmediatePacketF13F20(uint16_t address, bool f13, bool f14, bool f15, bool f16, bool f17, bool f18, bool f19, bool f20, uint8_t repeatCount = 2) : - ImmediatePacketLoco(address, repeatCount) - { - uint8_t offset = (im[0] & 0x80) ? 2 : 1; - - im[offset++] = 0xDE; // Feature Expansion Instruction: F13-F20 function control - - if(f13) - im[offset] |= 0x01; - if(f14) - im[offset] |= 0x02; - if(f15) - im[offset] |= 0x04; - if(f16) - im[offset] |= 0x08; - if(f17) - im[offset] |= 0x10; - if(f18) - im[offset] |= 0x20; - if(f19) - im[offset] |= 0x40; - if(f20) - im[offset] |= 0x80; - - setIMCount(offset + 1); - updateDHI(); - checksum = calcChecksum(*this); - } - }; - static_assert(sizeof(ImmediatePacketF13F20) == sizeof(ImmediatePacketLoco)); - - struct ImmediatePacketF21F28 : ImmediatePacketLoco - { - ImmediatePacketF21F28(uint16_t address, bool f21, bool f22, bool f23, bool f24, bool f25, bool f26, bool f27, bool f28, uint8_t repeatCount = 2) : - ImmediatePacketLoco(address, repeatCount) - { - uint8_t offset = (im[0] & 0x80) ? 2 : 1; - - im[offset++] = 0xDF; // Feature Expansion Instruction: F20-F28 function control - - if(f21) - im[offset] |= 0x01; - if(f22) - im[offset] |= 0x02; - if(f23) - im[offset] |= 0x04; - if(f24) - im[offset] |= 0x08; - if(f25) - im[offset] |= 0x10; - if(f26) - im[offset] |= 0x20; - if(f27) - im[offset] |= 0x40; - if(f28) - im[offset] |= 0x80; - - setIMCount(offset + 1); - updateDHI(); - checksum = calcChecksum(*this); - } - }; - static_assert(sizeof(ImmediatePacketF21F28) == sizeof(ImmediatePacketLoco)); -*/ - protected: - class Slots - { - private: - std::unordered_map m_addressToSlot; - std::unordered_map m_slotToAddress; - - public: - uint8_t getSlot(uint16_t address) const - { - auto it = m_addressToSlot.find(address); - return it != m_addressToSlot.end() ? it->second : SLOT_UNKNOWN; - } - - uint16_t getAddress(uint8_t slot) const - { - auto it = m_slotToAddress.find(slot); - return it != m_slotToAddress.end() ? it->second : 0; - } - - void set(uint16_t address, uint8_t slot) - { - m_addressToSlot[address] = slot; - m_slotToAddress[slot] = address; - } - - void clear() - { - m_addressToSlot.clear(); - m_slotToAddress.clear(); - } - }; - - std::function m_send; - std::atomic_bool m_debugLog; - Slots m_slots; - std::unordered_map> m_slotRequests; - uint8_t m_queryLocoSlots; - std::unordered_map> m_inputs; - - std::shared_ptr getDecoder(uint8_t slot, bool request = true); - - void send(uint16_t address, Message& message, uint8_t& slot); - template - inline void send(uint16_t address, T& message) - { - send(address, message, message.slot); - } - - public://protected: - bool isInputAddressAvailable(uint16_t address); - bool addInput(const std::shared_ptr& input); - void removeInput(const std::shared_ptr& input); - - public: - CLASS_ID("protocol.loconet") - - Property commandStation; - Property debugLog; - - LocoNet(Object& _parent, const std::string& parentPropertyName, std::function send); - - bool send(const Message& message); - void receive(const Message& message); - - void decoderChanged(const Hardware::Decoder& decoder, Hardware::DecoderChangeFlags changes, uint32_t functionNumber); - - void queryLocoSlots(); -}; - -} - -#endif diff --git a/server/src/hardware/protocol/loconet.cpp b/server/src/hardware/protocol/loconet/loconet.cpp similarity index 55% rename from server/src/hardware/protocol/loconet.cpp rename to server/src/hardware/protocol/loconet/loconet.cpp index 4135c7e4..f6ceb2f3 100644 --- a/server/src/hardware/protocol/loconet.cpp +++ b/server/src/hardware/protocol/loconet/loconet.cpp @@ -23,195 +23,26 @@ #include "loconet.hpp" #include #include -#include "../../core/eventloop.hpp" -#include "../../core/traintastic.hpp" -#include "../commandstation/commandstation.hpp" -#include "../input/loconetinput.hpp" -#include "../../utils/to_hex.hpp" +#include "../../../core/eventloop.hpp" +#include "../../../core/traintastic.hpp" +#include "../../commandstation/commandstation.hpp" +#include "../../input/loconetinput.hpp" -namespace Protocol { - -static std::string to_string(LocoNet::OpCode value) -{ - switch(value) - { - case LocoNet::OPC_BUSY: return "OPC_BUSY"; - case LocoNet::OPC_GPOFF: return "OPC_GPOFFqqq"; - case LocoNet::OPC_GPON: return "OPC_GPON"; - case LocoNet::OPC_IDLE: return "OPC_IDLE"; - case LocoNet::OPC_LOCO_SPD: return "OPC_LOCO_SPD"; - case LocoNet::OPC_LOCO_DIRF: return "OPC_LOCO_DIRF"; - case LocoNet::OPC_LOCO_SND: return "OPC_LOCO_SND"; - case LocoNet::OPC_SW_REQ: return "OPC_SW_REQ"; - case LocoNet::OPC_SW_REP: return "OPC_SW_REP"; - case LocoNet::OPC_INPUT_REP: return "OPC_INPUT_REP"; - case LocoNet::OPC_LONG_ACK: return "OPC_LONG_ACK"; - case LocoNet::OPC_SLOT_STAT1: return "OPC_SLOT_STAT1"; - case LocoNet::OPC_CONSIST_FUNC: return "OPC_CONSIST_FUNC"; - case LocoNet::OPC_UNLINK_SLOTS: return "OPC_UNLINK_SLOTS"; - case LocoNet::OPC_LINK_SLOTS: return "OPC_LINK_SLOTS"; - case LocoNet::OPC_MOVE_SLOTS: return "OPC_MOVE_SLOTS"; - case LocoNet::OPC_RQ_SL_DATA: return "OPC_RQ_SL_DATA"; - case LocoNet::OPC_SW_STATE: return "OPC_SW_STATE"; - case LocoNet::OPC_SW_ACK: return "OPC_SW_ACK"; - case LocoNet::OPC_LOCO_ADR: return "OPC_LOCO_ADR"; - case LocoNet::OPC_MULTI_SENSE: return "OPC_MULTI_SENSE"; - case LocoNet::OPC_PEER_XFER: return "OPC_PEER_XFER"; - case LocoNet::OPC_SL_RD_DATA: return "OPC_SL_RD_DATA"; - case LocoNet::OPC_IMM_PACKET: return "OPC_IMM_PACKET"; - case LocoNet::OPC_WR_SL_DATA: return "OPC_WR_SL_DATA"; - } - - return to_hex(value); -} - -std::string to_string(const LocoNet::Message& message, bool raw = false) -{ - std::string s{to_string(message.opCode)}; - - switch(message.opCode) - { - case LocoNet::OPC_GPON: - case LocoNet::OPC_GPOFF: - case LocoNet::OPC_IDLE: - case LocoNet::OPC_BUSY: - break; - - case LocoNet::OPC_LOCO_SPD: - { - const LocoNet::LocoSpd& locoSpd = static_cast(message); - s.append(" slot=").append(std::to_string(locoSpd.slot)); - s.append(" speed=").append(std::to_string(locoSpd.speed)); - break; - } - case LocoNet::OPC_LOCO_DIRF: - { - const LocoNet::LocoDirF& locoDirF = static_cast(message); - s.append(" slot=").append(std::to_string(locoDirF.slot)); - s.append(" dir=").append(locoDirF.direction() == Direction::Forward ? "fwd" : "rev"); - s.append(" f0=").append(locoDirF.f0() ? "on" : "off"); - s.append(" f1=").append(locoDirF.f1() ? "on" : "off"); - s.append(" f2=").append(locoDirF.f2() ? "on" : "off"); - s.append(" f3=").append(locoDirF.f3() ? "on" : "off"); - s.append(" f4=").append(locoDirF.f4() ? "on" : "off"); - break; - } - case LocoNet::OPC_LOCO_SND: - { - const LocoNet::LocoSnd& locoSnd = static_cast(message); - s.append(" slot=").append(std::to_string(locoSnd.slot)); - s.append(" f5=").append(locoSnd.f5() ? "on" : "off"); - s.append(" f6=").append(locoSnd.f6() ? "on" : "off"); - s.append(" f7=").append(locoSnd.f7() ? "on" : "off"); - s.append(" f8=").append(locoSnd.f8() ? "on" : "off"); - break; - } - case LocoNet::OPC_LOCO_F9F12: - { - const LocoNet::LocoF9F12& locoF9F12 = static_cast(message); - s.append(" slot=").append(std::to_string(locoF9F12.slot)); - s.append(" f9=").append(locoF9F12.f9() ? "on" : "off"); - s.append(" f10=").append(locoF9F12.f10() ? "on" : "off"); - s.append(" f11=").append(locoF9F12.f11() ? "on" : "off"); - s.append(" f12=").append(locoF9F12.f12() ? "on" : "off"); - break; - } - case LocoNet::OPC_INPUT_REP: - { - const LocoNet::InputRep& inputRep = static_cast(message); - s.append(" address=").append(std::to_string(inputRep.address())); - s.append(" input=").append(inputRep.isAuxInput() ? "aux" : "switch"); - s.append(" value=").append(inputRep.value() ? "high" : "low"); - break; - } - case LocoNet::OPC_RQ_SL_DATA: - { - const LocoNet::RequestSlotData& requestSlotData = static_cast(message); - s.append(" slot=").append(std::to_string(requestSlotData.slot)); - break; - } - case LocoNet::OPC_MULTI_SENSE: - { - const LocoNet::MultiSense& multiSense = static_cast(message); - if(multiSense.isTransponder()) - { - const LocoNet::MultiSenseTransponder& multiSenseTransponder = static_cast(multiSense); - s.append(multiSenseTransponder.isPresent() ? " present" : " absent"); - s.append(" sensorAddress=").append(std::to_string(multiSenseTransponder.sensorAddress())); - s.append(" transponderAddress=").append(std::to_string(multiSenseTransponder.transponderAddress())); - } - else - raw = true; - break; - } - case LocoNet::OPC_MULTI_SENSE_LONG: - { - const LocoNet::MultiSenseLong& multiSense = static_cast(message); - if(multiSense.isTransponder()) - { - const LocoNet::MultiSenseLongTransponder& multiSenseTransponder = static_cast(multiSense); - s.append(multiSenseTransponder.isPresent() ? " present" : " absent"); - s.append(" sensorAddress=").append(std::to_string(multiSenseTransponder.sensorAddress())); - s.append(" transponderAddress=").append(std::to_string(multiSenseTransponder.transponderAddress())); - s.append(" transponderDirection=").append(multiSenseTransponder.transponderDirection() == Direction::Forward ? "fwd" : "rev"); - } - else - raw = true; - break; - } - default: - raw = true; - break; - } - - if(raw) - { - s.append(" ["); - const uint8_t* bytes = reinterpret_cast(&message); - for(int i = 0; i < message.size(); i++) - { - if(i != 0) - s.append(" "); - s.append(to_hex(bytes[i])); - } - s.append("]"); - } - - return s; -} +namespace Protocol::LocoNet { void updateDecoderSpeed(const std::shared_ptr& decoder, uint8_t speed) { - decoder->emergencyStop.setValueInternal(speed == LocoNet::SPEED_ESTOP); + decoder->emergencyStop.setValueInternal(speed == SPEED_ESTOP); - if(speed == LocoNet::SPEED_STOP || speed == LocoNet::SPEED_ESTOP) + if(speed == SPEED_STOP || speed == SPEED_ESTOP) decoder->speedStep.setValueInternal(0); else - decoder->speedStep.setValueInternal(((speed - 1) * decoder->speedSteps) / (LocoNet::SPEED_MAX - 1)); -} - -uint8_t LocoNet::calcChecksum(const Message& msg) -{ - const uint8_t* p = reinterpret_cast(&msg); - const int size = msg.size() - 1; - uint8_t checksum = 0xFF; - for(int i = 0; i < size; i++) - checksum ^= p[i]; - return checksum; -} - -void LocoNet::updateChecksum(Message& msg) -{ - reinterpret_cast(&msg)[msg.size() - 1] = calcChecksum(msg); -} - -bool LocoNet::isChecksumValid(const Message& msg) -{ - return calcChecksum(msg) == reinterpret_cast(&msg)[msg.size() - 1]; + decoder->speedStep.setValueInternal(((speed - 1) * decoder->speedSteps) / (SPEED_MAX - 1)); } LocoNet::LocoNet(Object& _parent, const std::string& parentPropertyName, std::function send) : SubObject(_parent, parentPropertyName), + m_commandStation{dynamic_cast(&_parent)}, m_send{std::move(send)}, m_debugLog{true/*false*/}, m_queryLocoSlots{SLOT_UNKNOWN}, @@ -288,10 +119,10 @@ void LocoNet::receive(const Message& message) EventLoop::call( [this]() { - if(auto cs = std::dynamic_pointer_cast(parent().shared_from_this())) + if(m_commandStation) { - cs->emergencyStop.setValueInternal(false); - cs->trackVoltageOff.setValueInternal(false); + m_commandStation->emergencyStop.setValueInternal(false); + m_commandStation->trackVoltageOff.setValueInternal(false); } }); break; @@ -300,8 +131,8 @@ void LocoNet::receive(const Message& message) EventLoop::call( [this]() { - if(auto cs = std::dynamic_pointer_cast(parent().shared_from_this())) - cs->trackVoltageOff.setValueInternal(true); + if(m_commandStation) + m_commandStation->trackVoltageOff.setValueInternal(true); }); break; @@ -309,8 +140,8 @@ void LocoNet::receive(const Message& message) EventLoop::call( [this]() { - if(auto cs = std::dynamic_pointer_cast(parent().shared_from_this())) - cs->emergencyStop.setValueInternal(true); + if(m_commandStation) + m_commandStation->emergencyStop.setValueInternal(true); }); break; @@ -461,17 +292,35 @@ void LocoNet::decoderChanged(const Hardware::Decoder& decoder, Hardware::Decoder decoder.getFunctionValue(12)}; send(decoder.address, message); } - else if(functionNumber <= 20) + else if(functionNumber <= 19) { - LocoF13F20 message{ + LocoF13F19 message{ decoder.getFunctionValue(13), decoder.getFunctionValue(14), decoder.getFunctionValue(15), decoder.getFunctionValue(16), decoder.getFunctionValue(17), decoder.getFunctionValue(18), - decoder.getFunctionValue(19), - decoder.getFunctionValue(20)}; + decoder.getFunctionValue(19)}; + send(decoder.address, message); + } + else if(functionNumber == 20 || functionNumber == 28) + { + LocoF20F28 message{ + decoder.getFunctionValue(20), + decoder.getFunctionValue(28)}; + send(decoder.address, message); + } + else if(functionNumber <= 27) + { + LocoF21F27 message{ + decoder.getFunctionValue(21), + decoder.getFunctionValue(22), + decoder.getFunctionValue(23), + decoder.getFunctionValue(24), + decoder.getFunctionValue(25), + decoder.getFunctionValue(26), + decoder.getFunctionValue(27)}; send(decoder.address, message); } else @@ -490,14 +339,14 @@ std::shared_ptr LocoNet::getDecoder(uint8_t slot, bool reques if(slot < SLOT_LOCO_MIN || slot > SLOT_LOCO_MAX) return nullptr; - if(auto cs = std::dynamic_pointer_cast(parent().shared_from_this())) + if(m_commandStation) { const uint16_t address = m_slots.getAddress(slot); if(address != 0) { - auto decoder = cs->getDecoder(DecoderProtocol::DCC, address, isLongAddress(address)); + auto decoder = m_commandStation->getDecoder(DecoderProtocol::DCC, address, isLongAddress(address)); if(!decoder) - decoder = cs->getDecoder(DecoderProtocol::Auto, address); + decoder = m_commandStation->getDecoder(DecoderProtocol::Auto, address); return decoder; } else if(request) diff --git a/server/src/hardware/protocol/loconet/loconet.hpp b/server/src/hardware/protocol/loconet/loconet.hpp new file mode 100644 index 00000000..b1259c83 --- /dev/null +++ b/server/src/hardware/protocol/loconet/loconet.hpp @@ -0,0 +1,138 @@ +/** + * server/src/hardware/protocol/loconet.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2019-2020 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** + * Portions Copyright (C) Digitrax Inc. + * + * LocoNet is a registered trademark of DigiTrax, Inc. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_LOCONET_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_LOCONET_HPP + +#include +#include "../../../core/subobject.hpp" +#include "../../../core/property.hpp" +#include +#include "../../../enum/loconetcommandstation.hpp" +//#include +//#include +#include "../../../hardware/decoder/decoderchangeflags.hpp" +#include "messages.hpp" + +namespace Hardware { + namespace CommandStation { + class CommandStation; + } + class Decoder; +} + +class LocoNetInput; + +namespace Protocol::LocoNet { + +class LocoNet : public SubObject +{ + //friend class LocoNetInput; + + protected: + static constexpr bool isLongAddress(uint16_t address) + { + return address > 127; + } + + public: + + protected: + class Slots + { + private: + std::unordered_map m_addressToSlot; + std::unordered_map m_slotToAddress; + + public: + uint8_t getSlot(uint16_t address) const + { + auto it = m_addressToSlot.find(address); + return it != m_addressToSlot.end() ? it->second : SLOT_UNKNOWN; + } + + uint16_t getAddress(uint8_t slot) const + { + auto it = m_slotToAddress.find(slot); + return it != m_slotToAddress.end() ? it->second : 0; + } + + void set(uint16_t address, uint8_t slot) + { + m_addressToSlot[address] = slot; + m_slotToAddress[slot] = address; + } + + void clear() + { + m_addressToSlot.clear(); + m_slotToAddress.clear(); + } + }; + + Hardware::CommandStation::CommandStation* m_commandStation; // valid if parent is command station, else nullptr + std::function m_send; + std::atomic_bool m_debugLog; + Slots m_slots; + std::unordered_map> m_slotRequests; + uint8_t m_queryLocoSlots; + std::unordered_map> m_inputs; + + std::shared_ptr getDecoder(uint8_t slot, bool request = true); + + void send(uint16_t address, Message& message, uint8_t& slot); + template + inline void send(uint16_t address, T& message) + { + send(address, message, message.slot); + } + + public://protected: + bool isInputAddressAvailable(uint16_t address); + bool addInput(const std::shared_ptr& input); + void removeInput(const std::shared_ptr& input); + + public: + CLASS_ID("protocol.loconet") + + Property commandStation; + Property debugLog; + + LocoNet(Object& _parent, const std::string& parentPropertyName, std::function send); + + bool send(const Message& message); + void receive(const Message& message); + + void decoderChanged(const Hardware::Decoder& decoder, Hardware::DecoderChangeFlags changes, uint32_t functionNumber); + + void queryLocoSlots(); +}; + +} + +#endif diff --git a/server/src/hardware/protocol/loconet.md b/server/src/hardware/protocol/loconet/loconet.md similarity index 97% rename from server/src/hardware/protocol/loconet.md rename to server/src/hardware/protocol/loconet/loconet.md index fe3f9574..03c7b7d5 100644 --- a/server/src/hardware/protocol/loconet.md +++ b/server/src/hardware/protocol/loconet/loconet.md @@ -34,6 +34,8 @@ F16 OFF: 2020-07-16 22:51:20.933660 [debug] cs_1.loconet: rx: D4 [D4 20 1F 08 00 1C] 2020-07-16 22:51:20.949613 [debug] cs_1.loconet: rx: D4 [D4 20 1F 05 00 11] +F20 ON: +2020-07-17 11:00:28.212622 [debug] cs_1.loconet: rx: D4 [D4 20 1F 09 01 1C] diff --git a/server/src/hardware/protocol/loconet/messages.cpp b/server/src/hardware/protocol/loconet/messages.cpp new file mode 100644 index 00000000..549a425b --- /dev/null +++ b/server/src/hardware/protocol/loconet/messages.cpp @@ -0,0 +1,217 @@ +/** + * server/src/hardware/protocol/loconet/messages.cpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2019-2020 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "messages.hpp" +#include "../../../utils/to_hex.hpp" + +namespace Protocol::LocoNet { + +uint8_t calcChecksum(const Message& message) +{ + const uint8_t* p = reinterpret_cast(&message); + const int size = message.size() - 1; + uint8_t checksum = 0xFF; + for(int i = 0; i < size; i++) + checksum ^= p[i]; + return checksum; +} + +void updateChecksum(Message& message) +{ + reinterpret_cast(&message)[message.size() - 1] = calcChecksum(message); +} + +bool isChecksumValid(const Message& message) +{ + return calcChecksum(message) == reinterpret_cast(&message)[message.size() - 1]; +} + +std::string to_string(const Message& message, bool raw) +{ + std::string s; + if(std::string_view sv = to_string(message.opCode); !sv.empty()) + s = sv; + else + s = to_hex(message.opCode); + + switch(message.opCode) + { + case OPC_GPON: + case OPC_GPOFF: + case OPC_IDLE: + case OPC_BUSY: + break; + + case OPC_LOCO_SPD: + { + const LocoSpd& locoSpd = static_cast(message); + s.append(" slot=").append(std::to_string(locoSpd.slot)); + s.append(" speed=").append(std::to_string(locoSpd.speed)); + break; + } + case OPC_LOCO_DIRF: + { + const LocoDirF& locoDirF = static_cast(message); + s.append(" slot=").append(std::to_string(locoDirF.slot)); + s.append(" dir=").append(locoDirF.direction() == Direction::Forward ? "fwd" : "rev"); + s.append(" f0=").append(locoDirF.f0() ? "on" : "off"); + s.append(" f1=").append(locoDirF.f1() ? "on" : "off"); + s.append(" f2=").append(locoDirF.f2() ? "on" : "off"); + s.append(" f3=").append(locoDirF.f3() ? "on" : "off"); + s.append(" f4=").append(locoDirF.f4() ? "on" : "off"); + break; + } + case OPC_LOCO_SND: + { + const LocoSnd& locoSnd = static_cast(message); + s.append(" slot=").append(std::to_string(locoSnd.slot)); + s.append(" f5=").append(locoSnd.f5() ? "on" : "off"); + s.append(" f6=").append(locoSnd.f6() ? "on" : "off"); + s.append(" f7=").append(locoSnd.f7() ? "on" : "off"); + s.append(" f8=").append(locoSnd.f8() ? "on" : "off"); + break; + } + case OPC_LOCO_F9F12: + { + const LocoF9F12& locoF9F12 = static_cast(message); + s.append(" slot=").append(std::to_string(locoF9F12.slot)); + s.append(" f9=").append(locoF9F12.f9() ? "on" : "off"); + s.append(" f10=").append(locoF9F12.f10() ? "on" : "off"); + s.append(" f11=").append(locoF9F12.f11() ? "on" : "off"); + s.append(" f12=").append(locoF9F12.f12() ? "on" : "off"); + break; + } + case OPC_INPUT_REP: + { + const InputRep& inputRep = static_cast(message); + s.append(" address=").append(std::to_string(inputRep.address())); + s.append(" input=").append(inputRep.isAuxInput() ? "aux" : "switch"); + s.append(" value=").append(inputRep.value() ? "high" : "low"); + break; + } + case OPC_RQ_SL_DATA: + { + const RequestSlotData& requestSlotData = static_cast(message); + s.append(" slot=").append(std::to_string(requestSlotData.slot)); + break; + } + case OPC_MULTI_SENSE: + { + const MultiSense& multiSense = static_cast(message); + if(multiSense.isTransponder()) + { + const MultiSenseTransponder& multiSenseTransponder = static_cast(multiSense); + s.append(multiSenseTransponder.isPresent() ? " present" : " absent"); + s.append(" sensorAddress=").append(std::to_string(multiSenseTransponder.sensorAddress())); + s.append(" transponderAddress=").append(std::to_string(multiSenseTransponder.transponderAddress())); + } + else + raw = true; + break; + } + case OPC_D4: + { + const uint8_t* bytes = reinterpret_cast(&message); + if(bytes[1] == 0x20) + { + switch(bytes[3]) + { + case 0x08: + { + const LocoF13F19& locoF13F19 = static_cast(message); + s.append(" slot=").append(std::to_string(locoF13F19.slot)); + s.append(" f13=").append(locoF13F19.f13() ? "on" : "off"); + s.append(" f14=").append(locoF13F19.f14() ? "on" : "off"); + s.append(" f15=").append(locoF13F19.f15() ? "on" : "off"); + s.append(" f16=").append(locoF13F19.f16() ? "on" : "off"); + s.append(" f17=").append(locoF13F19.f17() ? "on" : "off"); + s.append(" f18=").append(locoF13F19.f18() ? "on" : "off"); + s.append(" f19=").append(locoF13F19.f19() ? "on" : "off"); + break; + } + case 0x05: + { + const LocoF20F28& locoF20F28 = static_cast(message); + s.append(" slot=").append(std::to_string(locoF20F28.slot)); + s.append(" f20=").append(locoF20F28.f20() ? "on" : "off"); + s.append(" f28=").append(locoF20F28.f28() ? "on" : "off"); + break; + } + case 0x09: + { + const LocoF21F27& locoF21F27 = static_cast(message); + s.append(" slot=").append(std::to_string(locoF21F27.slot)); + s.append(" f21=").append(locoF21F27.f21() ? "on" : "off"); + s.append(" f22=").append(locoF21F27.f22() ? "on" : "off"); + s.append(" f23=").append(locoF21F27.f23() ? "on" : "off"); + s.append(" f24=").append(locoF21F27.f24() ? "on" : "off"); + s.append(" f25=").append(locoF21F27.f25() ? "on" : "off"); + s.append(" f26=").append(locoF21F27.f26() ? "on" : "off"); + s.append(" f27=").append(locoF21F27.f27() ? "on" : "off"); + break; + } + default: + raw = true; + break; + } + } + else + raw = true; + break; + } + case OPC_MULTI_SENSE_LONG: + { + const MultiSenseLong& multiSense = static_cast(message); + if(multiSense.isTransponder()) + { + const MultiSenseLongTransponder& multiSenseTransponder = static_cast(multiSense); + s.append(multiSenseTransponder.isPresent() ? " present" : " absent"); + s.append(" sensorAddress=").append(std::to_string(multiSenseTransponder.sensorAddress())); + s.append(" transponderAddress=").append(std::to_string(multiSenseTransponder.transponderAddress())); + s.append(" transponderDirection=").append(multiSenseTransponder.transponderDirection() == Direction::Forward ? "fwd" : "rev"); + } + else + raw = true; + break; + } + default: + raw = true; + break; + } + + if(raw) + { + s.append(" ["); + const uint8_t* bytes = reinterpret_cast(&message); + for(int i = 0; i < message.size(); i++) + { + if(i != 0) + s.append(" "); + s.append(to_hex(bytes[i])); + } + s.append("]"); + } + + return s; +} + +} diff --git a/server/src/hardware/protocol/loconet/messages.hpp b/server/src/hardware/protocol/loconet/messages.hpp new file mode 100644 index 00000000..26ce173b --- /dev/null +++ b/server/src/hardware/protocol/loconet/messages.hpp @@ -0,0 +1,1281 @@ +/** + * server/src/hardware/protocol/loconet/messages.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2019-2020 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** + * Portions Copyright (C) Digitrax Inc. + * + * LocoNet is a registered trademark of DigiTrax, Inc. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_MESSAGES_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_MESSAGES_HPP + +#include +#include +#include "opcode.hpp" + +namespace Protocol::LocoNet { + +struct Message; + +uint8_t calcChecksum(const Message& msmessageg); +void updateChecksum(Message& message); +bool isChecksumValid(const Message& message); +std::string to_string(const Message& message, bool raw = false); + +constexpr uint8_t SLOT_LOCO_MIN = 1; +constexpr uint8_t SLOT_LOCO_MAX = 119; +constexpr uint8_t SLOT_FAST_CLOCK = 123; +constexpr uint8_t SLOT_PROGRAMMING_TRACK = 124; +constexpr uint8_t SLOT_UNKNOWN = 255; //!< placeholder to indicate invalid slot + +constexpr uint8_t SPEED_STOP = 0; +constexpr uint8_t SPEED_ESTOP = 1; +constexpr uint8_t SPEED_MAX = 127; + +constexpr uint8_t SL_CONUP = 0x40; +constexpr uint8_t SL_BUSY = 0x20; +constexpr uint8_t SL_ACTIVE = 0x10; +constexpr uint8_t SL_CONDN = 0x08; + +constexpr uint8_t SL_DIR = 0x20; +constexpr uint8_t SL_F0 = 0x10; +constexpr uint8_t SL_F4 = 0x08; +constexpr uint8_t SL_F3 = 0x04; +constexpr uint8_t SL_F2 = 0x02; +constexpr uint8_t SL_F1 = 0x01; + +constexpr uint8_t SL_F5 = 0x01; +constexpr uint8_t SL_F6 = 0x02; +constexpr uint8_t SL_F7 = 0x04; +constexpr uint8_t SL_F8 = 0x08; + +constexpr uint8_t SL_F9 = 0x01; +constexpr uint8_t SL_F10 = 0x02; +constexpr uint8_t SL_F11 = 0x04; +constexpr uint8_t SL_F12 = 0x08; + +constexpr uint8_t SL_F13 = 0x01; +constexpr uint8_t SL_F14 = 0x02; +constexpr uint8_t SL_F15 = 0x04; +constexpr uint8_t SL_F16 = 0x08; +constexpr uint8_t SL_F17 = 0x10; +constexpr uint8_t SL_F18 = 0x20; +constexpr uint8_t SL_F19 = 0x40; + +constexpr uint8_t SL_F20 = 0x20; +constexpr uint8_t SL_F28 = 0x40; + +constexpr uint8_t SL_F21 = 0x01; +constexpr uint8_t SL_F22 = 0x02; +constexpr uint8_t SL_F23 = 0x04; +constexpr uint8_t SL_F24 = 0x08; +constexpr uint8_t SL_F25 = 0x10; +constexpr uint8_t SL_F26 = 0x20; +constexpr uint8_t SL_F27 = 0x40; + +constexpr uint8_t MULTI_SENSE_TYPE_MASK = 0xE0; +constexpr uint8_t MULTI_SENSE_TYPE_TRANSPONDER_GONE = 0x00; +constexpr uint8_t MULTI_SENSE_TYPE_TRANSPONDER_PRESENT = 0x20; +constexpr uint8_t MULTI_SENSE_TRANSPONDER_ADDRESS_SHORT = 0xFD; + +struct Message +{ + OpCode opCode; + + Message() + { + } + + Message(OpCode _opCode) : + opCode{_opCode} + { + } + + uint8_t size() const + { + switch(opCode & 0xE0) + { + case 0x80: // 1 0 0 F D C B A + return 2; + + case 0xA0: // 1 0 1 F D C B A + return 4; + + case 0xC0: // 1 1 0 F D C B A + return 6; + + case 0xE0: // 1 1 1 F D C B A => length in next byte + return reinterpret_cast(this)[1]; + + default: + return 0; // not an op opcode, bit 7 not 1 + } + } +}; + +struct SlotMessage : Message +{ + uint8_t slot; + + SlotMessage(OpCode _opCode, uint8_t _slot) : + Message{_opCode}, + slot{_slot} + { + } +}; + +struct Idle : Message +{ + uint8_t checksum; + + Idle() : + Message{OPC_IDLE}, + checksum{0x7A} + { + } +}; +static_assert(sizeof(Idle) == 2); + +struct GlobalPowerOn : Message +{ + uint8_t checksum; + + GlobalPowerOn() : + Message{OPC_GPON}, + checksum{0x7C} + { + } +}; +static_assert(sizeof(GlobalPowerOn) == 2); + +struct GlobalPowerOff : Message +{ + uint8_t checksum; + + GlobalPowerOff() : + Message{OPC_GPOFF}, + checksum{0x7D} + { + } +}; +static_assert(sizeof(GlobalPowerOff) == 2); + +struct Busy : Message +{ + uint8_t checksum; + + Busy() : + Message{OPC_BUSY}, + checksum{0x7E} + { + } +}; +static_assert(sizeof(Busy) == 2); + + + +struct LocoAdr : Message +{ + uint8_t addressHigh; + uint8_t addressLow; + uint8_t checksum; + + LocoAdr(uint16_t address) : + Message{OPC_LOCO_ADR}, + addressHigh{static_cast(address >> 7)}, + addressLow{static_cast(address & 0x7F)} + { + checksum = calcChecksum(*this); + } +}; +static_assert(sizeof(LocoAdr) == 4); + +struct LocoSpd : SlotMessage +{ + uint8_t speed; + uint8_t checksum; + + LocoSpd(uint8_t _speed) : + SlotMessage{OPC_LOCO_SPD, SLOT_UNKNOWN}, + speed{_speed} + { + checksum = calcChecksum(*this); + } +}; +static_assert(sizeof(LocoSpd) == 4); + +struct LocoDirF : SlotMessage +{ + uint8_t dirf; + uint8_t checksum; + + LocoDirF(Direction direction, bool f0, bool f1, bool f2, bool f3, bool f4) : + SlotMessage{OPC_LOCO_DIRF, SLOT_UNKNOWN}, + dirf{0} + { + if(direction == Direction::Forward) + dirf |= SL_DIR; + if(f0) + dirf |= SL_F0; + if(f1) + dirf |= SL_F1; + if(f2) + dirf |= SL_F2; + if(f3) + dirf |= SL_F3; + if(f4) + dirf |= SL_F4; + } + + inline Direction direction() const + { + return (dirf & SL_DIR) ? Direction::Forward : Direction::Reverse; + } + + inline void setDirection(Direction value) + { + if(value == Direction::Forward) + dirf |= SL_DIR; + else + dirf &= ~SL_DIR; + } + + inline bool f0() const + { + return dirf & SL_F0; + } + + inline void setF0(bool value) + { + if(value) + dirf |= SL_F0; + else + dirf &= ~SL_F0; + } + + inline bool f1() const + { + return dirf & SL_F1; + } + + inline void setF1(bool value) + { + if(value) + dirf |= SL_F1; + else + dirf &= ~SL_F1; + } + + inline bool f2() const + { + return dirf & SL_F2; + } + + inline void setF2(bool value) + { + if(value) + dirf |= SL_F2; + else + dirf &= ~SL_F2; + } + + inline bool f3() const + { + return dirf & SL_F3; + } + + inline void setF3(bool value) + { + if(value) + dirf |= SL_F3; + else + dirf &= ~SL_F3; + } + + inline bool f4() const + { + return dirf & SL_F4; + } + + inline void setF4(bool value) + { + if(value) + dirf |= SL_F4; + else + dirf &= ~SL_F4; + } +}; +static_assert(sizeof(LocoDirF) == 4); + +struct LocoSnd : SlotMessage +{ + uint8_t snd; + uint8_t checksum; + + LocoSnd(bool f5, bool f6, bool f7, bool f8) : + SlotMessage{OPC_LOCO_SND, SLOT_UNKNOWN}, + snd{0} + { + if(f5) + snd |= SL_F5; + if(f6) + snd |= SL_F6; + if(f7) + snd |= SL_F7; + if(f8) + snd |= SL_F8; + + checksum = calcChecksum(*this); + } + + inline bool f5() const + { + return snd & SL_F5; + } + + inline void setF5(bool value) + { + if(value) + snd |= SL_F5; + else + snd &= ~SL_F5; + } + + inline bool f6() const + { + return snd & SL_F6; + } + + inline void setF6(bool value) + { + if(value) + snd |= SL_F6; + else + snd &= ~SL_F6; + } + + inline bool f7() const + { + return snd & SL_F7; + } + + inline void setF7(bool value) + { + if(value) + snd |= SL_F7; + else + snd &= ~SL_F7; + } + + inline bool f8() const + { + return snd & SL_F8; + } + + inline void setF8(bool value) + { + if(value) + snd |= SL_F8; + else + snd &= ~SL_F8; + } +}; +static_assert(sizeof(LocoSnd) == 4); + +struct LocoF9F12 : SlotMessage +{ + uint8_t function; + uint8_t checksum; + + LocoF9F12(bool f9, bool f10, bool f11, bool f12) : + SlotMessage{OPC_LOCO_F9F12, SLOT_UNKNOWN}, + function{0} + { + if(f9) + function |= SL_F9; + if(f10) + function |= SL_F10; + if(f11) + function |= SL_F11; + if(f12) + function |= SL_F12; + + checksum = calcChecksum(*this); + } + + inline bool f9() const + { + return function & SL_F9; + } + + inline void setF9(bool value) + { + if(value) + function |= SL_F9; + else + function &= ~SL_F9; + } + + inline bool f10() const + { + return function & SL_F10; + } + + inline void setF10(bool value) + { + if(value) + function |= SL_F10; + else + function &= ~SL_F10; + } + + inline bool f11() const + { + return function & SL_F7; + } + + inline void setF11(bool value) + { + if(value) + function |= SL_F11; + else + function &= ~SL_F11; + } + + inline bool f12() const + { + return function & SL_F8; + } + + inline void setF12(bool value) + { + if(value) + function |= SL_F12; + else + function &= ~SL_F12; + } +}; +static_assert(sizeof(LocoF9F12) == 4); + +/* + +2020-02-04 21:27:59.123954 [debug] cs1: unknown message: dataLen=0x0008, header=0x00a0, data=b2 11 70 2c +2020-02-04 21:27:59.413558 [debug] cs1: unknown message: dataLen=0x0008, header=0x00a0, data=b2 19 40 14 +2020-02-04 21:28:00.046282 [debug] cs1: unknown message: dataLen=0x0008, header=0x00a0, data=b2 11 50 0c +2020-02-04 21:28:00.433662 [debug] cs1: unknown message: dataLen=0x0008, header=0x00a0, data=b2 11 60 3c +2020-02-04 21:28:02.502012 [debug] cs1: unknown message: dataLen=0x0008, header=0x00a0, data=b2 09 70 34 +2020-02-04 21:28:02.913595 [debug] cs1: unknown message: dataLen=0x0008, header=0x00a0, data=b2 11 40 1c +2020-02-04 21:28:03.629638 [debug] cs1: unknown message: dataLen=0x0008, header=0x00a0, data=b2 09 50 14 +2020-02-04 21:28:03.937476 [debug] cs1: unknown message: dataLen=0x0008, header=0x00a0, data=b2 09 60 24 + + */ + + + +struct InputRep : Message +{ + uint8_t in1; + uint8_t in2; + uint8_t checksum; + + InputRep() : + Message{OPC_INPUT_REP} + { + } + + inline uint16_t address() const + { + return (in1 & 0x7F) | (static_cast(in2 & 0x0F) << 7); + } + + inline bool isSwitchInput() const + { + return in2 & 0x20; + } + + inline bool isAuxInput() const + { + return !isSwitchInput(); + } + + inline bool value() const + { + return in2 & 0x10; + } +}; +static_assert(sizeof(InputRep) == 4); + + + +struct RequestSlotData : Message +{ + uint8_t slot; + uint8_t data2; + uint8_t checksum; + + RequestSlotData(uint8_t _slot) : + Message(OPC_RQ_SL_DATA), + slot{_slot}, + data2{0} + { + checksum = calcChecksum(*this); + } +}; +static_assert(sizeof(RequestSlotData) == 4); + + +// d0 00 42 20 13 5e + +struct MultiSense : Message +{ + uint8_t data1; + uint8_t data2; + uint8_t data3; + uint8_t data4; + uint8_t checksum; + + MultiSense() : + Message(OPC_MULTI_SENSE) + { + } + + bool isTransponder() const + { + return + ((data1 & MULTI_SENSE_TYPE_MASK) == MULTI_SENSE_TYPE_TRANSPONDER_GONE) || + ((data1 & MULTI_SENSE_TYPE_MASK) == MULTI_SENSE_TYPE_TRANSPONDER_PRESENT); + } +}; +static_assert(sizeof(MultiSense) == 6); + +struct LocoF13F19 : Message +{ + uint8_t data1; + uint8_t slot; + uint8_t data3; + uint8_t function; + uint8_t checksum; + + LocoF13F19(bool f13, bool f14, bool f15, bool f16, bool f17, bool f18, bool f19) : + Message(OPC_D4), + data1{0x20}, + slot{SLOT_UNKNOWN}, + data3{0x08}, + function{0} + { + if(f13) + function |= SL_F13; + if(f14) + function |= SL_F14; + if(f15) + function |= SL_F15; + if(f16) + function |= SL_F16; + if(f17) + function |= SL_F17; + if(f18) + function |= SL_F18; + if(f19) + function |= SL_F19; + } + + inline bool f13() const + { + return function & SL_F13; + } + + inline void setF13(bool value) + { + if(value) + function |= SL_F13; + else + function &= ~SL_F13; + } + + inline bool f14() const + { + return function & SL_F14; + } + + inline void setF14(bool value) + { + if(value) + function |= SL_F14; + else + function &= ~SL_F14; + } + + inline bool f15() const + { + return function & SL_F15; + } + + inline void setF15(bool value) + { + if(value) + function |= SL_F15; + else + function &= ~SL_F15; + } + + inline bool f16() const + { + return function & SL_F16; + } + + inline void setF16(bool value) + { + if(value) + function |= SL_F16; + else + function &= ~SL_F16; + } + + inline bool f17() const + { + return function & SL_F17; + } + + inline void setF17(bool value) + { + if(value) + function |= SL_F17; + else + function &= ~SL_F17; + } + + inline bool f18() const + { + return function & SL_F18; + } + + inline void setF18(bool value) + { + if(value) + function |= SL_F18; + else + function &= ~SL_F18; + } + + inline bool f19() const + { + return function & SL_F19; + } + + inline void setF19(bool value) + { + if(value) + function |= SL_F19; + else + function &= ~SL_F19; + } +}; +static_assert(sizeof(LocoF13F19) == 6); + +struct LocoF20F28 : Message +{ + uint8_t data1; + uint8_t slot; + uint8_t data3; + uint8_t function; + uint8_t checksum; + + LocoF20F28(bool f20, bool f28) : + Message(OPC_D4), + data1{0x20}, + slot{SLOT_UNKNOWN}, + data3{0x05}, + function{0} + { + if(f20) + function |= SL_F20; + if(f28) + function |= SL_F28; + } + + inline bool f20() const + { + return function & SL_F20; + } + + inline void setF20(bool value) + { + if(value) + function |= SL_F20; + else + function &= ~SL_F20; + } + inline bool f28() const + { + return function & SL_F28; + } + + inline void setF28(bool value) + { + if(value) + function |= SL_F28; + else + function &= ~SL_F28; + } +}; +static_assert(sizeof(LocoF20F28) == 6); + +struct LocoF21F27 : Message +{ + uint8_t data1; + uint8_t slot; + uint8_t data3; + uint8_t function; + uint8_t checksum; + + LocoF21F27(bool f21, bool f22, bool f23, bool f24, bool f25, bool f26, bool f27) : + Message(OPC_D4), + data1{0x20}, + slot{SLOT_UNKNOWN}, + data3{0x09}, + function{0} + { + if(f21) + function |= SL_F21; + if(f22) + function |= SL_F22; + if(f23) + function |= SL_F23; + if(f24) + function |= SL_F24; + if(f25) + function |= SL_F25; + if(f26) + function |= SL_F26; + if(f27) + function |= SL_F27; + } + + inline bool f21() const + { + return function & SL_F21; + } + + inline void setF21(bool value) + { + if(value) + function |= SL_F21; + else + function &= ~SL_F21; + } + + inline bool f22() const + { + return function & SL_F22; + } + + inline void setF22(bool value) + { + if(value) + function |= SL_F22; + else + function &= ~SL_F22; + } + + inline bool f23() const + { + return function & SL_F23; + } + + inline void setF23(bool value) + { + if(value) + function |= SL_F23; + else + function &= ~SL_F23; + } + + inline bool f24() const + { + return function & SL_F24; + } + + inline void setF24(bool value) + { + if(value) + function |= SL_F24; + else + function &= ~SL_F24; + } + + inline bool f25() const + { + return function & SL_F25; + } + + inline void setF25(bool value) + { + if(value) + function |= SL_F25; + else + function &= ~SL_F25; + } + + inline bool f26() const + { + return function & SL_F26; + } + + inline void setF26(bool value) + { + if(value) + function |= SL_F26; + else + function &= ~SL_F26; + } + + inline bool f27() const + { + return function & SL_F27; + } + + inline void setF27(bool value) + { + if(value) + function |= SL_F27; + else + function &= ~SL_F27; + } +}; +static_assert(sizeof(LocoF21F27) == 6); + +struct MultiSenseTransponder : MultiSense +{ + bool isPresent() const + { + return (data1 & MULTI_SENSE_TYPE_MASK) == MULTI_SENSE_TYPE_TRANSPONDER_PRESENT; + } + + uint16_t sensorAddress() const + { + return (static_cast(data1 & 0x1F) << 7) | (data2 & 0x7F); + } + + uint16_t transponderAddress() const + { + if(isTransponderAddressLong()) + return (static_cast(data3 & 0x7F) << 7) | (data4 & 0x7F); + else + return (data4 & 0x7F); + } + + bool isTransponderAddressLong() const + { + return data3 != MULTI_SENSE_TRANSPONDER_ADDRESS_SHORT; + } +}; +static_assert(sizeof(MultiSenseTransponder) == 6); + +struct MultiSenseLong : Message +{ + uint8_t len; + uint8_t data1; + uint8_t data2; + uint8_t data3; + uint8_t data4; + uint8_t data5; + uint8_t data6; + uint8_t checksum; + + MultiSenseLong() : + Message(OPC_MULTI_SENSE_LONG), + len{9} + { + } + + bool isTransponder() const + { + return + ((data1 & MULTI_SENSE_TYPE_MASK) == MULTI_SENSE_TYPE_TRANSPONDER_GONE) || + ((data1 & MULTI_SENSE_TYPE_MASK) == MULTI_SENSE_TYPE_TRANSPONDER_PRESENT); + } +}; +static_assert(sizeof(MultiSenseLong) == 9); + +struct MultiSenseLongTransponder : MultiSenseLong +{ + bool isPresent() const + { + return (data1 & MULTI_SENSE_TYPE_MASK) == MULTI_SENSE_TYPE_TRANSPONDER_PRESENT; + } + + uint16_t sensorAddress() const + { + return (static_cast(data1 & 0x1F) << 7) | (data2 & 0x7F); + } + + uint16_t transponderAddress() const + { + if(isTransponderAddressLong()) + return (static_cast(data3 & 0x7F) << 7) | (data4 & 0x7F); + else + return (data4 & 0x7F); + } + + bool isTransponderAddressLong() const + { + return data3 != MULTI_SENSE_TRANSPONDER_ADDRESS_SHORT; + } + + Direction transponderDirection() const + { + return (data5 & 0x40) ? Direction::Forward : Direction::Reverse; + } +}; +static_assert(sizeof(MultiSenseLongTransponder) == 9); + +// OPC_SL_RD_DATA [E7 0E 1F 13 6F 01 30 07 08 19 00 00 00 52] +struct SlotReadData : Message +{ + uint8_t len; + uint8_t slot; + uint8_t stat; + uint8_t adr; + uint8_t spd; + uint8_t dirf; + uint8_t trk; + uint8_t ss2; + uint8_t adr2; + uint8_t snd; + uint8_t id1; + uint8_t id2; + uint8_t checksum; + + SlotReadData() : + Message(OPC_SL_RD_DATA), + len{14} + { + } + + bool isBusy() const + { + return stat & SL_BUSY; + } + + bool isActive() const + { + return stat & SL_ACTIVE; + } + + uint16_t address() const + { + return (static_cast(adr2) << 7) | adr; + } + + bool isEmergencyStop() const + { + return spd == 0x01; + } + + uint8_t speed() const + { + return spd > 1 ? spd - 1 : 0; + } + + inline Direction direction() const + { + return (dirf & SL_DIR) ? Direction::Forward : Direction::Reverse; + } + + inline void setDirection(Direction value) + { + if(value == Direction::Forward) + dirf |= SL_DIR; + else + dirf &= ~SL_DIR; + } + + inline bool f0() const + { + return dirf & SL_F0; + } + + inline void setF0(bool value) + { + if(value) + dirf |= SL_F0; + else + dirf &= ~SL_F0; + } + + inline bool f1() const + { + return dirf & SL_F1; + } + + inline void setF1(bool value) + { + if(value) + dirf |= SL_F1; + else + dirf &= ~SL_F1; + } + + inline bool f2() const + { + return dirf & SL_F2; + } + + inline void setF2(bool value) + { + if(value) + dirf |= SL_F2; + else + dirf &= ~SL_F2; + } + + inline bool f3() const + { + return dirf & SL_F3; + } + + inline void setF3(bool value) + { + if(value) + dirf |= SL_F3; + else + dirf &= ~SL_F3; + } + + inline bool f4() const + { + return dirf & SL_F4; + } + + inline void setF4(bool value) + { + if(value) + dirf |= SL_F4; + else + dirf &= ~SL_F4; + } + + inline bool f5() const + { + return snd & SL_F5; + } + + inline void setF5(bool value) + { + if(value) + snd |= SL_F5; + else + snd &= ~SL_F5; + } + + inline bool f6() const + { + return snd & SL_F6; + } + + inline void setF6(bool value) + { + if(value) + snd |= SL_F6; + else + snd &= ~SL_F6; + } + + inline bool f7() const + { + return snd & SL_F7; + } + + inline void setF7(bool value) + { + if(value) + snd |= SL_F7; + else + snd &= ~SL_F7; + } + + inline bool f8() const + { + return snd & SL_F8; + } + + inline void setF8(bool value) + { + if(value) + snd |= SL_F8; + else + snd &= ~SL_F8; + } +}; +static_assert(sizeof(SlotReadData) == 14); +/* +struct ImmediatePacket : Message +{ + uint8_t len; + uint8_t header; + uint8_t reps; + uint8_t dhi; + uint8_t im[5]; + uint8_t checksum; + + ImmediatePacket() : + Message(OPC_IMM_PACKET), + len{11}, + header{0x7F}, + reps{0}, + dhi{0}, + im{0, 0, 0, 0, 0} + { + } + + void setIMCount(uint8_t value) + { + assert(value <= 5); + reps = (reps & 0x8F) | ((value & 0x07) << 4); + } + + void updateDHI() + { + dhi = 0x20 | + (im[0] & 0x40) >> 7 | + (im[1] & 0x40) >> 6 | + (im[2] & 0x40) >> 5 | + (im[3] & 0x40) >> 4 | + (im[4] & 0x40) >> 3; + } +}; +static_assert(sizeof(ImmediatePacket) == 11); + +struct ImmediatePacketLoco : ImmediatePacket +{ + ImmediatePacketLoco(uint16_t address, uint8_t repeatCount) : + ImmediatePacket() + { + assert(repeatCount <= 7); + reps = repeatCount & 0x07; + + if(isLongAddress(address)) + { + im[0] = 0xC0 | ((address >> 8) & 0x3F); + im[1] = address & 0xFF; + } + else + im[0] = address & 0x7F; + } +}; +static_assert(sizeof(ImmediatePacketLoco) == sizeof(ImmediatePacket)); + +struct ImmediatePacketF9F12 : ImmediatePacketLoco +{ + ImmediatePacketF9F12(uint16_t address, bool f9, bool f10, bool f11, bool f12, uint8_t repeatCount = 2) : + ImmediatePacketLoco(address, repeatCount) + { + const uint8_t offset = (im[0] & 0x80) ? 2 : 1; + + im[offset] = 0xB0; // Function group two instruction: F9-F12 + if(f9) + im[offset] |= 0x01; + if(f10) + im[offset] |= 0x02; + if(f11) + im[offset] |= 0x04; + if(f12) + im[offset] |= 0x08; + + setIMCount(offset + 1); + updateDHI(); + checksum = calcChecksum(*this); + } +}; +static_assert(sizeof(ImmediatePacketF9F12) == sizeof(ImmediatePacketLoco)); + +struct ImmediatePacketF13F20 : ImmediatePacketLoco +{ + ImmediatePacketF13F20(uint16_t address, bool f13, bool f14, bool f15, bool f16, bool f17, bool f18, bool f19, bool f20, uint8_t repeatCount = 2) : + ImmediatePacketLoco(address, repeatCount) + { + uint8_t offset = (im[0] & 0x80) ? 2 : 1; + + im[offset++] = 0xDE; // Feature Expansion Instruction: F13-F20 function control + + if(f13) + im[offset] |= 0x01; + if(f14) + im[offset] |= 0x02; + if(f15) + im[offset] |= 0x04; + if(f16) + im[offset] |= 0x08; + if(f17) + im[offset] |= 0x10; + if(f18) + im[offset] |= 0x20; + if(f19) + im[offset] |= 0x40; + if(f20) + im[offset] |= 0x80; + + setIMCount(offset + 1); + updateDHI(); + checksum = calcChecksum(*this); + } +}; +static_assert(sizeof(ImmediatePacketF13F20) == sizeof(ImmediatePacketLoco)); + +struct ImmediatePacketF21F28 : ImmediatePacketLoco +{ + ImmediatePacketF21F28(uint16_t address, bool f21, bool f22, bool f23, bool f24, bool f25, bool f26, bool f27, bool f28, uint8_t repeatCount = 2) : + ImmediatePacketLoco(address, repeatCount) + { + uint8_t offset = (im[0] & 0x80) ? 2 : 1; + + im[offset++] = 0xDF; // Feature Expansion Instruction: F20-F28 function control + + if(f21) + im[offset] |= 0x01; + if(f22) + im[offset] |= 0x02; + if(f23) + im[offset] |= 0x04; + if(f24) + im[offset] |= 0x08; + if(f25) + im[offset] |= 0x10; + if(f26) + im[offset] |= 0x20; + if(f27) + im[offset] |= 0x40; + if(f28) + im[offset] |= 0x80; + + setIMCount(offset + 1); + updateDHI(); + checksum = calcChecksum(*this); + } +}; +static_assert(sizeof(ImmediatePacketF21F28) == sizeof(ImmediatePacketLoco)); +*/ + +} + +#endif diff --git a/server/src/hardware/protocol/loconet/opcode.hpp b/server/src/hardware/protocol/loconet/opcode.hpp new file mode 100644 index 00000000..3bd0a9ab --- /dev/null +++ b/server/src/hardware/protocol/loconet/opcode.hpp @@ -0,0 +1,112 @@ +/** + * server/src/hardware/protocol/loconet/opcode.hpp + * + * This file is part of the traintastic source code. + * + * Copyright (C) 2019-2020 Reinder Feenstra + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/** + * Portions Copyright (C) Digitrax Inc. + * + * LocoNet is a registered trademark of DigiTrax, Inc. + */ + +#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_OPCODE_HPP +#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_LOCONET_OPCODE_HPP + +#include +#include + +namespace Protocol::LocoNet { + +enum OpCode : uint8_t +{ + // 2 byte message opcodes: + OPC_BUSY = 0x81, + OPC_GPOFF = 0x82, + OPC_GPON = 0x83, + OPC_IDLE = 0x85, + + // 4 byte message opcodes: + OPC_LOCO_SPD = 0xA0, + OPC_LOCO_DIRF = 0xA1, + OPC_LOCO_SND = 0xA2, + OPC_LOCO_F9F12 = 0xA3, // based on reverse engineering, see loconet.md + OPC_SW_REQ = 0xB0, + OPC_SW_REP = 0xB1, + OPC_INPUT_REP = 0xB2, + OPC_LONG_ACK = 0xB4, + OPC_SLOT_STAT1 = 0xB5, + OPC_CONSIST_FUNC = 0xB6, + OPC_UNLINK_SLOTS = 0xB8, + OPC_LINK_SLOTS = 0xB9, + OPC_MOVE_SLOTS = 0xBA, + OPC_RQ_SL_DATA = 0xBB, + OPC_SW_STATE = 0xBC, + OPC_SW_ACK = 0xBD, + OPC_LOCO_ADR = 0xBF, + + // 6 byte message opcodes: + OPC_MULTI_SENSE = 0xD0, // based on reverse engineering, see loconet.md + OPC_D4 = 0xD4,// based on reverse engineering, probably used for multiple sub commands, see loconet.md + + // variable byte message opcodes: + OPC_MULTI_SENSE_LONG = 0XE0, // based on reverse engineering, see loconet.md + OPC_PEER_XFER = 0xE5, + OPC_SL_RD_DATA = 0xE7, + OPC_IMM_PACKET = 0xED, + OPC_WR_SL_DATA = 0xEF, +}; + +constexpr std::string_view to_string(OpCode value) +{ + switch(value) + { + case LocoNet::OPC_BUSY: return "OPC_BUSY"; + case LocoNet::OPC_GPOFF: return "OPC_GPOFFqqq"; + case LocoNet::OPC_GPON: return "OPC_GPON"; + case LocoNet::OPC_IDLE: return "OPC_IDLE"; + case LocoNet::OPC_LOCO_SPD: return "OPC_LOCO_SPD"; + case LocoNet::OPC_LOCO_DIRF: return "OPC_LOCO_DIRF"; + case LocoNet::OPC_LOCO_SND: return "OPC_LOCO_SND"; + case LocoNet::OPC_SW_REQ: return "OPC_SW_REQ"; + case LocoNet::OPC_SW_REP: return "OPC_SW_REP"; + case LocoNet::OPC_INPUT_REP: return "OPC_INPUT_REP"; + case LocoNet::OPC_LONG_ACK: return "OPC_LONG_ACK"; + case LocoNet::OPC_SLOT_STAT1: return "OPC_SLOT_STAT1"; + case LocoNet::OPC_CONSIST_FUNC: return "OPC_CONSIST_FUNC"; + case LocoNet::OPC_UNLINK_SLOTS: return "OPC_UNLINK_SLOTS"; + case LocoNet::OPC_LINK_SLOTS: return "OPC_LINK_SLOTS"; + case LocoNet::OPC_MOVE_SLOTS: return "OPC_MOVE_SLOTS"; + case LocoNet::OPC_RQ_SL_DATA: return "OPC_RQ_SL_DATA"; + case LocoNet::OPC_SW_STATE: return "OPC_SW_STATE"; + case LocoNet::OPC_SW_ACK: return "OPC_SW_ACK"; + case LocoNet::OPC_LOCO_ADR: return "OPC_LOCO_ADR"; + case LocoNet::OPC_MULTI_SENSE: return "OPC_MULTI_SENSE"; + case LocoNet::OPC_PEER_XFER: return "OPC_PEER_XFER"; + case LocoNet::OPC_SL_RD_DATA: return "OPC_SL_RD_DATA"; + case LocoNet::OPC_IMM_PACKET: return "OPC_IMM_PACKET"; + case LocoNet::OPC_WR_SL_DATA: return "OPC_WR_SL_DATA"; + } + + return {}; +} + +} + +#endif