Added 1st version of Traintastic DIY interface

Dieser Commit ist enthalten in:
Reinder Feenstra 2022-06-28 23:10:08 +02:00
Ursprung cac339a001
Commit f6f5511f25
35 geänderte Dateien mit 2701 neuen und 0 gelöschten Zeilen

Datei anzeigen

@ -38,6 +38,7 @@
#include <traintastic/enum/serialflowcontrol.hpp>
#include <traintastic/enum/signalaspect.hpp>
#include <traintastic/enum/speedunit.hpp>
#include <traintastic/enum/traintasticdiyinterfacetype.hpp>
#include <traintastic/enum/turnoutposition.hpp>
#include <traintastic/enum/weightunit.hpp>
#include <traintastic/enum/worldscale.hpp>
@ -86,6 +87,7 @@ QString translateEnum(const QString& enumName, qint64 value)
TRANSLATE_ENUM(SerialFlowControl)
TRANSLATE_ENUM(SignalAspect)
TRANSLATE_ENUM(SpeedUnit)
TRANSLATE_ENUM(TraintasticDIYInterfaceType)
TRANSLATE_ENUM(TurnoutPosition)
TRANSLATE_ENUM(WeightUnit)
TRANSLATE_ENUM(WorldScale)

Datei anzeigen

@ -42,6 +42,10 @@ TODO
Information about the connected HSI-88 interface, e.g. *Ver. 0.62 / 08.07.02 / HSI-88 / (c) LDT*.
## I2005: *info* {#i2005}
Information about the connected Traintastic DIY device.
## I9001: Stopped script {#i9001}
TODO

Datei anzeigen

@ -33,6 +33,12 @@ If not, remapping decoder functions or using a different command station is the
**Solution:** The number of speed steps that can be used is determinded by the command station or interface. Changing the decoders speed steps to *Auto* should usally work.
## W2004: Input address *address* is invalid {#w2004}
The connected Traintastic DIY device does not have an input with *address*.
## W2005: Output address *address* is invalid {#w2005}
The connected Traintastic DIY device does not have an output with *address*.
## W9999: *message* {#w9999}
Custom warning message generated by a [Lua script](../lua.md).

Datei anzeigen

@ -0,0 +1,203 @@
# Traintastic DIY protocol {#tdiyp}
The Traintastic DIY protocol is designed to make it possible to develop custom hardware, e.g. by using the Arduino platform and use it with Traintastic.
The Traintastic DIY protocol is currently supported via:
- Serial port: baudrate and flow control can be chosen, data format is fixed at 8N1 (8 data byte, no parity, one stop bit)
- Network connection (TCP): port number can be chosen.
It is currently limited to:
- Reading inputs
- Controlling outputs
Other features might be added in the future.
## Message format {#tdiyp-message-format}
Each Traintastic DIY protocol message starts with an opcode byte, besides the message type it also contains the data payload length in the lowest nibble.
If the lowest nibble is `0xF` the the second byte of the message determines the payload length.
The message always ends with a checksum byte, the checksum is the result of XOR-ing of all message bytes.
Examples:
```
0x50 0x50
```
The lowest nibble of the first byte is `0` indicating a zero byte payload.
The checksum is identical to the opcode, there is no data to XOR with.
```
0x24 0x11 0x22 0x33 0x44 0x60
```
The lowest nibble of the first byte is `4` indicating a 4 byte payload.
The checksum is `0x24` XOR `0x11` XOR `0x22` XOR `0x33` XOR `0x44` = `0x60`.
```
0x2F 0x20 ... 0x??
```
The lowest nibble of the first byte is `F` indicating that the second byte must be used as payload length, 32 byte.
The checksum is `0x2F` XOR `0x20` XOR *all payload bytes*.
## Messages {#tdiyp-messages}
Messages are send by Traintastic to the DIY device, for every message the DIY device sends a response message.
Some messages are sent unsolicited by the DIY device to Traintastic if changes are detected by the DIY device.
| Command | |
|---------------------------------------------|-----------------------------------------|
| [Heartbeat](#tdiyp-heartbeat) | Mandatory |
| [Get information](#tdiyp-get-information) | Mandatory |
| [Get features](#tdiyp-get-features) | Mandatory |
| [Get input state](#tdiyp-get-input-state) | Mandatory if input feature flag is set |
| [Set input state](#tdiyp-set-input-state) | Mandatory if input feature flag is set |
| [Get output state](#tdiyp-get-output-state) | Mandatory if output feature flag is set |
| [Set output state](#tdiyp-set-output-state) | Mandatory if output feature flag is set |
**Badges**:
- The $badge:since:v0.2$ badge indicates in which version of Traintastic the message is added.
### Heartbeat $badge:since:v0.2$ {#tdiyp-heartbeat}
The heartbeat message is sent by Traintastic to check if the DIY device is (still) present, the DIY device responds with a heartbeat message.
The heartbeat rate can be configured in Traintastic, by default the heartbeat message is one second after the last message is received from the DIY device.
#### Request message
```
0x00 <checksum>
```
#### Response message
```
0x00 <checksum>
```
### Get information $badge:since:v0.2$ {#tdiyp-get-information}
The *get information* message is the first message sent after connecting.
The DIY device responds with an *information* message containing a description of the connected DIY device.
This is pure informational and displayed in the message console.
#### Request message
```
0xF0 <checksum>
```
#### Response message
```
0xFF <len> <text...> <checksum>
```
### Get features $badge:since:v0.2$ {#tdiyp-get-features}
The *get features* message is the second message sent by Traintastic after connecting.
The DIY device responds with a *features* message containing flags which indicate what is supported by the DIY device.
#### Request message
```
0xE0 <checksum>
```
#### Response message
```
0xE4 <FF1> <FF2> <FF3> <FF4> <checksum>
```
- `<FF1>` feature flags 1, OR-ed value of:
- `0x01` input feature flag: set if the DIY device has inputs $badge:since:v0.2$
- `0x02` output feature flag: set if the DIY device has outputs $badge:since:v0.2$
- `0x04`...`0x80` are reserved, do not use
- `<FF2>` feature flags 2, reserved must be `0x00`
- `<FF3>` feature flags 3, reserved must be `0x00`
- `<FF4>` feature flags 4, reserved must be `0x00`
### Get input state $badge:since:v0.2$ {#tdiyp-get-input-state}
Sent by Traintastic to retrieve the current input state.
Address zero has a special meaning, it is used as broadcast address to retrieve the current state of all inputs.
#### Request message
```
0x12 <AH> <AL> <checksum>
```
- `<AH>` high byte of 16bit input address
- `<AL>` low byte of 16bit input address
#### Response
If the address is non zero the DIY device responds with a *[set input state](#tdiyp-set-input-state)* message containing the current state of the input address.
If the address is zero the DIY device responds with multiple *[set input state](#tdiyp-set-input-state)* messages, one for each know input address or
send a single *[set input state](#tdiyp-set-input-state)* message with address zero and state *invalid* to inform Traintastic that the address zero request is not supported.
### Set input state $badge:since:v0.2$ {#tdiyp-set-input-state}
Sent by the DIY device as response to the *[get input state](#tdiyp-get-input-state)* message and must be sent by the DIY device whenever an input state changes.
#### Message
```
0x13 <AH> <AL> <S> <checksum>
```
- `<AH>` high byte of 16bit input address
- `<AL>` low byte of 16bit input address
- `<S>` input state:
- `0x00` if input state is unknown
- `0x01` if input state is low/false
- `0x02` if input state is high/true
- `0x03` if input is invalid (only as response to a *[get input state](#tdiyp-get-input-state)* message)
- `0x04`...`0xFF` are reserved, do not use
Examples:
```
0x13 0x00 0x12 0x02 0x03
```
Input 18 state changed to high/true
```
0x13 0x02 0xA2 0x01 0xB2
```
Input 674 state changed to low/false
### Get output state $badge:since:v0.2$ {#tdiyp-get-output-state}
Sent by Traintastic to retrieve the current output state.
Address zero has a special meaning, it is used as broadcast address to retrieve the current state of all outputs.
#### Request message
```
0x22 <AH> <AL> <checksum>
```
- `<AH>` high byte of 16bit output address
- `<AL>` low byte of 16bit output address
#### Response message
If the address is non zero the DIY device responds with a *[set output state](#tdiyp-set-output-state)* message containing the current state of the output address.
If the address is zero the DIY device responds with multiple *[set inpoutputut state](#tdiyp-set-output-state)* messages, one for each know output address or
send a single *[set output state](#tdiyp-set-output-state)* message with address zero and state *invalid* to inform Traintastic that the address zero request is not supported.
### Output state changed $badge:since:v0.2$ {#tdiyp-output-state-change}
Sent by Traintastic to change the state of an output, the DIY device responds with a *get output state* message containing the new output state,
if for some reason the output state cannot be the current state must be send.
Sent by the DIY device as response to the *[get output state](#tdiyp-get-output-state)* message and must be sent by the DIY device whenever an output state changes.
#### Message
```
0x23 <AH> <AL> <S> <checksum>
```
- `<AH>` high byte of 16bit output address
- `<AL>` low byte of 16bit output address
- `<S>` output state:
- `0x00` if output state is unknown
- `0x01` if output state is low/false
- `0x02` if output state is high/true
- `0x03` if output is invalid (only as response to a *[get output state](#tdiyp-get-output-state)* message)
- `0x04`...`0xFF` are reserved, do not use

Datei anzeigen

@ -126,6 +126,10 @@
"type": "appendix",
"markdown": "xpressnet.md"
},
{
"type": "appendix",
"markdown": "traintasticdiyprotocol.md"
},
{
"type": "appendix",
"markdown": "messages.md",

Datei anzeigen

@ -119,6 +119,10 @@ file(GLOB SOURCES
"src/hardware/protocol/loconet/*.cpp"
"src/hardware/protocol/loconet/iohandler/*.hpp"
"src/hardware/protocol/loconet/iohandler/*.cpp"
"src/hardware/protocol/traintasticdiy/*.hpp"
"src/hardware/protocol/traintasticdiy/*.cpp"
"src/hardware/protocol/traintasticdiy/iohandler/*.hpp"
"src/hardware/protocol/traintasticdiy/iohandler/*.cpp"
"src/hardware/protocol/xpressnet/*.hpp"
"src/hardware/protocol/xpressnet/*.cpp"
"src/hardware/protocol/xpressnet/iohandler/*.hpp"

Datei anzeigen

@ -0,0 +1,34 @@
/**
* server/src/enum/traintasticdiyinterfacetype.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021 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_ENUM_TRAINTASTICDIYINTERFACETYPE_HPP
#define TRAINTASTIC_SERVER_ENUM_TRAINTASTICDIYINTERFACETYPE_HPP
#include <traintastic/enum/traintasticdiyinterfacetype.hpp>
#include <array>
inline constexpr std::array<TraintasticDIYInterfaceType, 2> traintasticDIYInterfaceTypeValues{{
TraintasticDIYInterfaceType::Serial,
TraintasticDIYInterfaceType::NetworkTCP,
}};
#endif

Datei anzeigen

@ -30,6 +30,7 @@ std::shared_ptr<Interface> Interfaces::create(World& world, std::string_view cla
IF_CLASSID_CREATE(ECoSInterface)
IF_CLASSID_CREATE(HSI88Interface)
IF_CLASSID_CREATE(LocoNetInterface)
IF_CLASSID_CREATE(TraintasticDIYInterface)
IF_CLASSID_CREATE(WlanMausInterface)
IF_CLASSID_CREATE(XpressNetInterface)
IF_CLASSID_CREATE(Z21Interface)

Datei anzeigen

@ -30,6 +30,7 @@
#include "ecosinterface.hpp"
#include "hsi88.hpp"
#include "loconetinterface.hpp"
#include "traintasticdiyinterface.hpp"
#include "wlanmausinterface.hpp"
#include "xpressnetinterface.hpp"
#include "z21interface.hpp"
@ -43,6 +44,7 @@ struct Interfaces
ECoSInterface::classId,
HSI88Interface::classId,
LocoNetInterface::classId,
TraintasticDIYInterface::classId,
WlanMausInterface::classId,
XpressNetInterface::classId,
Z21Interface::classId

Datei anzeigen

@ -0,0 +1,273 @@
/**
* server/src/hardware/interface/traintasticdiyinterface.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 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 "traintasticdiyinterface.hpp"
#include "../input/list/inputlisttablemodel.hpp"
#include "../output/list/outputlisttablemodel.hpp"
#include "../protocol/traintasticdiy/messages.hpp"
#include "../protocol/traintasticdiy/iohandler/serialiohandler.hpp"
#include "../protocol/traintasticdiy/iohandler/simulationiohandler.hpp"
#include "../protocol/traintasticdiy/iohandler/tcpiohandler.hpp"
#include "../../core/attributes.hpp"
#include "../../log/log.hpp"
#include "../../log/logmessageexception.hpp"
#include "../../utils/displayname.hpp"
#include "../../utils/inrange.hpp"
#include "../../world/world.hpp"
constexpr auto inputListColumns = InputListColumn::Id | InputListColumn::Name | InputListColumn::Address;
constexpr auto outputListColumns = OutputListColumn::Id | OutputListColumn::Name | OutputListColumn::Address;
TraintasticDIYInterface::TraintasticDIYInterface(World& world, std::string_view _id)
: Interface(world, _id)
, type{this, "type", TraintasticDIYInterfaceType::Serial, PropertyFlags::ReadWrite | PropertyFlags::Store,
[this](TraintasticDIYInterfaceType /*value*/)
{
updateVisible();
}}
, device{this, "device", "", PropertyFlags::ReadWrite | PropertyFlags::Store}
, baudrate{this, "baudrate", 19200, PropertyFlags::ReadWrite | PropertyFlags::Store}
, flowControl{this, "flow_control", SerialFlowControl::None, PropertyFlags::ReadWrite | PropertyFlags::Store}
, hostname{this, "hostname", "192.168.1.203", PropertyFlags::ReadWrite | PropertyFlags::Store}
, port{this, "port", 5550, PropertyFlags::ReadWrite | PropertyFlags::Store}
, traintasticDIY{this, "traintastic_diy", nullptr, PropertyFlags::ReadOnly | PropertyFlags::Store | PropertyFlags::SubObject}
, inputs{this, "inputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject}
, outputs{this, "outputs", nullptr, PropertyFlags::ReadOnly | PropertyFlags::NoStore | PropertyFlags::SubObject}
{
name = "Traintastic DIY";
traintasticDIY.setValueInternal(std::make_shared<TraintasticDIY::Settings>(*this, traintasticDIY.name()));
inputs.setValueInternal(std::make_shared<InputList>(*this, inputs.name(), inputListColumns));
outputs.setValueInternal(std::make_shared<OutputList>(*this, outputs.name(), outputListColumns));
Attributes::addDisplayName(type, DisplayName::Interface::type);
Attributes::addEnabled(type, !online);
Attributes::addValues(type, traintasticDIYInterfaceTypeValues);
m_interfaceItems.insertBefore(type, notes);
Attributes::addDisplayName(device, DisplayName::Serial::device);
Attributes::addEnabled(device, !online);
Attributes::addVisible(device, false);
m_interfaceItems.insertBefore(device, notes);
Attributes::addDisplayName(baudrate, DisplayName::Serial::baudrate);
Attributes::addEnabled(baudrate, !online);
Attributes::addVisible(baudrate, false);
m_interfaceItems.insertBefore(baudrate, notes);
Attributes::addDisplayName(flowControl, DisplayName::Serial::flowControl);
Attributes::addEnabled(flowControl, !online);
Attributes::addValues(flowControl, SerialFlowControlValues);
Attributes::addVisible(flowControl, false);
m_interfaceItems.insertBefore(flowControl, notes);
Attributes::addDisplayName(hostname, DisplayName::IP::hostname);
Attributes::addEnabled(hostname, !online);
Attributes::addVisible(hostname, false);
m_interfaceItems.insertBefore(hostname, notes);
Attributes::addDisplayName(port, DisplayName::IP::port);
Attributes::addEnabled(port, !online);
Attributes::addVisible(port, false);
m_interfaceItems.insertBefore(port, notes);
m_interfaceItems.insertBefore(traintasticDIY, notes);
Attributes::addDisplayName(inputs, DisplayName::Hardware::inputs);
m_interfaceItems.insertBefore(inputs, notes);
Attributes::addDisplayName(outputs, DisplayName::Hardware::outputs);
m_interfaceItems.insertBefore(outputs, notes);
updateVisible();
}
bool TraintasticDIYInterface::addInput(Input& input)
{
const bool success = InputController::addInput(input);
if(success)
inputs->addObject(input.shared_ptr<Input>());
return success;
}
bool TraintasticDIYInterface::removeInput(Input& input)
{
const bool success = InputController::removeInput(input);
if(success)
inputs->removeObject(input.shared_ptr<Input>());
return success;
}
void TraintasticDIYInterface::inputSimulateChange(uint32_t channel, uint32_t address)
{
if(m_kernel && inRange(address, outputAddressMinMax(channel)))
m_kernel->simulateInputChange(address);
}
bool TraintasticDIYInterface::addOutput(Output& output)
{
const bool success = OutputController::addOutput(output);
if(success)
outputs->addObject(output.shared_ptr<Output>());
return success;
}
bool TraintasticDIYInterface::removeOutput(Output& output)
{
const bool success = OutputController::removeOutput(output);
if(success)
outputs->removeObject(output.shared_ptr<Output>());
return success;
}
bool TraintasticDIYInterface::setOutputValue(uint32_t channel, uint32_t address, bool value)
{
assert(isOutputChannel(channel));
return
m_kernel &&
inRange(address, outputAddressMinMax(channel)) &&
m_kernel->setOutput(static_cast<uint16_t>(address), value);
}
bool TraintasticDIYInterface::setOnline(bool& value, bool simulation)
{
if(!m_kernel && value)
{
try
{
if(simulation)
{
m_kernel = TraintasticDIY::Kernel::create<TraintasticDIY::SimulationIOHandler>(traintasticDIY->config());
}
else
{
switch(type)
{
case TraintasticDIYInterfaceType::Serial:
m_kernel = TraintasticDIY::Kernel::create<TraintasticDIY::SerialIOHandler>(traintasticDIY->config(), device.value(), baudrate.value(), flowControl.value());
break;
case TraintasticDIYInterfaceType::NetworkTCP:
m_kernel = TraintasticDIY::Kernel::create<TraintasticDIY::TCPIOHandler>(traintasticDIY->config(), hostname.value(), port.value());
break;
}
}
if(!m_kernel)
{
assert(false);
return false;
}
status.setValueInternal(InterfaceStatus::Initializing);
m_kernel->setLogId(id.value());
m_kernel->setOnStarted(
[this]()
{
status.setValueInternal(InterfaceStatus::Online);
});
m_kernel->setInputController(this);
m_kernel->setOutputController(this);
m_kernel->start();
m_traintasticDIYPropertyChanged = traintasticDIY->propertyChanged.connect(
[this](BaseProperty& /*property*/)
{
m_kernel->setConfig(traintasticDIY->config());
});
Attributes::setEnabled({type, device, baudrate, flowControl, hostname, port}, false);
}
catch(const LogMessageException& e)
{
status.setValueInternal(InterfaceStatus::Offline);
Log::log(*this, e.message(), e.args());
return false;
}
}
else if(m_kernel && !value)
{
Attributes::setEnabled({type, device, baudrate, flowControl, hostname, port}, true);
m_traintasticDIYPropertyChanged.disconnect();
m_kernel->stop();
m_kernel.reset();
status.setValueInternal(InterfaceStatus::Offline);
}
return true;
}
void TraintasticDIYInterface::addToWorld()
{
Interface::addToWorld();
m_world.inputControllers->add(std::dynamic_pointer_cast<InputController>(shared_from_this()));
m_world.outputControllers->add(std::dynamic_pointer_cast<OutputController>(shared_from_this()));
}
void TraintasticDIYInterface::loaded()
{
Interface::loaded();
updateVisible();
}
void TraintasticDIYInterface::destroying()
{
for(const auto& input : *inputs)
{
assert(input->interface.value() == std::dynamic_pointer_cast<InputController>(shared_from_this()));
input->interface = nullptr;
}
for(const auto& output : *outputs)
{
assert(output->interface.value() == std::dynamic_pointer_cast<OutputController>(shared_from_this()));
output->interface = nullptr;
}
m_world.inputControllers->remove(std::dynamic_pointer_cast<InputController>(shared_from_this()));
m_world.outputControllers->remove(std::dynamic_pointer_cast<OutputController>(shared_from_this()));
Interface::destroying();
}
void TraintasticDIYInterface::idChanged(const std::string& newId)
{
if(m_kernel)
m_kernel->setLogId(newId);
}
void TraintasticDIYInterface::updateVisible()
{
const bool isSerial = (type == TraintasticDIYInterfaceType::Serial);
Attributes::setVisible(device, isSerial);
Attributes::setVisible(baudrate, isSerial);
Attributes::setVisible(flowControl, isSerial);
const bool isNetwork = (type == TraintasticDIYInterfaceType::NetworkTCP);
Attributes::setVisible(hostname, isNetwork);
Attributes::setVisible(port, isNetwork);
}

Datei anzeigen

@ -0,0 +1,90 @@
/**
* server/src/hardware/interface/traintasticdiyinterface.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 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_INTERFACE_TRAINTASTICDIYINTERFACE_HPP
#define TRAINTASTIC_SERVER_HARDWARE_INTERFACE_TRAINTASTICDIYINTERFACE_HPP
#include "interface.hpp"
#include "../protocol/traintasticdiy/kernel.hpp"
#include "../protocol/traintasticdiy/settings.hpp"
#include "../input/inputcontroller.hpp"
#include "../input/list/inputlist.hpp"
#include "../output/outputcontroller.hpp"
#include "../output/list/outputlist.hpp"
#include "../../core/objectproperty.hpp"
#include "../../enum/traintasticdiyinterfacetype.hpp"
#include "../../enum/serialflowcontrol.hpp"
/**
* \brief Traintastic DIY hardware interface
*/
class TraintasticDIYInterface final
: public Interface
, public InputController
, public OutputController
{
CLASS_ID("interface.traintastic_diy")
DEFAULT_ID("traintastic_diy")
CREATE(TraintasticDIYInterface)
private:
std::unique_ptr<TraintasticDIY::Kernel> m_kernel;
boost::signals2::connection m_traintasticDIYPropertyChanged;
void addToWorld() final;
void loaded() final;
void destroying() final;
void idChanged(const std::string& newId) final;
void updateVisible();
protected:
bool setOnline(bool& value, bool simulation) final;
public:
Property<TraintasticDIYInterfaceType> type;
Property<std::string> device;
Property<uint32_t> baudrate;
Property<SerialFlowControl> flowControl;
Property<std::string> hostname;
Property<uint16_t> port;
ObjectProperty<TraintasticDIY::Settings> traintasticDIY;
ObjectProperty<InputList> inputs;
ObjectProperty<OutputList> outputs;
TraintasticDIYInterface(World& world, std::string_view _id);
// InputController:
std::pair<uint32_t, uint32_t> inputAddressMinMax(uint32_t /*channel*/) const final { return {TraintasticDIY::Kernel::ioAddressMin, TraintasticDIY::Kernel::ioAddressMax}; }
[[nodiscard]] bool addInput(Input& input) final;
[[nodiscard]] bool removeInput(Input& input) final;
void inputSimulateChange(uint32_t channel, uint32_t address) final;
// OutputController:
std::pair<uint32_t, uint32_t> outputAddressMinMax(uint32_t /*channel*/) const final { return {TraintasticDIY::Kernel::ioAddressMin, TraintasticDIY::Kernel::ioAddressMax}; }
[[nodiscard]] bool addOutput(Output& output) final;
[[nodiscard]] bool removeOutput(Output& output) final;
[[nodiscard]] bool setOutputValue(uint32_t channel, uint32_t address, bool value) final;
};
#endif

Datei anzeigen

@ -0,0 +1,40 @@
/**
* server/src/hardware/protocol/traintasticdiy/config.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 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_TRAINTASTICDIY_CONFIG_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_TRAINTASTICDIY_CONFIG_HPP
#include <chrono>
namespace TraintasticDIY {
struct Config
{
std::chrono::milliseconds heartbeatTimeout;
bool debugLogRXTX;
bool debugLogHeartbeat;
};
}
#endif

Datei anzeigen

@ -0,0 +1,84 @@
/**
* server/src/hardware/protocol/traintasticdiy/featureflags.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 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_TRAINTASTICDIY_FEATUREFLAGS_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_TRAINTASTICDIY_FEATUREFLAGS_HPP
#include <cstdint>
namespace TraintasticDIY {
enum class FeatureFlags1 : uint8_t
{
None = 0x00,
Input = 0x01,
Output = 0x02,
};
enum class FeatureFlags2 : uint8_t
{
None = 0x00,
};
enum class FeatureFlags3 : uint8_t
{
None = 0x00,
};
enum class FeatureFlags4 : uint8_t
{
None = 0x00,
};
template<class T>
constexpr bool isFeatureFlagType()
{
return
std::is_same_v<T, FeatureFlags1> ||
std::is_same_v<T, FeatureFlags2> ||
std::is_same_v<T, FeatureFlags3> ||
std::is_same_v<T, FeatureFlags4>;
}
}
template<class T, std::enable_if_t<TraintasticDIY::isFeatureFlagType<T>()>* = nullptr>
constexpr T operator&(const T& lhs, const T& rhs)
{
using UT = std::underlying_type_t<T>;
return static_cast<T>(static_cast<UT>(lhs) & static_cast<UT>(rhs));
}
template<class T, std::enable_if_t<TraintasticDIY::isFeatureFlagType<T>()>* = nullptr>
constexpr T operator|(const T& lhs, const T& rhs)
{
using UT = std::underlying_type_t<T>;
return static_cast<T>(static_cast<UT>(lhs) | static_cast<UT>(rhs));
}
template<class T, std::enable_if_t<TraintasticDIY::isFeatureFlagType<T>()>* = nullptr>
constexpr bool contains(T set, T value)
{
return (set & value) == value;
}
#endif

Datei anzeigen

@ -0,0 +1,62 @@
/**
* server/src/hardware/protocol/traintasticdiy/inputstate.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 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_TRAINTASTICDIY_INPUTSTATE_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_TRAINTASTICDIY_INPUTSTATE_HPP
#include <cstdint>
#include <string_view>
namespace TraintasticDIY {
enum class InputState : uint8_t
{
Undefined = 0,
False = 1,
True = 2,
Invalid = 3,
};
}
constexpr std::string_view toString(TraintasticDIY::InputState value)
{
using InputState = TraintasticDIY::InputState;
switch(value)
{
case InputState::Undefined:
return "Undefined";
case InputState::False:
return "False";
case InputState::True:
return "True";
case InputState::Invalid:
return "Invalid";
}
return {};
}
#endif

Datei anzeigen

@ -0,0 +1,100 @@
/**
* server/src/hardware/protocol/traintasticdiy/iohandler/hardwareiohandler.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 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 "hardwareiohandler.hpp"
#include "../kernel.hpp"
#include "../messages.hpp"
#include "../../../../core/eventloop.hpp"
#include "../../../../log/log.hpp"
namespace TraintasticDIY {
HardwareIOHandler::HardwareIOHandler(Kernel& kernel)
: IOHandler{kernel}
, m_readBufferOffset{0}
, m_writeBufferOffset{0}
{
}
bool HardwareIOHandler::send(const Message& message)
{
if(m_writeBufferOffset + message.size() > m_writeBuffer.size())
return false;
const bool wasEmpty = m_writeBufferOffset == 0;
memcpy(m_writeBuffer.data() + m_writeBufferOffset, &message, message.size());
m_writeBufferOffset += message.size();
if(wasEmpty)
write();
return true;
}
void HardwareIOHandler::processRead(size_t bytesTransferred)
{
const std::byte* pos = m_readBuffer.data();
bytesTransferred += m_readBufferOffset;
while(bytesTransferred > 1)
{
const Message* message = nullptr;
size_t drop = 0;
while(drop < bytesTransferred)
{
message = reinterpret_cast<const Message*>(pos);
if(message->size() <= bytesTransferred && isChecksumValid(*message))
break;
drop++;
pos++;
bytesTransferred--;
}
if(drop != 0)
{
EventLoop::call(
[this, drop]()
{
Log::log(m_kernel.logId(), LogMessage::W2001_RECEIVED_MALFORMED_DATA_DROPPED_X_BYTES, drop);
});
}
assert(message);
if(message->size() <= bytesTransferred)
{
m_kernel.receive(*message);
pos += message->size();
bytesTransferred -= message->size();
}
else
break;
}
if(bytesTransferred != 0)
memmove(m_readBuffer.data(), pos, bytesTransferred);
m_readBufferOffset = bytesTransferred;
}
}

Datei anzeigen

@ -0,0 +1,54 @@
/**
* server/src/hardware/protocol/traintasticdiy/iohandler/hardwareiohandler.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 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_TRAINTASTICDIY_IOHANDLER_HARDWAREIOHANDLER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_TRAINTASTICDIY_IOHANDLER_HARDWAREIOHANDLER_HPP
#include <cstddef>
#include <array>
#include "iohandler.hpp"
namespace TraintasticDIY {
class Kernel;
struct Message;
class HardwareIOHandler : public IOHandler
{
protected:
std::array<std::byte, 1500> m_readBuffer;
size_t m_readBufferOffset;
std::array<std::byte, 1500> m_writeBuffer;
size_t m_writeBufferOffset;
HardwareIOHandler(Kernel& kernel);
void processRead(size_t bytesTransferred);
virtual void write() = 0;
public:
bool send(const Message& message) final;
};
}
#endif

Datei anzeigen

@ -0,0 +1,61 @@
/**
* server/src/hardware/protocol/traintasticdiy/iohandler/iohandler.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 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_TRAINTASTICDIY_IOHANDLER_IOHANDLER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_TRAINTASTICDIY_IOHANDLER_IOHANDLER_HPP
namespace TraintasticDIY {
class Kernel;
struct Message;
class IOHandler
{
protected:
Kernel& m_kernel;
IOHandler(Kernel& kernel)
: m_kernel{kernel}
{
}
public:
IOHandler(const IOHandler&) = delete;
IOHandler& operator =(const IOHandler&) = delete;
virtual ~IOHandler() = default;
virtual void start() = 0;
virtual void stop() = 0;
virtual bool send(const Message& message) = 0;
};
template<class T>
constexpr bool isSimulation()
{
return false;
}
}
#endif

Datei anzeigen

@ -0,0 +1,105 @@
/**
* server/src/hardware/protocol/traintasticdiy/iohandler/serialiohandler.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 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 "serialiohandler.hpp"
#include "../kernel.hpp"
#include "../messages.hpp"
#include "../../../../core/eventloop.hpp"
#include "../../../../log/log.hpp"
#include "../../../../utils/serialport.hpp"
namespace TraintasticDIY {
SerialIOHandler::SerialIOHandler(Kernel& kernel, const std::string& device, uint32_t baudrate, SerialFlowControl flowControl)
: HardwareIOHandler(kernel)
, m_serialPort{m_kernel.ioContext()}
{
SerialPort::open(m_serialPort, device, baudrate, 8, SerialParity::None, SerialStopBits::One, flowControl);
}
SerialIOHandler::~SerialIOHandler()
{
if(m_serialPort.is_open())
m_serialPort.close();
}
void SerialIOHandler::start()
{
read();
}
void SerialIOHandler::stop()
{
m_serialPort.close();
}
void SerialIOHandler::read()
{
m_serialPort.async_read_some(boost::asio::buffer(m_readBuffer.data() + m_readBufferOffset, m_readBuffer.size() - m_readBufferOffset),
[this](const boost::system::error_code& ec, std::size_t bytesTransferred)
{
if(!ec)
{
processRead(bytesTransferred);
read();
}
else if(ec != boost::asio::error::operation_aborted)
{
EventLoop::call(
[this, ec]()
{
Log::log(m_kernel.logId(), LogMessage::E2002_SERIAL_READ_FAILED_X, ec);
// TODO interface status -> error
});
}
});
}
void SerialIOHandler::write()
{
m_serialPort.async_write_some(boost::asio::buffer(m_writeBuffer.data(), m_writeBufferOffset),
[this](const boost::system::error_code& ec, std::size_t bytesTransferred)
{
if(!ec)
{
if(bytesTransferred < m_writeBufferOffset)
{
m_writeBufferOffset -= bytesTransferred;
memmove(m_writeBuffer.data(), m_writeBuffer.data() + bytesTransferred, m_writeBufferOffset);
write();
}
else
m_writeBufferOffset = 0;
}
else if(ec != boost::asio::error::operation_aborted)
{
EventLoop::call(
[this, ec]()
{
Log::log(m_kernel.logId(), LogMessage::E2001_SERIAL_WRITE_FAILED_X, ec);
// TODO interface status -> error
});
}
});
}
}

Datei anzeigen

@ -0,0 +1,50 @@
/**
* server/src/hardware/protocol/traintasticdiy/iohandler/serialiohandler.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 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_TRAINTASTICDIY_IOHANDLER_SERIALIOHANDLER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_TRAINTASTICDIY_IOHANDLER_SERIALIOHANDLER_HPP
#include "hardwareiohandler.hpp"
#include <boost/asio/serial_port.hpp>
#include "../../../../enum/serialflowcontrol.hpp"
namespace TraintasticDIY {
class SerialIOHandler final : public HardwareIOHandler
{
private:
boost::asio::serial_port m_serialPort;
void read();
void write() final;
public:
SerialIOHandler(Kernel& kernel, const std::string& device, uint32_t baudrate, SerialFlowControl flowControl);
~SerialIOHandler() final;
void start() final;
void stop() final;
};
}
#endif

Datei anzeigen

@ -0,0 +1,110 @@
/**
* server/src/hardware/protocol/traintasticdiy/iohandler/simulationiohandler.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 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 "simulationiohandler.hpp"
#include "../kernel.hpp"
#include "../messages.hpp"
#include <version.hpp>
namespace TraintasticDIY {
static std::shared_ptr<std::byte[]> copy(const Message& message)
{
auto* bytes = new std::byte[message.size()];
std::memcpy(bytes, &message, message.size());
return std::shared_ptr<std::byte[]>{bytes};
}
SimulationIOHandler::SimulationIOHandler(Kernel& kernel)
: IOHandler(kernel)
{
}
bool SimulationIOHandler::send(const Message& message)
{
switch(message.opCode)
{
case OpCode::Heartbeat:
reply(Heartbeat());
break;
case OpCode::GetInputState:
{
const auto& getInputState = static_cast<const GetInputState&>(message);
reply(SetInputState(getInputState.address(), InputState::Invalid));
break;
}
case OpCode::GetOutputState:
{
const auto& getOutputState = static_cast<const GetOutputState&>(message);
reply(SetOutputState(getOutputState.address(), OutputState::Invalid));
break;
}
case OpCode::SetOutputState:
{
#ifndef NDEBUG
const auto& setOutputState = static_cast<const SetOutputState&>(message);
assert(setOutputState.state == OutputState::False || setOutputState.state == OutputState::True);
#endif
reply(message);
break;
}
case OpCode::GetFeatures:
{
reply(Features(FeatureFlags1::Input | FeatureFlags1::Output));
break;
}
case OpCode::GetInfo:
{
constexpr std::string_view text{"Traintastic DIY simulator v" TRAINTASTIC_VERSION};
static_assert(text.size() <= 255);
auto info = std::make_unique<std::byte[]>(sizeof(InfoBase) + text.size() + sizeof(Checksum));
auto& infoBase = *reinterpret_cast<InfoBase*>(info.get());
infoBase.opCode = OpCode::Info;
infoBase.length = text.length();
std::memcpy(info.get() + sizeof(InfoBase), text.data(), text.size());
updateChecksum(infoBase);
reply(infoBase);
break;
}
case OpCode::SetInputState:
case OpCode::Features:
case OpCode::Info:
assert(false); // only send by device
break;
}
return true;
}
void SimulationIOHandler::reply(const Message& message)
{
// post the reply, so it has some delay
//! \todo better delay simulation? at least message transfer time?
m_kernel.ioContext().post(
[this, data=copy(message)]()
{
m_kernel.receive(*reinterpret_cast<const Message*>(data.get()));
});
}
}

Datei anzeigen

@ -0,0 +1,55 @@
/**
* server/src/hardware/protocol/traintasticdiy/iohandler/simulationiohandler.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 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_TRAINTASTICDIY_IOHANDLER_SIMULATIONIOHANDLER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_TRAINTASTICDIY_IOHANDLER_SIMULATIONIOHANDLER_HPP
#include "iohandler.hpp"
#include <array>
#include <cstddef>
namespace TraintasticDIY {
class SimulationIOHandler final : public IOHandler
{
private:
void reply(const Message& message);
public:
SimulationIOHandler(Kernel& kernel);
void start() final {}
void stop() final {}
bool send(const Message& message) final;
};
template<>
constexpr bool isSimulation<SimulationIOHandler>()
{
return true;
}
}
#endif

Datei anzeigen

@ -0,0 +1,114 @@
/**
* server/src/hardware/protocol/traintasticdiy/iohandler/tcpiohandler.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 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 "tcpiohandler.hpp"
#include <boost/asio/write.hpp>
#include "../kernel.hpp"
#include "../messages.hpp"
#include "../../../../core/eventloop.hpp"
#include "../../../../log/log.hpp"
#include "../../../../log/logmessageexception.hpp"
namespace TraintasticDIY {
TCPIOHandler::TCPIOHandler(Kernel& kernel, const std::string& hostname, uint16_t port)
: HardwareIOHandler(kernel)
, m_socket{m_kernel.ioContext()}
{
boost::system::error_code ec;
m_endpoint.port(port);
m_endpoint.address(boost::asio::ip::make_address(hostname, ec));
if(ec)
throw LogMessageException(LogMessage::E2003_MAKE_ADDRESS_FAILED_X, ec);
m_socket.connect(m_endpoint, ec);
if(ec)
throw LogMessageException(LogMessage::E2005_SOCKET_CONNECT_FAILED_X, ec);
m_socket.set_option(boost::asio::socket_base::linger(true, 0));
m_socket.set_option(boost::asio::ip::tcp::no_delay(true));
}
TCPIOHandler::~TCPIOHandler()
{
}
void TCPIOHandler::start()
{
read();
}
void TCPIOHandler::stop()
{
}
void TCPIOHandler::read()
{
m_socket.async_read_some(boost::asio::buffer(m_readBuffer.data() + m_readBufferOffset, m_readBuffer.size() - m_readBufferOffset),
[this](const boost::system::error_code& ec, std::size_t bytesTransferred)
{
if(!ec)
{
processRead(bytesTransferred);
read();
}
else if(ec != boost::asio::error::operation_aborted)
{
EventLoop::call(
[this, ec]()
{
Log::log(m_kernel.logId(), LogMessage::E1007_SOCKET_READ_FAILED_X, ec);
// TODO interface status -> error
});
}
});
}
void TCPIOHandler::write()
{
m_socket.async_write_some(boost::asio::buffer(m_writeBuffer.data(), m_writeBufferOffset),
[this](const boost::system::error_code& ec, std::size_t bytesTransferred)
{
if(!ec)
{
m_writeBufferOffset -= bytesTransferred;
if(m_writeBufferOffset > 0)
{
memmove(m_writeBuffer.data(), m_writeBuffer.data() + bytesTransferred, m_writeBufferOffset);
write();
}
}
else if(ec != boost::asio::error::operation_aborted)
{
EventLoop::call(
[this, ec]()
{
Log::log(m_kernel.logId(), LogMessage::E1006_SOCKET_WRITE_FAILED_X, ec);
// TODO interface status -> error
});
}
});
}
}

Datei anzeigen

@ -0,0 +1,50 @@
/**
* server/src/hardware/protocol/traintasticdiy/iohandler/tcpiohandler.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 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_TRAINTASTICDIY_IOHANDLER_TCPIOHANDLER_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_TRAINTASTICDIY_IOHANDLER_TCPIOHANDLER_HPP
#include "hardwareiohandler.hpp"
#include <boost/asio/ip/tcp.hpp>
namespace TraintasticDIY {
class TCPIOHandler final : public HardwareIOHandler
{
private:
boost::asio::ip::tcp::socket m_socket;
boost::asio::ip::tcp::endpoint m_endpoint;
void read();
void write() final;
public:
TCPIOHandler(Kernel& kernel, const std::string& hostname, uint16_t port);
~TCPIOHandler() final;
void start() final;
void stop() final;
};
}
#endif

Datei anzeigen

@ -0,0 +1,325 @@
/**
* server/src/hardware/protocol/traintasticdiy/kernel.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 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 "kernel.hpp"
#include "messages.hpp"
#include "../../decoder/decoder.hpp"
#include "../../decoder/decoderchangeflags.hpp"
#include "../../input/inputcontroller.hpp"
#include "../../output/outputcontroller.hpp"
#include "../../../utils/inrange.hpp"
#include "../../../utils/setthreadname.hpp"
#include "../../../core/eventloop.hpp"
#include "../../../log/log.hpp"
namespace TraintasticDIY {
constexpr TriState toTriState(InputState value)
{
switch(value)
{
case InputState::False:
return TriState::False;
case InputState::True:
return TriState::True;
case InputState::Undefined:
case InputState::Invalid:
break;
}
return TriState::Undefined;
}
constexpr TriState toTriState(OutputState value)
{
switch(value)
{
case OutputState::False:
return TriState::False;
case OutputState::True:
return TriState::True;
case OutputState::Undefined:
case OutputState::Invalid:
break;
}
return TriState::Undefined;
}
Kernel::Kernel(const Config& config, bool simulation)
: m_ioContext{1}
, m_simulation{simulation}
, m_heartbeatTimeout{m_ioContext}
, m_inputController{nullptr}
, m_outputController{nullptr}
, m_config{config}
#ifndef NDEBUG
, m_started{false}
#endif
{
}
void Kernel::setConfig(const Config& config)
{
m_ioContext.post(
[this, newConfig=config]()
{
m_config = newConfig;
});
}
void Kernel::start()
{
assert(m_ioHandler);
assert(!m_started);
m_featureFlagsSet = false;
m_featureFlags1 = FeatureFlags1::None;
m_featureFlags2 = FeatureFlags2::None;
m_featureFlags3 = FeatureFlags3::None;
m_featureFlags4 = FeatureFlags4::None;
m_thread = std::thread(
[this]()
{
setThreadName("traintasticdiy");
auto work = std::make_shared<boost::asio::io_context::work>(m_ioContext);
m_ioContext.run();
});
m_ioContext.post(
[this]()
{
m_ioHandler->start();
send(GetInfo());
send(GetFeatures());
restartHeartbeatTimeout();
if(m_onStarted)
EventLoop::call(
[this]()
{
m_onStarted();
});
});
#ifndef NDEBUG
m_started = true;
#endif
}
void Kernel::stop()
{
m_ioContext.post(
[this]()
{
m_heartbeatTimeout.cancel();
m_ioHandler->stop();
});
m_ioContext.stop();
m_thread.join();
#ifndef NDEBUG
m_started = false;
#endif
}
void Kernel::receive(const Message& message)
{
if(m_config.debugLogRXTX && (message != Heartbeat() || m_config.debugLogHeartbeat))
EventLoop::call(
[this, msg=toString(message)]()
{
Log::log(m_logId, LogMessage::D2002_RX_X, msg);
});
restartHeartbeatTimeout();
switch(message.opCode)
{
case OpCode::Heartbeat:
break;
case OpCode::SetInputState:
{
if(!m_featureFlagsSet || !hasFeatureInput())
break;
const auto& setInputState = static_cast<const SetInputState&>(message);
const uint16_t address = setInputState.address();
if(inRange(address, ioAddressMin, ioAddressMax))
{
auto it = m_inputValues.find(address);
if(it == m_inputValues.end() || it->second != setInputState.state)
{
m_inputValues[address] = setInputState.state;
EventLoop::call(
[this, address, state=setInputState.state]()
{
if(state == InputState::Invalid)
{
if(m_inputController->inputs().count({InputController::defaultInputChannel, address}) != 0)
Log::log(m_logId, LogMessage::W2004_INPUT_ADDRESS_X_IS_INVALID, address);
}
else
m_inputController->updateInputValue(InputController::defaultInputChannel, address, toTriState(state));
});
}
}
break;
}
case OpCode::SetOutputState:
{
if(!m_featureFlagsSet || !hasFeatureOutput())
break;
const auto& setOutputState = static_cast<const SetOutputState&>(message);
const uint16_t address = setOutputState.address();
if(inRange(address, ioAddressMin, ioAddressMax))
{
auto it = m_outputValues.find(address);
if(it == m_outputValues.end() || it->second != setOutputState.state)
{
m_outputValues[address] = setOutputState.state;
EventLoop::call(
[this, address, state=setOutputState.state]()
{
if(state == OutputState::Invalid)
{
if(m_outputController->outputs().count({OutputController::defaultOutputChannel, address}) != 0)
Log::log(m_logId, LogMessage::W2005_OUTPUT_ADDRESS_X_IS_INVALID, address);
}
else
m_outputController->updateOutputValue(OutputController::defaultOutputChannel, address, toTriState(state));
});
}
}
break;
}
case OpCode::Features:
{
const auto& features = static_cast<const Features&>(message);
m_featureFlagsSet = true;
m_featureFlags1 = features.featureFlags1;
m_featureFlags2 = features.featureFlags2;
m_featureFlags3 = features.featureFlags3;
m_featureFlags4 = features.featureFlags4;
if(hasFeatureInput())
EventLoop::call(
[this]()
{
for(const auto& it : m_inputController->inputs())
postSend(GetInputState(static_cast<uint16_t>(it.first.address)));
});
if(hasFeatureOutput())
EventLoop::call(
[this]()
{
for(const auto& it : m_outputController->outputs())
postSend(GetOutputState(static_cast<uint16_t>(it.first.address)));
});
break;
}
case OpCode::Info:
{
const auto& info = static_cast<const InfoBase&>(message);
EventLoop::call(
[this, text=std::string(info.text())]()
{
Log::log(m_logId, LogMessage::I2005_X, text);
});
break;
}
case OpCode::GetInfo:
case OpCode::GetFeatures:
case OpCode::GetOutputState:
case OpCode::GetInputState:
assert(false);
break;
}
}
bool Kernel::setOutput(uint16_t address, bool value)
{
postSend(SetOutputState(address, value ? OutputState::True : OutputState::False));
return true;
}
void Kernel::simulateInputChange(uint16_t address)
{
if(m_simulation)
m_ioContext.post(
[this, address]()
{
auto it = m_inputValues.find(address);
receive(SetInputState(address, (it == m_inputValues.end() && it->second == InputState::True) ? InputState::False : InputState::True));
});
}
void Kernel::setIOHandler(std::unique_ptr<IOHandler> handler)
{
assert(handler);
assert(!m_ioHandler);
m_ioHandler = std::move(handler);
}
void Kernel::send(const Message& message)
{
if(m_ioHandler->send(message))
{
if(m_config.debugLogRXTX && (message != Heartbeat() || m_config.debugLogHeartbeat))
EventLoop::call(
[this, msg=toString(message)]()
{
Log::log(m_logId, LogMessage::D2001_TX_X, msg);
});
}
else
{} // log message and go to error state
}
void Kernel::restartHeartbeatTimeout()
{
m_heartbeatTimeout.expires_after(m_config.heartbeatTimeout);
m_heartbeatTimeout.async_wait(std::bind(&Kernel::heartbeatTimeoutExpired, this, std::placeholders::_1));
}
void Kernel::heartbeatTimeoutExpired(const boost::system::error_code& ec)
{
if(ec)
return;
m_heartbeatTimeout.cancel();
send(Heartbeat());
restartHeartbeatTimeout();
}
}

Datei anzeigen

@ -0,0 +1,233 @@
/**
* server/src/hardware/protocol/traintasticdiy/kernel.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 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_TRAINTASTICDIY_KERNEL_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_TRAINTASTICDIY_KERNEL_HPP
#include <unordered_map>
#include <thread>
#include <boost/asio/io_context.hpp>
#include <boost/asio/steady_timer.hpp>
#include <traintastic/enum/tristate.hpp>
#include "config.hpp"
#include "featureflags.hpp"
#include "inputstate.hpp"
#include "outputstate.hpp"
#include "iohandler/iohandler.hpp"
class InputController;
class OutputController;
namespace TraintasticDIY {
struct Message;
class Kernel
{
private:
boost::asio::io_context m_ioContext;
std::unique_ptr<IOHandler> m_ioHandler;
const bool m_simulation;
std::thread m_thread;
std::string m_logId;
boost::asio::steady_timer m_heartbeatTimeout;
std::function<void()> m_onStarted;
bool m_featureFlagsSet;
FeatureFlags1 m_featureFlags1;
FeatureFlags2 m_featureFlags2;
FeatureFlags3 m_featureFlags3;
FeatureFlags4 m_featureFlags4;
InputController* m_inputController;
std::unordered_map<uint16_t, InputState> m_inputValues;
OutputController* m_outputController;
std::unordered_map<uint16_t, OutputState> m_outputValues;
Config m_config;
#ifndef NDEBUG
bool m_started;
#endif
Kernel(const Config& config, bool simulation);
void setIOHandler(std::unique_ptr<IOHandler> handler);
template<class T>
void postSend(const T& message)
{
m_ioContext.post(
[this, message]()
{
send(message);
});
}
void send(const Message& message);
inline bool hasFeatureInput() const { return contains(m_featureFlags1, FeatureFlags1::Input); }
inline bool hasFeatureOutput() const { return contains(m_featureFlags1, FeatureFlags1::Output); }
void restartHeartbeatTimeout();
void heartbeatTimeoutExpired(const boost::system::error_code& ec);
public:
static constexpr uint16_t ioAddressMin = 1;
static constexpr uint16_t ioAddressMax = std::numeric_limits<uint16_t>::max();
Kernel(const Kernel&) = delete;
Kernel& operator =(const Kernel&) = delete;
/**
* \brief IO context for TraintasticDIY kernel and IO handler
*
* \return The IO context
*/
boost::asio::io_context& ioContext() { return m_ioContext; }
/**
* \brief Create kernel and IO handler
*
* \param[in] config TraintasticDIY configuration
* \param[in] args IO handler arguments
* \return The kernel instance
*/
template<class IOHandlerType, class... Args>
static std::unique_ptr<Kernel> create(const Config& config, Args... args)
{
static_assert(std::is_base_of_v<IOHandler, IOHandlerType>);
std::unique_ptr<Kernel> kernel{new Kernel(config, isSimulation<IOHandlerType>())};
kernel->setIOHandler(std::make_unique<IOHandlerType>(*kernel, std::forward<Args>(args)...));
return kernel;
}
/**
* \brief Access the IO handler
*
* \return The IO handler
* \note The IO handler runs in the kernel's IO context, not all functions can be called safely!
*/
template<class T>
T& ioHandler()
{
assert(dynamic_cast<T*>(m_ioHandler.get()));
return static_cast<T&>(*m_ioHandler);
}
/**
*
*
*/
inline const std::string& logId() { return m_logId; }
/**
* \brief Set object id used for log messages
*
* \param[in] value The object id
*/
inline void setLogId(std::string value)
{
m_logId = std::move(value);
}
/**
* \brief Set TraintasticDIY configuration
*
* \param[in] config The TraintasticDIY configuration
*/
void setConfig(const Config& config);
/**
* \brief ...
*
* \param[in] callback ...
* \note This function may not be called when the kernel is running.
*/
inline void setOnStarted(std::function<void()> callback)
{
assert(!m_started);
m_onStarted = std::move(callback);
}
/**
* \brief Set the input controller
*
* \param[in] inputController The input controller
* \note This function may not be called when the kernel is running.
*/
inline void setInputController(InputController* inputController)
{
assert(!m_started);
m_inputController = inputController;
}
/**
* \brief Set the output controller
*
* \param[in] outputController The output controller
* \note This function may not be called when the kernel is running.
*/
inline void setOutputController(OutputController* outputController)
{
assert(!m_started);
m_outputController = outputController;
}
/**
* \brief Start the kernel and IO handler
*/
void start();
/**
* \brief Stop the kernel and IO handler
*/
void stop();
/**
* \brief ...
*
* This must be called by the IO handler whenever a TraintasticDIY message is received.
*
* \param[in] message The received TraintasticDIY message
* \note This function must run in the kernel's IO context
*/
void receive(const Message& message);
/**
*
* \param[in] address Output address, #ioAddressMin..#ioAddressMax
* \param[in] value Output value: \c true is on, \c false is off.
* \return \c true if send successful, \c false otherwise.
*/
bool setOutput(uint16_t address, bool value);
/**
* \brief Simulate input change
* \param[in] address Input address, #ioAddressMin..#ioAddressMax
*/
void simulateInputChange(uint16_t address);
};
}
#endif

Datei anzeigen

@ -0,0 +1,88 @@
#include "messages.hpp"
#include <cassert>
#include "../../../utils/tohex.hpp"
namespace TraintasticDIY {
Checksum calcChecksum(const Message& message)
{
const uint8_t* p = reinterpret_cast<const uint8_t*>(&message);
const size_t dataSize = message.dataSize();
uint8_t checksum = p[0];
for(size_t i = 1; i <= dataSize; i++)
checksum ^= p[i];
return static_cast<Checksum>(checksum);
}
void updateChecksum(Message& message)
{
*(reinterpret_cast<Checksum*>(&message) + message.dataSize() + 1) = calcChecksum(message);
}
bool isChecksumValid(const Message& message)
{
return calcChecksum(message) == *(reinterpret_cast<const Checksum*>(&message) + message.dataSize() + 1);
}
std::string toString(const Message& message)
{
std::string s{::toString(message.opCode)};
switch(message.opCode)
{
case OpCode::Heartbeat:
case OpCode::GetInfo:
case OpCode::GetFeatures:
assert(message.dataSize() == 0);
break;
case OpCode::GetInputState:
{
const auto& getInputState = static_cast<const GetInputState&>(message);
s.append(" address=").append(std::to_string(getInputState.address()));
break;
}
case OpCode::SetInputState:
{
const auto& setInputState = static_cast<const SetInputState&>(message);
s.append(" address=").append(std::to_string(setInputState.address()));
s.append(" state=").append(::toString(setInputState.state));
break;
}
case OpCode::GetOutputState:
{
const auto& getOutputState = static_cast<const GetOutputState&>(message);
s.append(" address=").append(std::to_string(getOutputState.address()));
break;
}
case OpCode::SetOutputState:
{
const auto& setOutputState = static_cast<const SetOutputState&>(message);
s.append(" address=").append(std::to_string(setOutputState.address()));
s.append(" state=").append(::toString(setOutputState.state));
break;
}
case OpCode::Features:
{
break;
}
case OpCode::Info:
{
break;
}
}
s.append(" [");
const uint8_t* bytes = reinterpret_cast<const uint8_t*>(&message);
for(size_t i = 0; i < message.size(); i++)
{
if(i != 0)
s.append(" ");
s.append(toHex(bytes[i]));
}
s.append("]");
return s;
}
}

Datei anzeigen

@ -0,0 +1,233 @@
/**
* server/src/hardware/protocol/traintasticdiy/messages.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 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_TRAINTASTICDIY_MESSAGES_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_TRAINTASTICDIY_MESSAGES_HPP
#include <cstring>
#include <string>
#include "opcode.hpp"
#include "inputstate.hpp"
#include "outputstate.hpp"
#include "featureflags.hpp"
#include "../../../utils/byte.hpp"
namespace TraintasticDIY {
using Checksum = std::byte;
struct Message;
Checksum calcChecksum(const Message& message);
void updateChecksum(Message& message);
bool isChecksumValid(const Message& message);
std::string toString(const Message& message);
struct Message
{
OpCode opCode;
Message(OpCode opCode_)
: opCode{opCode_}
{
}
size_t dataSize() const
{
if(const uint8_t len = (static_cast<uint8_t>(opCode) & 0x0F); len != 0x0F)
return len;
return sizeof(uint8_t) + *(reinterpret_cast<const uint8_t*>(this) + 1);
}
size_t size() const
{
return sizeof(Message) + dataSize() + 1;
}
};
struct Heartbeat : Message
{
Checksum checksum;
Heartbeat()
: Message(OpCode::Heartbeat)
, checksum{calcChecksum(*this)}
{
}
};
static_assert(sizeof(Heartbeat) == 2);
struct GetInputState : Message
{
uint8_t addressHigh;
uint8_t addressLow;
Checksum checksum;
GetInputState(uint16_t address_ = 0)
: Message(OpCode::GetInputState)
, addressHigh{high8(address_)}
, addressLow{low8(address_)}
, checksum{calcChecksum(*this)}
{
}
uint16_t address() const
{
return to16(addressLow, addressHigh);
}
};
static_assert(sizeof(GetInputState) == 4);
struct SetInputState : Message
{
uint8_t addressHigh;
uint8_t addressLow;
InputState state;
Checksum checksum;
SetInputState(uint16_t address_, InputState state_)
: Message(OpCode::SetInputState)
, addressHigh{high8(address_)}
, addressLow{low8(address_)}
, state{state_}
, checksum{calcChecksum(*this)}
{
}
uint16_t address() const
{
return to16(addressLow, addressHigh);
}
};
static_assert(sizeof(SetInputState) == 5);
struct GetOutputState : Message
{
uint8_t addressHigh;
uint8_t addressLow;
Checksum checksum;
GetOutputState(uint16_t address_ = 0)
: Message(OpCode::GetOutputState)
, addressHigh{high8(address_)}
, addressLow{low8(address_)}
, checksum{calcChecksum(*this)}
{
}
uint16_t address() const
{
return to16(addressLow, addressHigh);
}
};
static_assert(sizeof(GetOutputState) == 4);
struct SetOutputState : Message
{
uint8_t addressHigh;
uint8_t addressLow;
OutputState state;
Checksum checksum;
SetOutputState(uint16_t address_, OutputState state_)
: Message(OpCode::SetOutputState)
, addressHigh{high8(address_)}
, addressLow{low8(address_)}
, state{state_}
, checksum{calcChecksum(*this)}
{
}
uint16_t address() const
{
return to16(addressLow, addressHigh);
}
};
static_assert(sizeof(SetOutputState) == 5);
struct GetFeatures : Message
{
Checksum checksum;
GetFeatures()
: Message(OpCode::GetFeatures)
, checksum{calcChecksum(*this)}
{
}
};
static_assert(sizeof(GetFeatures) == 2);
struct Features : Message
{
FeatureFlags1 featureFlags1;
FeatureFlags2 featureFlags2;
FeatureFlags3 featureFlags3;
FeatureFlags4 featureFlags4;
Checksum checksum;
Features(FeatureFlags1 ff1 = FeatureFlags1::None, FeatureFlags2 ff2 = FeatureFlags2::None, FeatureFlags3 ff3 = FeatureFlags3::None, FeatureFlags4 ff4 = FeatureFlags4::None)
: Message(OpCode::Features)
, featureFlags1{ff1}
, featureFlags2{ff2}
, featureFlags3{ff3}
, featureFlags4{ff4}
, checksum{calcChecksum(*this)}
{
}
};
static_assert(sizeof(Features) == 6);
struct GetInfo : Message
{
Checksum checksum;
GetInfo()
: Message(OpCode::GetInfo)
, checksum{calcChecksum(*this)}
{
}
};
static_assert(sizeof(GetInfo) == 2);
struct InfoBase : Message
{
uint8_t length;
std::string_view text() const
{
return {reinterpret_cast<const char*>(this) + sizeof(Message) + sizeof(length), length};
}
};
static_assert(sizeof(InfoBase) == 2);
}
inline bool operator ==(const TraintasticDIY::Message& lhs, const TraintasticDIY::Message& rhs)
{
return lhs.size() == rhs.size() && std::memcmp(&lhs, &rhs, lhs.size()) == 0;
}
inline bool operator !=(const TraintasticDIY::Message& lhs, const TraintasticDIY::Message& rhs)
{
return lhs.size() != rhs.size() || std::memcmp(&lhs, &rhs, lhs.size()) != 0;
}
#endif

Datei anzeigen

@ -0,0 +1,81 @@
/**
* server/src/hardware/protocol/traintasticdiy/opcode.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 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_TRAINTASTICDIY_OPCODE_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_TRAINTASTICDIY_OPCODE_HPP
#include <cstdint>
namespace TraintasticDIY {
enum class OpCode : uint8_t
{
Heartbeat = 0x00,
GetInputState = 0x12,
SetInputState = 0x13,
GetOutputState = 0x22,
SetOutputState = 0x23,
GetFeatures = 0xE0,
Features = 0xE4,
GetInfo = 0xF0,
Info = 0xFF,
};
}
constexpr std::string_view toString(TraintasticDIY::OpCode value)
{
using OpCode = TraintasticDIY::OpCode;
switch(value)
{
case OpCode::Heartbeat:
return "Heartbeat";
case OpCode::GetInputState:
return "GetInputState";
case OpCode::SetInputState:
return "SetInputState";
case OpCode::GetOutputState:
return "GetOutputState";
case OpCode::SetOutputState:
return "SetOutputState";
case OpCode::GetFeatures:
return "GetFeatures";
case OpCode::Features:
return "Features";
case OpCode::GetInfo:
return "GetInfo";
case OpCode::Info:
return "Info";
}
return {};
}
#endif

Datei anzeigen

@ -0,0 +1,62 @@
/**
* server/src/hardware/protocol/traintasticdiy/outputstate.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 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_TRAINTASTICDIY_OUTPUTSTATE_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_TRAINTASTICDIY_OUTPUTSTATE_HPP
#include <cstdint>
#include <string_view>
namespace TraintasticDIY {
enum class OutputState : uint8_t
{
Undefined = 0,
False = 1,
True = 2,
Invalid = 3,
};
}
constexpr std::string_view toString(TraintasticDIY::OutputState value)
{
using OutputState = TraintasticDIY::OutputState;
switch(value)
{
case OutputState::Undefined:
return "Undefined";
case OutputState::False:
return "False";
case OutputState::True:
return "True";
case OutputState::Invalid:
return "Invalid";
}
return {};
}
#endif

Datei anzeigen

@ -0,0 +1,56 @@
/**
* server/src/hardware/protocol/traintasticdiy/settings.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 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 "settings.hpp"
#include "../../../core/attributes.hpp"
#include "../../../utils/displayname.hpp"
namespace TraintasticDIY {
Settings::Settings(Object& _parent, std::string_view parentPropertyName)
: SubObject(_parent, parentPropertyName)
, heartbeatTimeout{this, "heartbeat_timeout", heartbeatTimeoutDefault, PropertyFlags::ReadWrite | PropertyFlags::Store}
, debugLogRXTX{this, "debug_log_rx_tx", false, PropertyFlags::ReadWrite | PropertyFlags::Store}
, debugLogHeartbeat{this, "debug_log_heartbeat", false, PropertyFlags::ReadWrite | PropertyFlags::Store}
{
Attributes::addMinMax(heartbeatTimeout, heartbeatTimeoutMin, heartbeatTimeoutMax);
m_interfaceItems.add(heartbeatTimeout);
Attributes::addDisplayName(debugLogRXTX, DisplayName::Hardware::debugLogRXTX);
m_interfaceItems.add(debugLogRXTX);
m_interfaceItems.add(debugLogHeartbeat);
}
Config Settings::config() const
{
Config config;
config.heartbeatTimeout = std::chrono::milliseconds(heartbeatTimeout);
config.debugLogRXTX = debugLogRXTX;
config.debugLogHeartbeat = debugLogHeartbeat;
return config;
}
}

Datei anzeigen

@ -0,0 +1,53 @@
/**
* server/src/hardware/protocol/traintasticdiy/settings.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 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_TRAINTASTICDIY_SETTINGS_HPP
#define TRAINTASTIC_SERVER_HARDWARE_PROTOCOL_TRAINTASTICDIY_SETTINGS_HPP
#include "../../../core/subobject.hpp"
#include "../../../core/property.hpp"
#include "config.hpp"
namespace TraintasticDIY {
class Settings final : public SubObject
{
CLASS_ID("traintastic_diy_settings")
private:
static constexpr uint16_t heartbeatTimeoutMin = 100;
static constexpr uint16_t heartbeatTimeoutDefault = 1'000;
static constexpr uint16_t heartbeatTimeoutMax = 60'000;
public:
Property<uint16_t> heartbeatTimeout;
Property<bool> debugLogRXTX;
Property<bool> debugLogHeartbeat;
Settings(Object& _parent, std::string_view parentPropertyName);
Config config() const;
};
}
#endif

Datei anzeigen

@ -28,6 +28,7 @@
#include "../src/hardware/interface/ecosinterface.hpp"
#include "../src/hardware/interface/hsi88.hpp"
#include "../src/hardware/interface/loconetinterface.hpp"
#include "../src/hardware/interface/traintasticdiyinterface.hpp"
#include "../src/hardware/interface/wlanmausinterface.hpp"
#include "../src/hardware/interface/xpressnetinterface.hpp"
#include "../src/hardware/interface/z21interface.hpp"
@ -83,6 +84,7 @@ TEMPLATE_TEST_CASE("Create world and interface => destroy world", "[object-creat
, ECoSInterface
, HSI88Interface
, LocoNetInterface
, TraintasticDIYInterface
, WlanMausInterface
, XpressNetInterface
, Z21Interface
@ -106,6 +108,7 @@ TEMPLATE_TEST_CASE("Create world and interface => destroy interface", "[object-c
, ECoSInterface
, HSI88Interface
, LocoNetInterface
, TraintasticDIYInterface
, WlanMausInterface
, XpressNetInterface
, Z21Interface

Datei anzeigen

@ -73,6 +73,7 @@ enum class LogMessage : uint32_t
I2002_HARDWARE_TYPE_X = LogMessageOffset::info + 2002,
I2003_FIRMWARE_VERSION_X = LogMessageOffset::info + 2003,
I2004_HSI_88_X = LogMessageOffset::info + 2004,
I2005_X = LogMessageOffset::info + 2005,
I9001_STOPPED_SCRIPT = LogMessageOffset::info + 9001,
I9999_X = LogMessageOffset::info + 9999,
@ -113,6 +114,8 @@ enum class LogMessage : uint32_t
W2001_RECEIVED_MALFORMED_DATA_DROPPED_X_BYTES = LogMessageOffset::warning + 2001,
W2002_COMMAND_STATION_DOESNT_SUPPORT_FUNCTIONS_ABOVE_FX = LogMessageOffset::warning + 2002,
W2003_COMMAND_STATION_DOESNT_SUPPORT_X_SPEEDSTEPS_USING_X = LogMessageOffset::warning + 2003,
W2004_INPUT_ADDRESS_X_IS_INVALID = LogMessageOffset::warning + 2004,
W2005_OUTPUT_ADDRESS_X_IS_INVALID = LogMessageOffset::warning + 2005,
W9999_X = LogMessageOffset::warning + 9999,
// Error:

Datei anzeigen

@ -0,0 +1,41 @@
/**
* shared/src/traintastic/enum/traintasticdiyinterfacetype.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021-2022 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_SHARED_TRAINTASTIC_ENUM_TRAINTASTICDIYINTERFACETYPE_HPP
#define TRAINTASTIC_SHARED_TRAINTASTIC_ENUM_TRAINTASTICDIYINTERFACETYPE_HPP
#include <cstdint>
#include "enum.hpp"
enum class TraintasticDIYInterfaceType : uint8_t
{
Serial = 0,
NetworkTCP = 1,
};
TRAINTASTIC_ENUM(TraintasticDIYInterfaceType, "traintastic_diy_interface_type", 2,
{
{TraintasticDIYInterfaceType::Serial, "serial"},
{TraintasticDIYInterfaceType::NetworkTCP, "network_tcp"},
});
#endif

Datei anzeigen

@ -61,6 +61,7 @@ class_id:interface.dccplusplus=DCC++
class_id:interface.ecos=ECoS
class_id:interface.hsi88=HSI-88
class_id:interface.loconet=LocoNet
class_id:interface.traintastic_diy=Traintastic DIY
class_id:interface.wlanmaus=WLANmaus
class_id:interface.xpressnet=XpressNet
class_id:interface.z21=Z21
@ -167,6 +168,9 @@ input_map_item.block:type=Type
interface.dccplusplus:dcc_plus_plus=DCC++(EX)
interface.ecos:ecos=ECoS
interface.ecos:ecos=ECoS
interface.ecos:ecos_detector=ECoS detector
interface.ecos:s88=S88
interface.hsi88:modules_left=Modules left
interface.hsi88:modules_middle=Modulles middle
@ -174,6 +178,8 @@ interface.hsi88:modules_right=Modules right
interface.loconet:interface=Interface
interface.traintastic_diy:traintastic_diy=Traintastic DIY
interface.xpressnet:interface=Interface
interface.xpressnet:s88_module_count=S88 module count
interface.xpressnet:s88_start_address=S88 start address
@ -321,6 +327,7 @@ message:I2001=Unknown loco address: %1
message:I2002=Hardware type: %1
message:I2003=Firmware version: %1
message:I2004=HSI-88: %1
message:I2005=%1
message:I9001=Stopped script
message:I9999=%1
message:N1001=Received signal: %1
@ -357,6 +364,8 @@ message:W1002=Setting %1 doesnt exist
message:W2001=Received malformed data dropped %1 bytes
message:W2002=Command station doesn't support functions above F%1
message:W2003=Command station doesn't support %1 speedsteps using %2
message:W2004=Input address %1 is invalid
message:W2005=Output address %1 is invalid
message:W9999=%1
object:id=Id
@ -507,6 +516,12 @@ train:speed_max=Maximum speed
train:vehicles=Vehicles
train:weight=Weight
traintastic_diy_interface_type:network_tcp=Network (TCP)
traintastic_diy_interface_type:serial=Serial
traintastic_diy_settings:debug_log_heartbeat=Log heartbeat communication
traintastic_diy_settings:heartbeat_timeout=Heartbeat timeout
turnout_position:left=Left
turnout_position:right=Right
turnout_position:straight=Straight