700 Zeilen
13 KiB
C++
700 Zeilen
13 KiB
C++
/**
|
|
* server/src/hardware/protocol/marklincan/messages.hpp
|
|
*
|
|
* This file is part of the traintastic source code.
|
|
*
|
|
* Copyright (C) 2023 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.
|
|
*/
|
|
|
|
#ifndef TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_MESSAGES_HPP
|
|
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_MARKLINCAN_MESSAGES_HPP
|
|
|
|
#include <cstdint>
|
|
#include <cassert>
|
|
#include <cstring>
|
|
#include <string>
|
|
#include <initializer_list>
|
|
#include "../../../utils/byte.hpp"
|
|
#include "../../../utils/endian.hpp"
|
|
|
|
namespace MarklinCAN {
|
|
|
|
struct Message;
|
|
|
|
constexpr uint16_t calcHash(uint32_t uid)
|
|
{
|
|
const uint16_t hash = high16(uid) ^ low16(uid);
|
|
return ((hash << 3) & 0xFC00) | 0x0300 | (hash & 0x007F);
|
|
}
|
|
|
|
std::string toString(const Message& message);
|
|
|
|
enum class Command : uint8_t
|
|
{
|
|
System = 0x00,
|
|
Discovery = 0x01,
|
|
Bind = 0x02,
|
|
Verify = 0x03,
|
|
LocomotiveSpeed = 0x04,
|
|
LocomotiveDirection = 0x05,
|
|
LocomotiveFunction = 0x06,
|
|
ReadConfig = 0x07,
|
|
WriteConfig = 0x08,
|
|
AccessoryControl = 0x0B,
|
|
AccessoryConfig = 0x0C,
|
|
S88Polling = 0x10,
|
|
FeedbackEvent = 0x11,
|
|
SX1Event = 0x12,
|
|
Ping = 0x18, // or SoftwareVersionRequest
|
|
Update = 0x19,
|
|
ReadConfigData = 0x1A,
|
|
BootloaderCAN = 0x1B,
|
|
BootloaderTrack = 0x1C,
|
|
StatusDataConfig = 0x1D,
|
|
ConfigData = 0x20,
|
|
ConfigDataStream = 0x21,
|
|
};
|
|
|
|
enum class SystemSubCommand : uint8_t
|
|
{
|
|
SystemStop = 0x00,
|
|
SystemGo = 0x01,
|
|
SystemHalt = 0x02,
|
|
LocomotiveEmergencyStop = 0x03,
|
|
LocomotiveCycleEnd = 0x04,
|
|
AccessorySwitchTime = 0x06,
|
|
//Neuanmeldezahler = 0x09,
|
|
Overload = 0x0A,
|
|
Status = 0x0B,
|
|
ModelClock = 0x20,
|
|
};
|
|
|
|
enum class DeviceId : uint16_t
|
|
{
|
|
GleisFormatProzessorOrBooster= 0x0000, //!< Gleis Format Prozessor 60213,60214 / Booster 60173, 60174
|
|
Gleisbox = 0x0010, //!< Gleisbox 60112 und 60113
|
|
Connect6021 = 0x0020, //!< Connect 6021 Art-Nr.60128
|
|
MS2 = 0x0030, //!< MS 2 60653, Txxxxx
|
|
WirelessDevices = 0xFFE0, //!< Wireless Devices
|
|
CS2GUI = 0xFFFF //!< CS2-GUI (Master)
|
|
};
|
|
|
|
struct Message
|
|
{
|
|
static constexpr uint32_t responseMask = 0x00010000;
|
|
|
|
uint32_t id = 0;
|
|
uint8_t data[8] = {0, 0, 0, 0, 0, 0, 0, 0};
|
|
uint8_t dlc = 0;
|
|
|
|
Message() = default;
|
|
|
|
Message(Command command, bool response, uint32_t uid = 0)
|
|
{
|
|
id = (static_cast<uint32_t>(command) << 17) | (response ? 0x00010000 : 0) | calcHash(uid);
|
|
}
|
|
|
|
Message(Command command, bool response, std::initializer_list<uint8_t> data_, uint32_t uid = 0)
|
|
: Message(command, response, uid)
|
|
{
|
|
assert(data_.size() <= sizeof(data));
|
|
dlc = data_.size();
|
|
if(data_.size() != 0)
|
|
std::memcpy(data, data_.begin(), data_.size());
|
|
}
|
|
|
|
uint8_t priority() const
|
|
{
|
|
return (id >> 25) & 0x0F;
|
|
}
|
|
|
|
Command command() const
|
|
{
|
|
return static_cast<Command>((id >> 17) & 0xFF);
|
|
}
|
|
|
|
bool isResponse() const
|
|
{
|
|
return id & responseMask;
|
|
}
|
|
|
|
void setResponse(bool value)
|
|
{
|
|
if(value)
|
|
id |= responseMask;
|
|
else
|
|
id &= ~responseMask;
|
|
}
|
|
|
|
uint16_t hash() const
|
|
{
|
|
return id & 0xFFFF;
|
|
}
|
|
};
|
|
|
|
struct UidMessage : Message
|
|
{
|
|
UidMessage(Command command, bool response, uint32_t uid)
|
|
: Message(command, response, uid)
|
|
{
|
|
dlc = 4;
|
|
*reinterpret_cast<uint32_t*>(&data[0]) = host_to_be(uid);
|
|
}
|
|
|
|
uint32_t uid() const
|
|
{
|
|
return be_to_host(*reinterpret_cast<const uint32_t*>(&data[0]));
|
|
}
|
|
};
|
|
|
|
struct SystemMessage : UidMessage
|
|
{
|
|
SystemMessage(SystemSubCommand subCommand, uint32_t uid = 0)
|
|
: UidMessage{Command::System, false, uid}
|
|
{
|
|
dlc = 5;
|
|
data[4] = static_cast<uint8_t>(subCommand);
|
|
}
|
|
|
|
SystemSubCommand subCommand() const
|
|
{
|
|
return static_cast<SystemSubCommand>(data[4]);
|
|
}
|
|
};
|
|
|
|
struct SystemStop : SystemMessage
|
|
{
|
|
SystemStop(uint32_t uid = 0)
|
|
: SystemMessage(SystemSubCommand::SystemStop, uid)
|
|
{
|
|
}
|
|
};
|
|
|
|
struct SystemGo : SystemMessage
|
|
{
|
|
SystemGo(uint32_t uid = 0)
|
|
: SystemMessage(SystemSubCommand::SystemGo, uid)
|
|
{
|
|
}
|
|
};
|
|
|
|
struct SystemHalt : SystemMessage
|
|
{
|
|
SystemHalt(uint32_t uid = 0)
|
|
: SystemMessage(SystemSubCommand::SystemHalt, uid)
|
|
{
|
|
}
|
|
};
|
|
|
|
struct ModelClock : SystemMessage
|
|
{
|
|
ModelClock(uint32_t uid, uint8_t hour_, uint8_t minute_, uint8_t factor_)
|
|
: SystemMessage(SystemSubCommand::ModelClock, uid)
|
|
{
|
|
dlc = 8;
|
|
setHour(hour_);
|
|
setMinute(minute_);
|
|
setFactor(factor_);
|
|
}
|
|
|
|
uint8_t hour() const
|
|
{
|
|
return data[5];
|
|
}
|
|
|
|
void setHour(uint8_t value)
|
|
{
|
|
assert(value < 24);
|
|
data[5] = value;
|
|
}
|
|
|
|
uint8_t minute() const
|
|
{
|
|
return data[6];
|
|
}
|
|
|
|
void setMinute(uint8_t value)
|
|
{
|
|
assert(value < 60);
|
|
data[6] = value;
|
|
}
|
|
|
|
uint8_t factor() const
|
|
{
|
|
return data[7];
|
|
}
|
|
|
|
void setFactor(uint8_t value)
|
|
{
|
|
assert(value <= 60);
|
|
data[7] = value;
|
|
}
|
|
};
|
|
|
|
struct LocomotiveEmergencyStop : SystemMessage
|
|
{
|
|
LocomotiveEmergencyStop(uint32_t uid)
|
|
: SystemMessage(SystemSubCommand::LocomotiveEmergencyStop, uid)
|
|
{
|
|
}
|
|
};
|
|
|
|
struct AccessorySwitchTime : SystemMessage
|
|
{
|
|
AccessorySwitchTime(uint16_t switchTime_, uint32_t uid = 0)
|
|
: SystemMessage(SystemSubCommand::AccessorySwitchTime, uid)
|
|
{
|
|
dlc = 7;
|
|
setSwitchTime(switchTime_);
|
|
}
|
|
|
|
uint16_t switchTime() const
|
|
{
|
|
return to16(data[6], data[5]);
|
|
}
|
|
|
|
void setSwitchTime(uint16_t value)
|
|
{
|
|
assert(value <= 16300); // 163s in 10ms steps
|
|
data[5] = high8(value);
|
|
data[6] = low8(value);
|
|
}
|
|
};
|
|
|
|
struct LocomotiveSpeed : UidMessage
|
|
{
|
|
static constexpr uint16_t speedMax = 1000;
|
|
|
|
LocomotiveSpeed(uint32_t uid)
|
|
: UidMessage(Command::LocomotiveSpeed, false, uid)
|
|
{
|
|
}
|
|
|
|
LocomotiveSpeed(uint32_t uid, uint16_t speed_, bool response = false)
|
|
: LocomotiveSpeed(uid)
|
|
{
|
|
setResponse(response);
|
|
dlc = 6;
|
|
setSpeed(speed_);
|
|
}
|
|
|
|
bool hasSpeed() const
|
|
{
|
|
return dlc == 6;
|
|
}
|
|
|
|
uint16_t speed() const
|
|
{
|
|
assert(hasSpeed());
|
|
return to16(data[5], data[4]);
|
|
}
|
|
|
|
void setSpeed(uint16_t value)
|
|
{
|
|
assert(hasSpeed());
|
|
assert(value <= speedMax);
|
|
data[4] = high8(value);
|
|
data[5] = low8(value);
|
|
}
|
|
};
|
|
|
|
struct LocomotiveDirection : UidMessage
|
|
{
|
|
enum class Direction : uint8_t
|
|
{
|
|
Same = 0,
|
|
Forward = 1,
|
|
Reverse = 2,
|
|
Inverse = 3,
|
|
};
|
|
|
|
LocomotiveDirection(uint32_t uid)
|
|
: UidMessage(Command::LocomotiveDirection, false, uid)
|
|
{
|
|
}
|
|
|
|
LocomotiveDirection(uint32_t uid, Direction direction_, bool response = false)
|
|
: LocomotiveDirection(uid)
|
|
{
|
|
setResponse(response);
|
|
dlc = 5;
|
|
setDirection(direction_);
|
|
}
|
|
|
|
bool hasDirection() const
|
|
{
|
|
return dlc == 5;
|
|
}
|
|
|
|
Direction direction() const
|
|
{
|
|
assert(hasDirection());
|
|
return static_cast<Direction>(data[4]);
|
|
}
|
|
|
|
void setDirection(Direction value)
|
|
{
|
|
assert(hasDirection());
|
|
data[4] = static_cast<uint8_t>(value);
|
|
}
|
|
};
|
|
|
|
struct LocomotiveFunction : UidMessage
|
|
{
|
|
static constexpr uint8_t numberMax = 31;
|
|
static constexpr uint8_t valueOff = 0;
|
|
static constexpr uint8_t valueOnMin = 1;
|
|
static constexpr uint8_t valueOnMax = 31;
|
|
|
|
LocomotiveFunction(uint32_t uid, uint8_t number_)
|
|
: UidMessage(Command::LocomotiveFunction, false, uid)
|
|
{
|
|
dlc = 5;
|
|
setNumber(number_);
|
|
}
|
|
|
|
LocomotiveFunction(uint32_t uid, uint8_t number_, uint8_t value_, bool response = false)
|
|
: LocomotiveFunction(uid, number_)
|
|
{
|
|
setResponse(response);
|
|
dlc = 6;
|
|
setNumber(value_);
|
|
}
|
|
|
|
LocomotiveFunction(uint32_t uid, uint8_t number_, bool value_, bool response = false)
|
|
: LocomotiveFunction(uid, number_)
|
|
{
|
|
setResponse(response);
|
|
dlc = 6;
|
|
setValue(value_);
|
|
}
|
|
|
|
uint8_t number() const
|
|
{
|
|
return data[4];
|
|
}
|
|
|
|
void setNumber(uint8_t value)
|
|
{
|
|
assert(value <= numberMax);
|
|
data[4] = value;
|
|
}
|
|
|
|
bool hasValue() const
|
|
{
|
|
return dlc == 6;
|
|
}
|
|
|
|
bool isOn() const
|
|
{
|
|
return value() != valueOff;
|
|
}
|
|
|
|
uint8_t value() const
|
|
{
|
|
assert(hasValue());
|
|
return data[5];
|
|
}
|
|
|
|
void setValue(bool value)
|
|
{
|
|
setValue(value ? valueOnMin : valueOff);
|
|
}
|
|
|
|
void setValue(uint8_t value)
|
|
{
|
|
assert(hasValue());
|
|
assert(value <= valueOnMax);
|
|
data[5] = value;
|
|
}
|
|
};
|
|
|
|
struct AccessoryControl : UidMessage
|
|
{
|
|
static constexpr uint8_t positionOff = 0;
|
|
static constexpr uint8_t positionOn = 1;
|
|
|
|
AccessoryControl(uint32_t uid_, bool response = false)
|
|
: UidMessage(Command::AccessoryControl, response, uid_)
|
|
{
|
|
dlc = 6;
|
|
}
|
|
|
|
uint8_t position() const
|
|
{
|
|
return data[4];
|
|
}
|
|
|
|
void setPosition(uint8_t value)
|
|
{
|
|
data[4] = value;
|
|
}
|
|
|
|
uint8_t current() const
|
|
{
|
|
return data[5];
|
|
}
|
|
|
|
void setCurrent(uint8_t value)
|
|
{
|
|
assert(value <= 31);
|
|
data[5] = value;
|
|
}
|
|
|
|
bool isDefaultSwitchTime() const
|
|
{
|
|
return dlc == 6;
|
|
}
|
|
|
|
void setDefaultSwitchTime()
|
|
{
|
|
dlc = 6;
|
|
}
|
|
|
|
uint16_t switchTime() const
|
|
{
|
|
assert(dlc == 8);
|
|
return to16(data[7], data[6]);
|
|
}
|
|
|
|
void setSwitchTime(uint16_t value)
|
|
{
|
|
dlc = 8;
|
|
data[6] = high8(value);
|
|
data[7] = low8(value);
|
|
}
|
|
};
|
|
|
|
struct S88ModuleCount : UidMessage
|
|
{
|
|
S88ModuleCount(uint32_t uid, uint8_t count_)
|
|
: UidMessage(Command::S88Polling, false, uid)
|
|
{
|
|
dlc = 5;
|
|
setCount(count_);
|
|
}
|
|
|
|
uint8_t count() const
|
|
{
|
|
return data[5];
|
|
}
|
|
|
|
void setCount(uint8_t value)
|
|
{
|
|
data[5] = value;
|
|
}
|
|
};
|
|
|
|
struct S88ModuleState : UidMessage
|
|
{
|
|
//! \todo verify state endianess, asume big endian for now.
|
|
|
|
S88ModuleState(uint32_t uid, uint8_t module_)
|
|
: UidMessage(Command::S88Polling, true, uid)
|
|
{
|
|
dlc = 7;
|
|
setModule(module_);
|
|
}
|
|
|
|
uint8_t module() const
|
|
{
|
|
return data[5];
|
|
}
|
|
|
|
void setModule(uint8_t value)
|
|
{
|
|
data[5] = value;
|
|
}
|
|
|
|
uint16_t state() const
|
|
{
|
|
return to16(data[7], data[6]);
|
|
}
|
|
|
|
void setState(uint16_t value)
|
|
{
|
|
data[6] = low8(value);
|
|
data[7] = high8(value);
|
|
}
|
|
|
|
bool getState(uint8_t index) const
|
|
{
|
|
assert(index < 16);
|
|
return state() & (1 << index);
|
|
}
|
|
|
|
void setState(uint8_t index, bool value)
|
|
{
|
|
assert(index < 16);
|
|
if(value)
|
|
setState(state() | (1 << index));
|
|
else
|
|
setState(state() & ~(1 << index));
|
|
}
|
|
};
|
|
|
|
struct FeedbackMessage : UidMessage
|
|
{
|
|
FeedbackMessage(uint16_t deviceId_, uint16_t contactId_, bool response)
|
|
: UidMessage(Command::FeedbackEvent, response, 0)
|
|
{
|
|
dlc = 4;
|
|
setDeviceId(deviceId_);
|
|
setContactId(contactId_);
|
|
}
|
|
|
|
uint16_t deviceId() const
|
|
{
|
|
return to16(data[1], data[0]);
|
|
}
|
|
|
|
void setDeviceId(uint16_t value)
|
|
{
|
|
data[0] = high8(value);
|
|
data[1] = low8(value);
|
|
}
|
|
|
|
uint16_t contactId() const
|
|
{
|
|
return to16(data[3], data[2]);
|
|
}
|
|
|
|
void setContactId(uint16_t value)
|
|
{
|
|
data[2] = high8(value);
|
|
data[3] = low8(value);
|
|
}
|
|
};
|
|
|
|
struct FeedbackStateRequest : FeedbackMessage
|
|
{
|
|
FeedbackStateRequest(uint16_t deviceId_, uint16_t contactId_)
|
|
: FeedbackMessage(deviceId_, contactId_, false)
|
|
{
|
|
}
|
|
};
|
|
|
|
struct FeedbackStateParameter : FeedbackMessage
|
|
{
|
|
FeedbackStateParameter(uint16_t deviceId_, uint16_t contactId_, uint8_t parameter_)
|
|
: FeedbackMessage(deviceId_, contactId_, false)
|
|
{
|
|
dlc = 5;
|
|
setParameter(parameter_);
|
|
}
|
|
|
|
uint8_t parameter() const
|
|
{
|
|
return data[4];
|
|
}
|
|
|
|
void setParameter(uint8_t value)
|
|
{
|
|
data[4] = value;
|
|
}
|
|
};
|
|
|
|
struct FeedbackState : FeedbackMessage
|
|
{
|
|
FeedbackState(uint16_t deviceId_, uint16_t contactId_)
|
|
: FeedbackMessage(deviceId_, contactId_, true)
|
|
{
|
|
dlc = 8;
|
|
}
|
|
|
|
uint8_t stateOld() const
|
|
{
|
|
return data[4];
|
|
}
|
|
|
|
void setStateOld(uint8_t value)
|
|
{
|
|
data[4] = value;
|
|
}
|
|
|
|
uint8_t stateNew() const
|
|
{
|
|
return data[5];
|
|
}
|
|
|
|
void setStateNew(uint8_t value)
|
|
{
|
|
data[5] = value;
|
|
}
|
|
|
|
uint16_t time() const
|
|
{
|
|
return to16(data[7], data[6]);
|
|
}
|
|
|
|
void setTime(uint16_t value)
|
|
{
|
|
data[6] = high8(value);
|
|
data[7] = low8(value);
|
|
}
|
|
};
|
|
|
|
struct Ping : Message
|
|
{
|
|
Ping()
|
|
: Message(Command::Ping, false)
|
|
{
|
|
}
|
|
};
|
|
|
|
struct PingReply : UidMessage
|
|
{
|
|
PingReply(uint32_t uid)
|
|
: UidMessage(Command::Ping, true, uid)
|
|
{
|
|
dlc = 8;
|
|
}
|
|
|
|
uint8_t softwareVersionMajor() const
|
|
{
|
|
return data[4];
|
|
}
|
|
|
|
uint8_t softwareVersionMinor() const
|
|
{
|
|
return data[5];
|
|
}
|
|
|
|
void setSoftwareVersion(uint8_t major, uint8_t minor)
|
|
{
|
|
data[4] = major;
|
|
data[5] = minor;
|
|
}
|
|
|
|
DeviceId deviceId() const
|
|
{
|
|
return static_cast<DeviceId>(to16(data[7], data[6]));
|
|
}
|
|
|
|
void setDeviceId(DeviceId value)
|
|
{
|
|
data[6] = high8(static_cast<uint16_t>(value));
|
|
data[7] = low8(static_cast<uint16_t>(value));
|
|
}
|
|
};
|
|
|
|
std::string_view toString(MarklinCAN::DeviceId value);
|
|
|
|
}
|
|
|
|
#endif
|