Added 1st version of Traintastic DIY interface
Dieser Commit ist enthalten in:
Ursprung
cac339a001
Commit
f6f5511f25
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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).
|
||||
|
||||
203
manual/traintasticmanual/en-us/traintasticdiyprotocol.md
Normale Datei
203
manual/traintasticmanual/en-us/traintasticdiyprotocol.md
Normale Datei
@ -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
|
||||
@ -126,6 +126,10 @@
|
||||
"type": "appendix",
|
||||
"markdown": "xpressnet.md"
|
||||
},
|
||||
{
|
||||
"type": "appendix",
|
||||
"markdown": "traintasticdiyprotocol.md"
|
||||
},
|
||||
{
|
||||
"type": "appendix",
|
||||
"markdown": "messages.md",
|
||||
|
||||
@ -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"
|
||||
|
||||
34
server/src/enum/traintasticdiyinterfacetype.hpp
Normale Datei
34
server/src/enum/traintasticdiyinterfacetype.hpp
Normale Datei
@ -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
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
273
server/src/hardware/interface/traintasticdiyinterface.cpp
Normale Datei
273
server/src/hardware/interface/traintasticdiyinterface.cpp
Normale Datei
@ -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);
|
||||
}
|
||||
90
server/src/hardware/interface/traintasticdiyinterface.hpp
Normale Datei
90
server/src/hardware/interface/traintasticdiyinterface.hpp
Normale Datei
@ -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
|
||||
40
server/src/hardware/protocol/traintasticdiy/config.hpp
Normale Datei
40
server/src/hardware/protocol/traintasticdiy/config.hpp
Normale Datei
@ -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
|
||||
84
server/src/hardware/protocol/traintasticdiy/featureflags.hpp
Normale Datei
84
server/src/hardware/protocol/traintasticdiy/featureflags.hpp
Normale Datei
@ -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
|
||||
62
server/src/hardware/protocol/traintasticdiy/inputstate.hpp
Normale Datei
62
server/src/hardware/protocol/traintasticdiy/inputstate.hpp
Normale Datei
@ -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
|
||||
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
61
server/src/hardware/protocol/traintasticdiy/iohandler/iohandler.hpp
Normale Datei
61
server/src/hardware/protocol/traintasticdiy/iohandler/iohandler.hpp
Normale Datei
@ -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
|
||||
@ -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
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
@ -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()));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
|
||||
114
server/src/hardware/protocol/traintasticdiy/iohandler/tcpiohandler.cpp
Normale Datei
114
server/src/hardware/protocol/traintasticdiy/iohandler/tcpiohandler.cpp
Normale Datei
@ -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
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
325
server/src/hardware/protocol/traintasticdiy/kernel.cpp
Normale Datei
325
server/src/hardware/protocol/traintasticdiy/kernel.cpp
Normale Datei
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
233
server/src/hardware/protocol/traintasticdiy/kernel.hpp
Normale Datei
233
server/src/hardware/protocol/traintasticdiy/kernel.hpp
Normale Datei
@ -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
|
||||
88
server/src/hardware/protocol/traintasticdiy/messages.cpp
Normale Datei
88
server/src/hardware/protocol/traintasticdiy/messages.cpp
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
233
server/src/hardware/protocol/traintasticdiy/messages.hpp
Normale Datei
233
server/src/hardware/protocol/traintasticdiy/messages.hpp
Normale Datei
@ -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
|
||||
81
server/src/hardware/protocol/traintasticdiy/opcode.hpp
Normale Datei
81
server/src/hardware/protocol/traintasticdiy/opcode.hpp
Normale Datei
@ -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
|
||||
62
server/src/hardware/protocol/traintasticdiy/outputstate.hpp
Normale Datei
62
server/src/hardware/protocol/traintasticdiy/outputstate.hpp
Normale Datei
@ -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
|
||||
56
server/src/hardware/protocol/traintasticdiy/settings.cpp
Normale Datei
56
server/src/hardware/protocol/traintasticdiy/settings.cpp
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
53
server/src/hardware/protocol/traintasticdiy/settings.hpp
Normale Datei
53
server/src/hardware/protocol/traintasticdiy/settings.hpp
Normale Datei
@ -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
|
||||
@ -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
|
||||
|
||||
@ -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:
|
||||
|
||||
41
shared/src/traintastic/enum/traintasticdiyinterfacetype.hpp
Normale Datei
41
shared/src/traintastic/enum/traintasticdiyinterfacetype.hpp
Normale Datei
@ -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
|
||||
@ -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
|
||||
|
||||
Laden…
x
In neuem Issue referenzieren
Einen Benutzer sperren