diff --git a/server/src/hardware/protocol/z21/clientkernel.cpp b/server/src/hardware/protocol/z21/clientkernel.cpp index 12c8872a..d78a0892 100644 --- a/server/src/hardware/protocol/z21/clientkernel.cpp +++ b/server/src/hardware/protocol/z21/clientkernel.cpp @@ -89,6 +89,21 @@ void ClientKernel::receive(const Message& message) } break; + case LAN_X_EXT_ACCESSORY_INFO: + if(message.dataLen() == sizeof(LanXExtAccessoryInfo)) + { + const auto& reply = static_cast(message); + if(reply.isDataValid()) + { + EventLoop::call( + [this, address=reply.address(), value=reply.aspect()]() + { + m_outputController->updateOutputValue(OutputChannel::DCCext, address, value); + }); + } + } + break; + case LAN_X_BC: if(message == LanXBCTrackPowerOff() || message == LanXBCTrackShortCircuit()) { diff --git a/server/src/hardware/protocol/z21/iohandler/simulationiohandler.cpp b/server/src/hardware/protocol/z21/iohandler/simulationiohandler.cpp index 74cb3a66..ee4c88e1 100644 --- a/server/src/hardware/protocol/z21/iohandler/simulationiohandler.cpp +++ b/server/src/hardware/protocol/z21/iohandler/simulationiohandler.cpp @@ -151,6 +151,29 @@ bool SimulationIOHandler::send(const Message& message) } break; } + case LAN_X_SET_EXT_ACCESSORY: + { + if(message.dataLen() == sizeof(LanXSetExtAccessory)) + { + const auto& setAccessory = static_cast(message); + if((m_broadcastFlags & BroadcastFlags::PowerLocoTurnoutChanges) == BroadcastFlags::PowerLocoTurnoutChanges) + { + // Client has subscribed to turnout changes + reply(LanXExtAccessoryInfo(setAccessory.address(), setAccessory.aspect(), false)); + } + } + break; + } + case LAN_X_EXT_ACCESSORY_INFO: + { + if(message.dataLen() == sizeof(LanXGetExtAccessoryInfo)) + { + const auto& getAccessory = static_cast(message); + //We do not keep a record of accessory states so send "Unknown Position" + reply(LanXExtAccessoryInfo(getAccessory.address(), 0, true)); + } + break; + } } break; } diff --git a/server/src/hardware/protocol/z21/messages.cpp b/server/src/hardware/protocol/z21/messages.cpp index b990c716..26ff5f67 100644 --- a/server/src/hardware/protocol/z21/messages.cpp +++ b/server/src/hardware/protocol/z21/messages.cpp @@ -120,6 +120,24 @@ std::string toString(const Message& message, bool raw) s.append(" queue=").append(setTurnout.queue() ? "yes" : "no"); break; } + case LAN_X_EXT_ACCESSORY_INFO: + { + if(message.dataLen() == sizeof(LanXExtAccessoryInfo)) + { + const auto& reply = static_cast(message); + s = "LAN_X_EXT_ACCESSORY_INFO"; + s.append(" address=").append(std::to_string(reply.address())); + s.append(" aspect=").append(std::to_string(reply.aspect())); + s.append(" unknown=").append(std::to_string(!reply.isDataValid())); + } + else + { + const auto& getAccessoryInfo = static_cast(message); + s = "LAN_X_GET_EXT_ACCESSORY_INFO"; + s.append(" address=").append(std::to_string(getAccessoryInfo.address())); + } + break; + } case LAN_X_SET_EXT_ACCESSORY: { const auto& setExtAccessory = static_cast(message); diff --git a/server/src/hardware/protocol/z21/messages.hpp b/server/src/hardware/protocol/z21/messages.hpp index 60c7a213..691fc065 100644 --- a/server/src/hardware/protocol/z21/messages.hpp +++ b/server/src/hardware/protocol/z21/messages.hpp @@ -145,6 +145,8 @@ enum LocoMode : uint8_t static constexpr uint8_t LAN_X_TURNOUT_INFO = 0x43; static constexpr uint8_t LAN_X_SET_TURNOUT = 0x53; + +static constexpr uint8_t LAN_X_EXT_ACCESSORY_INFO = 0x44; static constexpr uint8_t LAN_X_SET_EXT_ACCESSORY = 0x54; static constexpr uint8_t LAN_X_BC = 0x61; @@ -549,9 +551,109 @@ struct LanXSetExtAccessory : LanX { return rawAddress() + 3; } + + inline uint8_t aspect() const + { + return db2; + } } ATTRIBUTE_PACKED; static_assert(sizeof(LanXSetExtAccessory) == 10); +// LAN_X_GET_EXT_ACCESSORY_INFO +struct LanXGetExtAccessoryInfo : LanX +{ + uint8_t addressMSB; + uint8_t addressLSB; + uint8_t db2 = 0x00; // Reserved for future extension, must be 0x00 + uint8_t checksum; + + LanXGetExtAccessoryInfo(uint16_t address) + : LanX(sizeof(LanXGetExtAccessoryInfo), LAN_X_EXT_ACCESSORY_INFO) + , addressMSB(high8(address + 3)) + , addressLSB(low8(address + 3)) + { + } + + inline uint16_t rawAddress() const + { + return to16(addressLSB, addressMSB); + } + + inline uint16_t address() const + { + return rawAddress() + 3; + } +} ATTRIBUTE_PACKED; +static_assert(sizeof(LanXGetExtAccessoryInfo) == 9); + +// LAN_X_EXT_ACCESSORY_INFO +struct LanXExtAccessoryInfo : LanX +{ + uint8_t addressMSB; + uint8_t addressLSB; + uint8_t db2; + uint8_t db3 = 0x00; + uint8_t checksum; + + LanXExtAccessoryInfo(uint16_t address) + : LanX(sizeof(LanXExtAccessoryInfo), LAN_X_EXT_ACCESSORY_INFO) + , addressMSB(high8(address + 3)) + , addressLSB(low8(address + 3)) + { + } + + LanXExtAccessoryInfo(uint16_t address, uint8_t aspect, bool isUnknown) + : LanXExtAccessoryInfo(address) + { + db2 = aspect; + db3 = isUnknown ? 0xFF : 0x00; // 0xFF represent Unknown Data + updateChecksum(); + } + + LanXExtAccessoryInfo(uint16_t address, bool dir, uint8_t powerOnTime) + : LanXExtAccessoryInfo(address) + { + assert(powerOnTime <= 127); + db2 = powerOnTime & 0x7F; + if(dir) + { + db2 |= 0x80; + } + updateChecksum(); + } + + inline uint16_t rawAddress() const + { + return to16(addressLSB, addressMSB); + } + + inline uint16_t address() const + { + return rawAddress() + 3; + } + + inline bool isDataValid() const + { + return db3 == 0x00; + } + + inline uint8_t aspect() const + { + return db2; + } + + inline bool direction() const + { + return db2 & 0x80; + } + + inline bool powerOnTime() const + { + return db2 & 0x7F; + } +} ATTRIBUTE_PACKED; +static_assert(sizeof(LanXExtAccessoryInfo) == 10); + // LAN_X_SET_STOP struct LanXSetStop : LanX {