[manual] Merge manual and lua manual into a single manual, now built using MkDocs #WIP

Dieser Commit ist enthalten in:
Reinder Feenstra 2025-09-09 23:15:52 +02:00
Ursprung fabd0b2a17
Commit 7add6a8048
179 geänderte Dateien mit 2757 neuen und 3615 gelöschten Zeilen

Datei anzeigen

@ -458,45 +458,23 @@ jobs:
with:
submodules: recursive
- name: Install python packages
run: sudo pip3 install cmarkgfm
- name: Install python requirements
run: sudo pip3 install -r requirements.txt
- name: Build manual
working-directory: ${{github.workspace}}/manual
run: ./builddoc.py html-single-page --output-dir build
run: ./build.py
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: traintastic-manual
path: ${{github.workspace}}/manual/build/*
build-manual-lua:
name: manual lua
runs-on: ubuntu-latest
steps:
- uses: FranzDiebold/github-env-vars-action@v2
- name: Checkout
uses: actions/checkout@v3
with:
submodules: recursive
- name: Build manual
working-directory: ${{github.workspace}}/manual
run: ./buildluadoc.py
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: traintastic-manual-lua
path: ${{github.workspace}}/manual/build.luadoc/*
path: ${{github.workspace}}/manual/output/*
package-innosetup:
name: package innosetup
runs-on: windows-latest
needs: [build-client, build-server, build-lang, build-manual, build-manual-lua]
needs: [build-client, build-server, build-lang, build-manual]
steps:
- uses: FranzDiebold/github-env-vars-action@v2
@ -528,13 +506,7 @@ jobs:
uses: actions/download-artifact@v4
with:
name: traintastic-manual
path: ${{github.workspace}}/manual/build
- name: "Download artifact: manual-lua"
uses: actions/download-artifact@v4
with:
name: traintastic-manual-lua
path: ${{github.workspace}}/manual/build.luadoc
path: ${{github.workspace}}/manual/output
- name: Build installer
shell: cmd
@ -551,7 +523,7 @@ jobs:
name: Deploy to website
if: ${{ github.event_name == 'push' }}
runs-on: ubuntu-latest
needs: [package-innosetup, build-server, build-client, build-data, build-manual, build-manual-lua]
needs: [package-innosetup, build-server, build-client, build-data, build-manual]
steps:
- uses: FranzDiebold/github-env-vars-action@v2
@ -595,12 +567,6 @@ jobs:
merge-multiple: true
path: ${{github.workspace}}/dist/${{env.CI_REF_NAME_SLUG}}/${{github.run_number}}
- name: "Download artifact: manual-lua"
uses: actions/download-artifact@v4
with:
name: traintastic-manual-lua
path: ${{github.workspace}}/dist/${{env.CI_REF_NAME_SLUG}}/${{github.run_number}}/manual-lua
- uses: easingthemes/ssh-deploy@v2.2.11
env:
SSH_PRIVATE_KEY: ${{secrets.SERVER_SSH_KEY}}

2
.gitignore vendored
Datei anzeigen

@ -48,3 +48,5 @@ CMakeLists.txt.*
.DS_Store
shared/translations/*.lang
CMakeSettings.json
.venv

22
manual/.gitignore vendored Normale Datei
Datei anzeigen

@ -0,0 +1,22 @@
*.sh
# generated by build.py
config/*
output/*
# generated by luadoc.py:
docs/*/appendix/lua/enum/*
docs/*/appendix/lua/example/*
docs/*/appendix/lua/object/*
docs/*/appendix/lua/set/*
docs/*/appendix/lua/class.md
docs/*/appendix/lua/enum.md
docs/*/appendix/lua/examples.md
docs/*/appendix/lua/globals.md
docs/*/appendix/lua/index-az.md
docs/*/appendix/lua/log.md
docs/*/appendix/lua/math.md
docs/*/appendix/lua/pv.md
docs/*/appendix/lua/set.md
docs/*/appendix/lua/string.md
docs/*/appendix/lua/table.md

Datei anzeigen

@ -1,27 +0,0 @@
#!/usr/bin/env python3
import sys
import os
from argparse import ArgumentParser
from traintasticmanualbuilder.utils import detect_version
if __name__ == '__main__':
if len(sys.argv) < 2:
print("Usage: " + sys.argv[0] + " <format>", file=sys.stderr)
sys.exit(1)
args = sys.argv[2:] # remove program name and format
# Standard options:
parser = ArgumentParser()
parser.add_argument('--base-dir', default=os.path.join(os.path.dirname(__file__), 'traintasticmanual'))
parser.add_argument('--output-dir')
parser.add_argument('--language', default='en-us')
parser.add_argument('--version', default=detect_version())
if sys.argv[1] == 'html-single-page':
from traintasticmanualbuilder.htmlsinglepage import HTMLSinglePageBuilder
HTMLSinglePageBuilder(parser.parse_args(args)).build()
else:
print("Unknown format: " + sys.argv[1], file=sys.stderr)
sys.exit(1)

Datei anzeigen

@ -0,0 +1,48 @@
# DCC-EX interface configuration
This page describes how to configure a DCC-EX command station in Traintastic.
!!! tip
The DCC-EX command stations can be added using the **setup wizard**, which guides you through the process step by step.
See [Quick start: Connect to your command station](../../quickstart/command-station.md) for details.
## Supported connection types
DCC-EX command stations can be connected to Traintastic in two ways:
- **Serial (RS-232/USB)** – Direct cable connection, typically via USB or a USB-to-Serial adapter.
- **Network (TCP/IP)** – Connection over Ethernet or Wi-Fi using the DCC-EX network protocol.
## Connection settings
Depending on the connection type, the following options are available:
### Serial connections
- **Device** – Path to the serial device (e.g., `COM3` on Windows or `/dev/ttyUSB0` on Linux).
- **Baudrate** – Communication speed, must match the DCC-EX configuration.
### Network connections
- **Hostname** – IP address or hostname of the DCC-EX.
- **Port** – TCP port number, must match the DCC-EX configuration. (default: 2560)
## DCC-EX settings
Additional options for fine-tuning behavior:
### Locomotive control
- **Speed steps** – Number of DCC speed steps to use for all locomotives.<br>
Options: 28 or 128 (default: 128).
### Advanced
- **Startup delay** – Delay (in milliseconds) between establishing the connection and sending the first command.<br>
Default: 2500 ms (2.5 seconds).
### Debugging
- **Debug log RX/TX** – Log all DCC-EX communication (ascii + description) for debugging purposes.
!!! tip "Need help or having issues?"
If you encounter problems while configuring DCC-EX, or if your setup behaves differently than expected, check the [community forum](https://discourse.traintastic.org).
Sharing your configuration and findings helps others and improves Traintastic.

Datei anzeigen

@ -0,0 +1,25 @@
# ECoS interface configuration
This page describes how to configure an ESU ECoS command station in Traintastic.
!!! tip
The ECoS command stations can be added using the **setup wizard**, which guides you through the process step by step.
See [Quick start: Connect to your command station](../../quickstart/command-station.md) for details.
## Supported connection types
ECoS command stations can only be connected to Traintastic over the network.
## Connection settings
- **Hostname** – IP address or hostname of the ECoS command station.
## ECoS settings
### Debugging
- **Debug log RX/TX** – Log all ECoS communication for debugging purposes.
!!! tip "Need help or having issues?"
If you encounter problems while configuring ECoS, or if your setup behaves differently than expected, check the [community forum](https://discourse.traintastic.org).
Sharing your configuration and findings helps others and improves Traintastic.

Datei anzeigen

@ -0,0 +1,27 @@
# HSI-88 interface configuration
This page describes how to configure an HSI-88 feedback interface in Traintastic.
## Supported connection types
The HSI-88 can only be connected to Traintastic using a direct serial cable connection, or using a USB-to-Serial adapter.
## Connection settings
- **Device** – Path to the serial device (e.g., `COM3` on Windows or `/dev/ttyUSB0` on Linux).
## HSI-88 settings
### Feedback modules
- **Modules left** – Number of 16-port S88 feedback modules connected to the **left** connector.
- **Modules middle** – Number of 16-port S88 feedback modules connected to the **middle** connector.
- **Modules right** – Number of 16-port S88 feedback modules connected to the **right** connector.
### Debugging
- **Debug log RX/TX** – Log all HSI-88 communication (hex) for debugging purposes.
!!! tip "Need help or having issues?"
If you encounter problems while configuring the HSI-88, or if your setup behaves differently than expected, check the [community forum](https://discourse.traintastic.org).
Sharing your configuration and findings helps others and improves Traintastic.

Datei anzeigen

@ -0,0 +1,58 @@
# Interface configuration
Traintastic supports a wide range of digital command stations, feedback sensors, and wireless throttle devices.
This section provides detailed instructions on configuring each supported interface for advanced users who need more control or whose setup is not fully covered by the setup wizard.
For a complete list of tested and supported hardware, see the [Supported hardware](../../appendix/supported-hardware.md) page.
## Overview
The interfaces covered in this section include:
### Command Station interfaces
These allow Traintastic to communicate with the layout's digital system and control locomotives, turnouts, signal and read feedback sensors:
- [DCC-EX](dcc-ex.md)
- [ECoS](ecos.md)
- [LocoNet](loconet.md) - Digitrax, Digikeijs, Uhlenbrock, YaMoRC
- [Märklin CAN](marklin-can.md) - Märklin CS2/CS3
- [XpressNet](xpressnet.md) - Lenz, Roco MultiMAUS
- [Z21](z21.md)
### Wireless throttle interfaces
Connect wireless hardware throttles to Traintastic for real-time train control:
- [WiThrottle](withrottle.md) - WiThrottle (iOS), EngineDriver (Android)
- [WLANmaus](wlanmaus.md) - Z21 WLANmaus, Z21 app
### Feedback sensor interfaces
Enable detection of train positions and layout events:
- [HSI-88](hsi-88.md)
### Custom / DIY Interfaces
For users building their own hardware:
- [Traintastic DIY](traintastic-diy.md)
## Using the Setup Wizard
Whenever possible, use the **setup wizard** to add and configure interfaces.
The wizard guides you through a step by step setup and applies recommended settings.
This is the preferred method for most users.
## Manual Configuration
Some interfaces, custom setups, or advanced features may require **manual configuration**.
Each page in this section provides step-by-step instructions, including:
- Interface requirements and limitations
- Connection and setup procedures
- Recommended settings and troubleshooting tips
!!! tip "Community Support"
If your interface or device is not listed, or if you encounter issues during configuration, check the [community forum](https://discourse.traintastic.org).
Sharing your experience helps improve Traintastic and expands the supported hardware documentation.

Datei anzeigen

@ -0,0 +1,68 @@
# LocoNet interface configuration
This page describes how to configure a LocoNet command station in Traintastic.
!!! tip
Many command stations can be added using the **setup wizard**, which guides you through the process step by step.
See [Quick start: Connect to your command station](../../quickstart/command-station.md) for details.
## Supported connection types
LocoNet command stations can be connected to Traintastic in several ways:
- **Serial (RS-232/USB)** – Direct cable connection, often via a USB-to-Serial adapter.
- **Network – Binary protocol** – TCP/IP connection using the LocoNet binary protocol.
- **Network – LBserver protocol** – TCP/IP connection using the LBserver text-based protocol.
- **Network – Z21 protocol** – UDP connection compatible with the Z21 interface.
## Connection settings
Depending on the connection type, the following options are available:
### Serial connections
- **Device** – Path to the serial device (e.g., `COM3` on Windows or `/dev/ttyUSB0` on Linux).
- **Baudrate** – Communication speed, must match the command stations configuration.
- **Flow control** – Hardware/software flow control setting, usually *None* unless the device requires it.
### Network connections
- **Hostname** – IP address or hostname of the command station.
- **Port** – TCP/UDP port number for the selected protocol.
## LocoNet settings
Additional options for fine-tuning behavior:
### Command station preset
- **Command station** – Select your command station model.<br>
This preset automatically adjusts related settings (timeouts, slot handling, etc.) to match that station.
Choosing **Custom** allows you to override and manually configure all options.
### Locomotive control
- **Locomotive slots** – Number of locomotive slots to manage.
- **F9–F28 functions** – Select how extended functions (F9–F28) are controlled.<br>
Default: Digitrax `OPC_IMM_PACKET`.<br>
Alternative: Uhlenbrock extended commands.
### Fast clock
- **Fast clock** – Enable or disable fast clock functionality.
- **Fast clock sync enabled** – Synchronize Traintastics fast clock with the LocoNet fast clock.
- **Fast clock sync interval** – Interval (seconds) at which synchronization occurs.
### Advanced
- **Echo timeout** – Timeout (ms) while waiting for echo responses.
- **Response timeout** – Timeout (ms) while waiting for command responses.
### Debugging
- **Debug log RX/TX** – Log all LocoNet traffic (hex + description).
- **PCAP capture** – Enable packet capture for debugging.
- **PCAP output file** – File path where captured packets are stored.
- **Listen-only mode** – Passive monitoring mode; no commands are sent.
!!! tip "Need help or having issues?"
If you encounter problems while configuring LocoNet, or if your setup behaves differently than expected, check the [community forum](https://discourse.traintastic.org).
Sharing your configuration and findings helps others and improves Traintastic.

Datei anzeigen

@ -0,0 +1,53 @@
# Märklin CAN interface configuration
This page describes how to configure a Märklin CAN command station in Traintastic.
!!! tip
Märklin CS2/CS3 can be added using the **setup wizard**, which guides you through the process step by step.
See [Quick start: Connect to your command station](../../quickstart/command-station.md) for details.
## Supported connection types
Märklin CAN command stations can be connected to Traintastic in two ways:
- **Network (TCP/IP)** – Recommended. Connect via Ethernet or Wi-Fi using the Märklin CAN protocol.
- **Serial (RS-232/USB)** – Direct cable connection, often via a USB-to-Serial adapter.
## Connection settings
Depending on the connection type, the following options are available:
### Network connections
- **Hostname** – IP address or hostname of the command station.
- **Port** – TCP port number (default depends on command station model).
### Serial connections
- **Device** – Path to the serial device (e.g., `COM3` on Windows or `/dev/ttyUSB0` on Linux).
- **Baudrate** – Communication speed, determined by the CAN bus interface.
- **Flow control** – Hardware/software flow control setting, usually *None* unless the interface requires it.
!!! note
Serial connections are used with **CAN bus interfaces** that bridge the Märklin CAN bus to a computer via RS-232 or USB.
This is different from connecting directly to a Märklin CS2/CS3, which should use the network connection instead.
## Märklin CAN settings
Additional options for fine-tuning behavior:
### General
- **Default switch time** – Default duration (ms) for switch/accessory commands.
### Identification
- **Node UID** – Unique identifier for the CAN node.
- **Node serial number** – Serial number of the CAN node.
Traintastic registers itself as a node on the Märklin CAN bus using the *Node UID* and *Node serial number*, in case of any conflicts they can be adjusted.
### Debugging
- **Debug log RX/TX** – Log all CAN bus traffic (hex + description).
- **Debug status data config** – Log CAN status/configuration messages for diagnostics.
- **Debug config stream** – Log the raw configuration stream.
!!! tip "Need help or having issues?"
If you encounter problems while configuring Märklin CAN, or if your setup behaves differently than expected, check the [community forum](https://discourse.traintastic.org).
Sharing your configuration and findings helps others and improves Traintastic.

Datei anzeigen

@ -0,0 +1,38 @@
# Traintastic DIY interface configuration
This page describes how to configure the **Traintastic DIY interface**, which is intended for hobbyists who build their own throttles, input/output devices, or feedback modules.
!!! note
There is **no setup wizard support** for DIY interfaces.
Manual configuration is required.
For protocol details and implementation guidance, see the [Traintastic DIY protocol reference](../../appendix/traintastic-diy-protocol.md).
## Supported connection types
DIY devices can connect to Traintastic via:
- **Serial** – Direct RS-232/USB connection.
- **Network** – TCP/IP connection over Ethernet or Wi-Fi.
## Connection settings
### Serial connections
- **Device** – Path to the serial device (e.g., `COM3` on Windows or `/dev/ttyUSB0` on Linux).
- **Baudrate** – Communication speed, must match the DIY device.
- **Flow control** – Hardware/software flow control setting, usually *None*.
### Network connections
- **Hostname** – IP address or hostname of the DIY device.
- **Port** – TCP port number, must match the device configuration.
## DIY settings
- **Startup delay** – Delay (ms) between connecting and sending the first command.
- **Heartbeat timeout** – Maximum time (ms) before connection is considered lost if no heartbeat is received.
- **Debug log RX/TX** – Log all communication traffic (hex + description).
- **Debug log heartbeat** – Log heartbeat messages for monitoring and debugging.
!!! tip "Need help or having issues?"
If you are building or testing a DIY device and run into problems, check the [community forum](https://discourse.traintastic.org).
Its a good place to share your designs and get feedback from other DIY users.

Datei anzeigen

@ -0,0 +1,28 @@
# WiThrottle interface configuration
This page describes how to configure the WiThrottle server in Traintastic.
!!! note
There is **no setup wizard support** for WiThrottle.
Configuration must be done manually.
## Overview
WiThrottle is a protocol used by many wireless throttle apps, such as **WiThrottle (iOS)** and **EngineDriver (Android)**.
When enabled, Traintastic acts as a **WiThrottle server** that wireless throttles connect to over the network.
## Connection settings
- **Port** – TCP port that throttles connect to.<br>
Default: `4444`.<br>
Normally this does not need to be changed unless the port is already in use.
## WiThrottle settings
### Debugging
- **Debug log RX/TX** – Log all WiThrottle traffic (hex + description) for debugging purposes.
!!! tip "Need help or having issues?"
If you encounter problems connecting a throttle to Traintastic, check the [community forum](https://discourse.traintastic.org).
Sharing your experience helps others and improves Traintastic.

Datei anzeigen

@ -0,0 +1,22 @@
# WLANmaus interface configuration
This page describes how to configure the WLANmaus server in Traintastic.
!!! note
There is **no setup wizard support** for WLANmaus.
Configuration must be done manually.
## Overview
The WLANmaus protocol is used by **Roco/Fleischmann WLANmaus handheld controllers** and the **Z21 mobile app**.
When enabled, Traintastic acts as a **WLANmaus server** that these throttles connect to over the network.
## WLANmaus settings
- **Allow emergency stop** – If enabled, the throttle can trigger an emergency stop for all locomotives.
- **Allow track power off** – If enabled, the throttle can switch track power off.
- **Allow track power/release emergency stop** – If enabled, the throttle can switch track power and release the emergency stop.
!!! tip "Need help or having issues?"
If you encounter problems using WLANmaus with Traintastic, check the [community forum](https://discourse.traintastic.org).
Sharing your findings helps others and improves Traintastic.

Datei anzeigen

@ -0,0 +1,61 @@
# XpressNet interface configuration
This page describes how to configure an XpressNet command station in Traintastic.
!!! tip
Many command stations can be added using the **setup wizard**, which guides you through the process step by step.
See [Quick start: Connect to your command station](../../quickstart/command-station.md) for details.
## Supported connection types
XpressNet command stations can be connected to Traintastic in two ways:
- **Serial (RS-232/USB)** – Direct cable connection, often via a USB-to-Serial adapter.
Some devices provide presets with predefined baud rate and flow control settings.
- **Network** – TCP/IP connection over Ethernet or Wi-Fi.
## Connection settings
Depending on the connection type, the following options are available:
### Serial connections
- **Serial interface type** – Preset for the connected hardware (e.g., RoSoft S88XPressNetLI).<br>
This preset sets or limits available baudrate and flow control options.
- **Device** – Path to the serial device (e.g., `COM3` on Windows or `/dev/ttyUSB0` on Linux).
- **Baudrate** – Communication speed. Defined by the selected serial interface type.
- **Flow control** – Hardware/software flow control setting, usually *None* unless the device requires it.
- **S88 start address** – (RoSoft S88XPressNetLI only) First address for connected S88 feedback modules.
- **S88 module count** – (RoSoft S88XPressNetLI only) Number of connected S88 feedback modules.
### Network connections
- **Hostname** – IP address or hostname of the command station.
- **Port** – TCP port number.
## XpressNet settings
Additional options for fine-tuning behavior:
### Command station preset
- **Command station** – Select your command station model.<br>
This preset automatically adjusts related options to match the station.
Choosing **Custom** allows you to override and configure all options manually.
### Locomotive control
- **Use emergency stop locomotive command** – Enable the dedicated XpressNet emergency stop command for locomotives.
- **Use Roco F13–F20 command** – Enable Roco-specific extended function command for F13–F20.
### Accessory control
- **Use Roco accessory addressing** – Enable Roco-style accessory addressing (alternative to standard XpressNet addressing).
### Debugging
- **Debug log RX/TX** – Log all XpressNet traffic (hex + description).
!!! tip "Need help or having issues?"
If you encounter problems while configuring XpressNet, or if your setup behaves differently than expected, check the [community forum](https://discourse.traintastic.org).
Sharing your configuration and findings helps others and improves Traintastic.

Datei anzeigen

@ -0,0 +1,29 @@
# Z21 interface configuration
This page describes how to configure a Z21 command station in Traintastic.
!!! tip
The Z21 can be added using the **setup wizard**, which guides you through the process step by step.
See [Quick start: Connect to your command station](../../quickstart/command-station.md) for details.
## Supported connection types
Z21 command stations can only be connected to Traintastic over the network.
All Z21 variants — **z21 start**, **z21 white**, and **Z21 black** — use the same configuration.
## Connection settings
- **Hostname** – IP address or hostname of the Z21 command station (e.g., `192.168.0.111`).
- **Port** – UDP port number. Default is **21105** and normally should not be changed.
## Z21 settings
Additional options:
### Debugging
- **Debug log RX/TX** – Log all Z21 traffic (hex + description).
!!! tip "Need help or having issues?"
If you encounter problems while configuring the Z21, or if your setup behaves differently than expected, check the [community forum](https://discourse.traintastic.org).
Sharing your configuration and findings helps others and improves Traintastic.

Datei anzeigen

@ -0,0 +1,47 @@
# Scripting basics
Traintastic includes a built-in **Lua scripting engine**.
Scripts allow you to extend the behavior of your world, automate tasks, and add custom logic that is not available through the standard user interface.
!!! tip
If you get stuck or want to see what others have built, visit the [community forum scripting category](https://discourse.traintastic.org/c/lua-scripting).
## What is Lua?
Lua is a lightweight, easy-to-learn scripting language, suitable for both beginners and experienced programmers.
It is developed by a team at the **Pontifical Catholic University of Rio de Janeiro (PUC-Rio)** in Brazil and is widely used in games, embedded systems, and automation tools.
In Traintastic, Lua is embedded directly into the application, so you can write scripts that interact with your model world without installing anything extra.
!!! note
Scripting is **completely optional**. You can enjoy Traintastic without writing a single line of code, but Lua scripts unlock powerful advanced possibilities.
## Working with scripts
To create or edit a script, open the **Lua scripts list** by selecting
**Objects → Lua scripts** from the main menu.
![Lua script list](../assets/images/lua/lua-scripts-list.png)
From this dialog you can:
- ![](../assets/images/toolbar/add.png) **Create** a new script (requires *edit mode*).
- ![](../assets/images/toolbar/edit.png) **Edit** the selected script in the script editor.
- ![](../assets/images/toolbar/delete.png) **Delete** a script (requires *edit mode*).
- ![](../assets/images/toolbar/run.png) **Run all** scripts, except those marked as disabled.
- ![](../assets/images/toolbar/stop.png) **Stop all** running scripts.
## Script editor
![Lua script editor](../assets/images/lua/lua-script-editor.png)
- A script can only be edited while it is **stopped** and **edit mode** is active.
- Use the **disabled** checkbox to prevent the script from running when pressing *Run all* in the scripts list.
- If an error occurs during execution, it will be logged in the **server log**.
## More information
- See the [Lua scripting reference](../appendix/lua/index.md) for available functions and objects.
- Check the [Lua scripting examples](../appendix/lua/examples.md) for ready-to-use ideas.
- Join the [community forum scripting category](https://discourse.traintastic.org/c/lua-scripting) to share ideas and ask questions.
- Learn more about the Lua language itself at [lua.org](https://www.lua.org).

Datei anzeigen

@ -0,0 +1,55 @@
# Command line options
The client and server both support various command line options to contol how the applications start and where data is stored.
These options are mainly useful for PCs only used to control the layout.
## Traintastic client
| Short | Long | Description |
|------------------------|-------------------------------|------------------------------|
| `-h` | `--help` | Displays help text |
| `-v` | `--version` | Displays version information |
| | `--fullscreen` | Start application fullscreen |
| `-c <hostname[:port]>` | `--connect <hostname[:port]>` | Connect to server |
## Traintastic server
Command line options available for Traintastic server depend on the used operation system.
The **operation system** column shows whether an option is available on **All**, **Windows**, **Linux** and/or **macOS**.
| Short | Long | Description | Operation system |
|-----------|------------------|------------------------------------------|------------------|
| `-h` | `--help` | Display help text and exit | All |
| `-v` | `--version` | Output version information and exit | All |
| `-D PATH` | `--datadir PATH` | Data directory | All |
| `-W UUID` | `--world UUID` | World UUID to load | All |
| | `--simulate` | Enable simulation after loading world | All |
| | `--online` | Enable communication after loading world | All |
| | `--power` | Enable power after loading world | All |
| | `--run` | Start after loading world | All |
| | `--tray` | Run application in system tray | Windows |
| `-d` | `--daemonize` | Run as background daemon | Linux, macOS |
| `-u NAME` | `--user NAME` | Run as user | Linux, macOS |
| `-g NAME` | `--group NAME` | Run as group | Linux, macOS |
| `-p [FILENAME]` | `--pidfile [FILENAME]` | Write pid file (default: `/run/traintastic-server.pid`) | Linux, macOS |
!!! note
`--simulate`, `--online`, `--power` and `--run` options only apply to the world loaded at startup.
!!! note
`--run` option requires `--power`, `--power` option must be set for `--run` to work.
### Data directory
The *data directory* is the location where Traintastic server stores all its data, such as: settings, worlds, logfile, backups.
The default location differs per operating system:
- **Windows**: `%LOCALAPPDATA%\traintastic\server`, e.g. `C:\Users\reinder\AppData\Local\traintastic\server`
- **Linux**:
- When running as normal user: `~/.config/traintastic-server`, e.g. `/home/reinder/.config/traintastic-server`
- When running as systemd service: `/var/opt/traintastic`
!!! note
Traintastic server stores its data per user. To use the same settings, worlds, logfile, backups
with multiple user accounts they all must start Traintastic server with the *data directory* option pointing to a location that is writable by all involved user accounts.

Datei anzeigen

@ -0,0 +1,98 @@
# LocoNet reference
LocoNet is a network bus developed by Digitrax Inc. in the early 90s for use with their products.
Today, it is used by multiple vendors, and products from different vendors can usually be combined thanks to the standardized bus.
This appendix describes Traintastics LocoNet implementation details.
This appendix does **not** explain the LocoNet protocol itself. Instead, it documents **how Traintastic implements and uses LocoNet** and which protocol messages are recognized.
It is intended for advanced users who are already familiar with the basics of the LocoNet protocol.
## Supported hardware
Traintastic supports a wide range of command stations and interfaces with LocoNet capability.
See the [Supported hardware](supported-hardware.md) page for a complete and up-to-date list.
## Implementation philosophy
Traintastic integrates with LocoNet while following its own control model:
- **Direct locomotive control**:
Traintastic does not use command station *consists*. Instead, it controls each locomotive of a train individually, adjusting speed based on each locos speed profile.
- **Minimal slot handling**:
Slot-related messages are only supported where necessary. Linking, moving, or juggling slots is not (yet) supported.
- **Extended feedback**:
Beyond the official LocoNet specification, Traintastic also supports additional messages discovered through traffic analysis (e.g., RailCom and Uhlenbrock LISSY feedback).
- **Fast clock support**:
Traintastic supports the LocoNet fast clock in both roles:
- **Master:** Traintastic controls the model time and distributes it on the LocoNet bus.
- **Slave:** Traintastic synchronizes its model time to the fast clock running on LocoNet.
- **Reverse-engineered extensions**:
Any LocoNet messages not covered by the official *LocoNet Personal Use Edition 1.0 Specification* are supported based on **traffic monitoring and reverse engineering**.
## Message support
### Power control
- Power on: `OPC_GPON` – Supported
- Power off: `OPC_GPOFF` – Supported
- `OPC_IDLE` – Supported
### Locomotive control
- Speed: `OPC_LOCO_SPD` – Supported
- Direction & F0–F4: `OPC_LOCO_DIRF` – Supported
- Functions F5–F8: `OPC_LOCO_SND` – Supported
- Functions F9-F28: `OPC_IMM_PACKET` - Supported
- Functions F9–F28: *Uhlenbrock* – Supported (reverse engineered)
- Slot management: `OPC_MOVE_SLOTS` - Not (yet) supported
- Consists: `OPC_CONSIST_FUNC`, `OPC_LINK_SLOTS`, `OPC_UNLINK_SLOTS`**Not supported**, Traintastic manages locomotives individually.
### Turnouts, signals, and outputs
- `OPC_SW_REQ` – Supported
- `OPC_SW_REP` – Not supported
- `OPC_SW_STATE` – Not supported
- `OPC_SW_ACK` – Not supported
### Feedback sensors
- Sensor feedback: `OPC_INPUT_REP` – Supported
- RailCom feedback: `OPC_MULTI_SENSE`, `OPC_MULTI_SENSE_LONG` – Supported (reverse engineered)
- Uhlenbrock LISSY (locomotive address, category, direction, speed) – Supported (reverse engineered)
### Slot and system data
- Slot read request: `OPC_RQ_SL_DATA` – Supported
- Slot read: `OPC_SL_RD_DATA` – Supported
- Slot write: `OPC_WR_SL_DATA` – Supported (fast clock)
- Slot status: `OPC_SLOT_STAT1` – Not supported
- Slot move: `OPC_MOVE_SLOTS` - Planned, not yet supported
### Programming
- Decoder programming - Planned, not yet supported
- SV Programming: `OPC_PEER_XFER` - Planned, not yet supported
- LNCV programming: *Uhlenbrock* – Supported (reverse engineered)
## Debugging and monitoring
Traintastic provides a debug option for LocoNet that logs all bus traffic.
Messages are shown in **hexadecimal format**, and for many message types a human-readable textual description of the content is also provided.
This is useful for:
- Diagnosing compatibility issues with specific devices.
- Verifying that messages are transmitted and received as expected.
- Exploring vendor-specific or undocumented extensions of LocoNet.
### Sending raw messages
Through [**Lua scripting**](../advanced/scripting-basics.md), it is also possible to:
- Send **raw LocoNet messages**, see [`send()`](lua/object/loconetinterface.md#sendpacket).
- Send **raw DCC track commands** (`OPC_IMM_PACKET`), see [`imm_packet()`](lua/object/loconetinterface.md#imm_packetdcc_packet--repeat--2).
!!! warning
Use this with caution.
- These messages bypass Traintastics normal handling.
- You need a solid understanding of LocoNet and DCC to avoid conflicts.
- Side effects may occur that Traintastic is not aware of or cannot manage.

Datei anzeigen

@ -0,0 +1,98 @@
# Lua Language Basics
This page introduces the basic syntax and concepts of the Lua programming language.
If you are already familiar with Lua, you can skip this page and continue with the [Globals](globals.md).
Lua is a lightweight and easy-to-learn scripting language. It is designed to be simple, flexible, and embeddable.
Traintastic uses Lua to allow you to write scripts that interact with your model railway world.
## Variables
Variables are used to store values.
```lua
name = "Traintastic"
speed = 80
enabled = true
```
- Strings are written in quotes (`"text"`).
- Numbers can be integers or decimals.
- Booleans are `true` or `false`.
## Comments
Comments are ignored by Lua but help you explain your code.
```lua
-- This is a single-line comment
--[[
This is a
multi-line comment
]]
```
## Control structures
Lua uses familiar programming constructs.
### If / else
```lua
if speed > 60 then
log.info("Fast!")
else
log.info("Slow.")
end
```
### Loops
```lua
for i = 1, 5 do
log.debug("Step", i)
end
```
```lua
while enabled do
log.debug("Running...")
break
end
```
## Functions
Functions group code into reusable blocks.
```lua
function greet(name)
log.info("Hello " .. name)
end
greet("Traintastic")
```
## Tables
Tables are Luas only data structure. They work as lists, dictionaries, or objects.
```lua
car = { type = "freight", cargo = "coal" }
log.debug(car.type) -- "freight"
numbers = { 10, 20, 30 }
log.debug(numbers[1]) -- 10
```
## Putting it together
With just these basics—variables, control flow, functions, and tables—you can already create useful scripts in Traintastic.
> **Tip**
> Dont worry about learning everything at once. Start simple and build up as you go.
## Next steps
- See what Traintastic adds on top of Lua in [Globals](globals.md).
- Explore [Persistent variables](pv.md) to keep data between sessions.
- Browse the [Examples](examples.md) to learn by doing.

Datei anzeigen

@ -0,0 +1 @@
# Events

Datei anzeigen

@ -0,0 +1,43 @@
# Scripting Reference – Introduction
Traintastic embeds a [Lua](https://www.lua.org) scripting engine that allows you to extend and automate your model railway world.
This reference describes all scripting features available inside Traintastic: built-in globals, libraries, objects, and custom extensions.
## How to use this reference
This reference is divided into sections:
- **Globals** – predefined variables and functions available everywhere.
- **Libraries** – extended functionality, such as math, string, and logging.
- **Persistent variables** – data that is stored across world save/load.
- **Enums, Sets, Objects** – Traintastic-specific types to interact with the world.
- **Examples** – practical usage samples that combine the above.
!!! tip
If you are already familiar with Lua, you can skip the language basics and go straight to the [Globals](globals.md).
If you are new to Lua, read the [Lua language basics](basics.md) first.
## Core Lua vs. Traintastic extensions
Most of Luas standard features are available: numbers, strings, tables, control flow, functions, etc.
Traintastic adds its own extensions to let you control the simulation:
- **The `world` global** – the entry point to interact with your world.
- **Enums and sets** – special types to represent model railway concepts.
- **Objects** – live references to trains, vehicles, sensors, switches, and more.
Together, these extensions make it possible to build powerful automations, ranging from simple macros to complex traffic control logic.
## Getting started
To try out scripting:
1. Open the **Lua scripts list** via *Objects → Lua scripts* in the main menu.
2. Create a new script and open it in the script editor.
3. Press **Run all** in the Lua script list to execute it.
## Next steps
- Learn [Lua language basics](basics.md) if youre new to Lua.
- Explore the [Globals](globals.md) page to see what is available.
- Check out the [Examples](examples.md) for ready-to-use snippets.

Datei anzeigen

@ -0,0 +1,70 @@
# Supported hardware
This page lists command stations and digital systems that have been tested with Traintastic.
Using the setup wizard is **recommended** whenever possible.
If wizard support is not available, setup can still be done manually by creating and configuring the correct interface type.
For details on manual interface configuration, see the [Interface configuration](../advanced/interface/index.md) section.
!!! tip "Not on the list?"
Your system may still work even if it isnt listed here.
Visit the [community forum](https://discourse.traintastic.org) to see if others have tried it, or share your own experience to help extend this list.
## Supported command stations
| Vendor / System | Interface type(s) | Notes |
|-----------------------|-------------------------------|---------------------------------|
| DCC-EX | USB, Network | - |
| Digikeijs DR5000 | USB, Network | - |
| ESU ECoS | Network | - |
| ESU ECoS 2 | Network | - |
| LDT HSI-88 | Serial | No wizard support. |
| Märklin CS2 | Network | - |
| Roco MultiMAUS | XpressNet | Requires a XpressNet interface. |
| Uhlenbrock Intellibox | LocoNet, Serial not supported | Requires a LocoNet interface. |
| Uhlenbrock IB-COM | USB | - |
!!! note
All systems listed here have been verified either with the setup wizard or through manual configuration.
If you encounter issues with a listed system, please report them on the [community forum](https://discourse.traintastic.org) so the documentation and wizard can be improved.
## Untested command stations
These systems are expected to work with Traintastic, but have not yet been verified.
| Vendor / System | Interface type(s) | Wizard support | Notes |
|-----------------------------|-------------------------------|----------------|-----------------------------------------|
| Digitrax LocoNet | Serial, USB, Network | No | Configure LocoNet interface manually. |
| Lenz XpressNet | Serial, USB, Network | No | Configure XpressNet interface manually. |
| Märklin CS3 | Network | Yes | - |
| Märklin CS3 plus | Network | Yes | - |
| Uhlenbrock Intellibox Basic | USB | Yes | - |
| Uhlenbrock Intellibox IR | Loconet, Serial not supported | Yes | Requires a LocoNet interface. |
| Uhlenbrock Intellibox II | USB | Yes | - |
| Uhlenbrock Intellibox 2neo | USB | Yes | - |
| YaMoRC YD7001 | USB, Network | No | - |
| YaMoRC YD7010 | USB, Network | No | - |
| z21 start | Network | Yes | - |
| z21 white | Network | Yes | - |
| Z21 black | Network | Yes | - |
!!! tip "Share your success!"
Have you successfully used one of these systems with Traintastic?
Please share your experience on the [community forum](https://discourse.traintastic.org).
Your feedback helps verify compatibility and improve this list for other users.
## Unsupported command stations
These systems are currently **not supported**.
Some lack sufficient technical documentation for implementation.
Supported for others has not been developed due to missing hardware.
| Vendor / System | Interface Type(s) | Notes |
|-----------------|-------------------|---------------------|
| Selectix | Serial | In development |
| VPEB Dinamo | Serial, USB | Track driver system |
!!! tip "Contributions are welcome!"
Can you help improve support for these systems?
Testing results, feedback, or legally obtained technical documentation are very welcome.
Please share on the [community forum](https://discourse.traintastic.org).

Datei anzeigen

@ -1,19 +1,21 @@
# Traintastic DIY protocol {#tdiyp}
# Traintastic DIY protocol
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 bits, no parity, one stop bit)
- Network connection (TCP): port number can be chosen.
It is currently limited to:
- Reading inputs
- Controlling outputs
- Throttles
Other features might be added in the future.
## Message format {#tdiyp-message-format}
## 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.
@ -39,29 +41,26 @@ The lowest nibble of the first byte is `F` indicating that the second byte must
The checksum is `0x2F` XOR `0x20` XOR *all payload bytes*.
## Messages {#tdiyp-messages}
## Messages
Messages are send by Traintastic to the DIY device, for most messages 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 |
| [Throttle set function](#tdiyp-throttle-set-function) | Mandatory if throttle feature flag is set |
| [Throttle set speed/direction](#tdiyp-throttle-set-speed-direction) | Mandatory if throttle feature flag is set |
| [Throttle subscribe/unsubscribe](#tdiyp-throttle-sub-unsub) | Mandatory if throttle feature flag is set |
**Badges**:
- The $badge:since:v0.2$ badge indicates in which version of Traintastic the message is added.
| Command | |
|---------------------------------------------------------------|-------------------------------------------|
| [Heartbeat](#heartbeat) | Mandatory |
| [Get information](#get-information) | Mandatory |
| [Get features](#get-features) | Mandatory |
| [Get input state](#get-input-state) | Mandatory if input feature flag is set |
| [Set input state](#set-input-state) | Mandatory if input feature flag is set |
| [Get output state](#get-output-state) | Mandatory if output feature flag is set |
| [Set output state](#set-output-state) | Mandatory if output feature flag is set |
| [Throttle set function](#throttle-set-function) | Mandatory if throttle feature flag is set |
| [Throttle set speed/direction](#throttle-set-speed-direction) | Mandatory if throttle feature flag is set |
| [Throttle subscribe/unsubscribe](#throttle-sub-unsub) | Mandatory if throttle feature flag is set |
### Heartbeat $badge:since:v0.2$ {#tdiyp-heartbeat}
### Heartbeat {#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.
@ -77,7 +76,7 @@ The heartbeat rate can be configured in Traintastic, by default the heartbeat me
```
### Get information $badge:since:v0.2$ {#tdiyp-get-information}
### Get information {#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.
@ -93,7 +92,7 @@ This is pure informational and displayed in the message console.
0xFF <len> <text...> <checksum>
```
### Get features $badge:since:v0.2$ {#tdiyp-get-features}
### Get features {#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.
@ -107,17 +106,18 @@ The DIY device responds with a *features* message containing flags which indicat
```
0xE4 <FF1> <FF2> <FF3> <FF4> <checksum>
```
- `<FF1>` feature flags 1:
- bit 0: input feature flag: set if the DIY device has inputs $badge:since:v0.2$
- bit 1: output feature flag: set if the DIY device has outputs $badge:since:v0.2$
- bit 2: throttle feature flag: set if the DIY device is a throttle $badge:since:v0.2$
- bit 3...7: reserved, must be `0`
- bit 0: input feature flag: set if the DIY device has inputs
- bit 1: output feature flag: set if the DIY device has outputs
- bit 2: throttle feature flag: set if the DIY device is a throttle
- bit 3...7: reserved, must be `0`
- `<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}
### Get input state {#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.
@ -131,15 +131,15 @@ Address zero has a special meaning, it is used as broadcast address to retrieve
- `<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 non zero the DIY device responds with a *[set input state](#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.
If the address is zero the DIY device responds with multiple *[set input state](#set-input-state)* messages, one for each know input address or
send a single *[set input state](#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}
### Set input state {#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.
Sent by the DIY device as response to the *[get input state](#get-input-state)* message and must be sent by the DIY device whenever an input state changes.
#### Message
```
@ -149,11 +149,11 @@ Sent by the DIY device as response to the *[get input state](#tdiyp-get-input-st
- `<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
- `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](#get-input-state)* message)
- `0x04`...`0xFF` are reserved, do not use
#### Examples
```
@ -167,7 +167,7 @@ Input 18 state changed to high/true
Input 674 state changed to low/false
### Get output state $badge:since:v0.2$ {#tdiyp-get-output-state}
### Get output state {#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.
@ -181,17 +181,17 @@ Address zero has a special meaning, it is used as broadcast address to retrieve
- `<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 non zero the DIY device responds with a *[set output state](#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.
If the address is zero the DIY device responds with multiple *[set inpoutputut state](#set-output-state)* messages, one for each know output address or
send a single *[set output state](#set-output-state)* message with address zero and state *invalid* to inform Traintastic that the address zero request is not supported.
### Set output state $badge:since:v0.2$ {#tdiyp-set-output-state}
### Set output state {#set-output-state}
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.
Sent by the DIY device as response to the *[get output state](#get-output-state)* message and must be sent by the DIY device whenever an output state changes.
#### Message
```
@ -201,19 +201,19 @@ Sent by the DIY device as response to the *[get output state](#tdiyp-get-output-
- `<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
- `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](#get-output-state)* message)
- `0x04`...`0xFF` are reserved, do not use
### Throttle set speed/direction $badge:since:v0.2$ {#tdiyp-throttle-set-speed-direction}
### Throttle set speed/direction {#throttle-set-speed-direction}
Set locomotive decoder speed and/or direction.
Once a *Throttle set speed/direction* message is sent, the *throttle id* will automatically be subscribed for speed, direction and function changes of the locomotive decoder specified by the *decoder address*.
To stop receiving these changes a *[throttle unsubscribe](#tdiyp-throttle-sub-unsub)* message has to be send to Traintastic by the DIY device.
To stop receiving these changes a *[throttle unsubscribe](#throttle-sub-unsub)* message has to be send to Traintastic by the DIY device.
#### Message
```
@ -223,17 +223,17 @@ To stop receiving these changes a *[throttle unsubscribe](#tdiyp-throttle-sub-un
- `<TH>` high byte of 16bit throttle id
- `<TL>` low byte of 16bit throttle id
- `<AH>`:
- bit 0...5: highest 6bit of 14bit decoder address
- bit 6 is reserved and must be `0`
- bit 7 can be set to force a *DCC long address*
- bit 0...5: highest 6bit of 14bit decoder address
- bit 6 is reserved and must be `0`
- bit 7 can be set to force a *DCC long address*
- `<AL>` lowest 8bit of 14bit decoder address
- `<SP>` speed (step), `0`...`<SM>`
- `<SM>` maximum speed (step), set to `0` for emergency stop
- `<FL>` flags:
- bit 0: direction `1`=forward, `0`=reverse
- bit 1...5: reserved, must be `0`
- bit 6: set to set direction
- bit 7: set to set speed
- bit 0: direction `1`=forward, `0`=reverse
- bit 1...5: reserved, must be `0`
- bit 6: set to set direction
- bit 7: set to set speed
*Throttle id* can be used to distinguish different throttles within the DIY device if it represents multiple throttles. If the DIY device is a single throttle use `0x00` `0x00` as *throttle id*.
@ -253,12 +253,12 @@ Set speed to 50% (= 7 / 14) in forward direction for decoder with address 3.
Emergency stop decoder with address 3, don't change direction.
### Throttle set function $badge:since:v0.2$ {#tdiyp-throttle-set-function}
### Throttle set function {#throttle-set-function}
Enable/disable locomotive decoder function.
Once a *Throttle set function* message is sent, the *throttle id* will automatically be subscribed for speed, direction and function changes of the locomotive decoder specified by the *decoder address*.
To stop receiving these changes a *[throttle unsubscribe](#tdiyp-throttle-sub-unsub)* message has to be send to Traintastic by the DIY device.
To stop receiving these changes a *[throttle unsubscribe](#throttle-sub-unsub)* message has to be send to Traintastic by the DIY device.
#### Message
```
@ -268,13 +268,13 @@ To stop receiving these changes a *[throttle unsubscribe](#tdiyp-throttle-sub-un
- `<TH>` high byte of 16bit throttle id
- `<TL>` low byte of 16bit throttle id
- `<AH>`:
- bit 0...5: highest 6bit of 14bit decoder address
- bit 6: reserved and must be `0`
- bit 7: set to force a *DCC long address*
- bit 0...5: highest 6bit of 14bit decoder address
- bit 6: reserved and must be `0`
- bit 7: set to force a *DCC long address*
- `<AL>` lowest 8bit of 14bit decoder address
- `<FN>`:
- bit 0...6: function number
- bit 7: function value
- bit 0...6: function number
- bit 7: function value
#### Examples
```
@ -288,11 +288,12 @@ Enable F0 for decoder with address 3.
Disable F1 for decoder with long address 5.
### Throttle subscribe/unsubscribe $badge:since:v0.2$ {#tdiyp-throttle-sub-unsub}
### Throttle subscribe/unsubscribe {#throttle-sub-unsub}
Subscribe/unsubscribe for change events. Traintastic will send a *[throttle set speed/direction](#tdiyp-throttle-set-speed-direction)* message whenever speed or direction changes and a *[throttle set function](#tdiyp-throttle-set-function)* message for every function that changes state.
Subscribe/unsubscribe for change events. Traintastic will send a *[throttle set speed/direction](#throttle-set-speed-direction)* message whenever speed or direction changes and a *[throttle set function](#throttle-set-function)* message for every function that changes state.
Note: Subscribe is supported since $badge:since:v0.3$, older version only support unsubscribe.
!!! note
Subscribe is supported since v0.3, older version only support unsubscribe.
#### Message
```
@ -302,11 +303,11 @@ Note: Subscribe is supported since $badge:since:v0.3$, older version only suppor
- `<TH>` high byte of 16bit throttle id
- `<TL>` low byte of 16bit throttle id
- `<AH>`:
- bit 0...5: highest 6bit of 14bit decoder address
- bit 6: action: `0` = Unsubscribe, `1` = Subscribe
- bit 7: set to force a *DCC long address*
- bit 0...5: highest 6bit of 14bit decoder address
- bit 6: action: `0` = Unsubscribe, `1` = Subscribe
- bit 7: set to force a *DCC long address*
- `<AL>` lowest 8bit of 14bit decoder address
When *subscribing* Traintastic will reply with a *[throttle set speed/direction](#tdiyp-throttle-set-speed-direction)* and a *[throttle set function](#tdiyp-throttle-set-function)* message for every function that is known for the address.
When *subscribing* Traintastic will reply with a *[throttle set speed/direction](#throttle-set-speed-direction)* and a *[throttle set function](#throttle-set-function)* message for every function that is known for the address.
When *unsubscribing* Traintastic will reply with the same message to confirm the unsubscribe.

Datei anzeigen

@ -0,0 +1,52 @@
# XpressNet reference
XpressNet (originally called X-Bus) is a communication bus developed by Lenz Elektronik GmbH.
It is used by Lenz and several other manufacturers, making it possible to mix and match devices from different vendors.
This appendix describes Traintastic's XpressNet implementation details.
## Supported hardware
Traintastic supports a wide range of command stations and interfaces with XpressNet capability.
See the [Supported hardware](supported-hardware.md) page for a complete and up-to-date list.
## Message support
### Power control
- Power on – Supported
- Power off – Supported
### Locomotive control
- Emergency stop all locomotives - Supported
- Emergency stop locomotive - Supported
- Speed & direction control: 14/27/28/128 steps – Supported
- Functions F0–F28: Supported
- Roco MultiMAUS functions F13–F20 – Supported (based on traffic analysis of MultiMAUS communication)
- Consists – **Not supported**, Traintastic manages locomotives individually.
### Turnouts, signals, and outputs
- Accessory control – Supported
### Feedback sensors
- Sensor feedback – Supported
### Fast clock
- OpenDCC custom extension – Not supported
### Programming
- Decoder programming - Planned, not yet supported
## Debugging and monitoring
Traintastic provides a debug option for XpressNet that logs all bus traffic.
Messages are shown in **hexadecimal format**, and for many message types a human-readable textual description of the content is also provided.
This is useful for:
- Diagnosing compatibility issues with specific devices.
- Verifying that messages are transmitted and received as expected.
- Exploring vendor-specific or undocumented extensions of XpressNet.
---
!!! footnote
For full protocol details, see the official [XpressNet specification in the *23151 Interface LAN und USB* manual (PDF, German)](https://www.lenz-elektronik.de/media/37/8b/2f/1734009949/b_23151.pdf) published by Lenz.

Datei anzeigen

Vorher

Breite:  |  Höhe:  |  Größe: 5.5 KiB

Nachher

Breite:  |  Höhe:  |  Größe: 5.5 KiB

Datei anzeigen

Vorher

Breite:  |  Höhe:  |  Größe: 5.4 KiB

Nachher

Breite:  |  Höhe:  |  Größe: 5.4 KiB

Datei anzeigen

Vorher

Breite:  |  Höhe:  |  Größe: 4.8 KiB

Nachher

Breite:  |  Höhe:  |  Größe: 4.8 KiB

Datei anzeigen

Vorher

Breite:  |  Höhe:  |  Größe: 697 B

Nachher

Breite:  |  Höhe:  |  Größe: 697 B

Datei anzeigen

Vorher

Breite:  |  Höhe:  |  Größe: 2.8 KiB

Nachher

Breite:  |  Höhe:  |  Größe: 2.8 KiB

Datei anzeigen

Vorher

Breite:  |  Höhe:  |  Größe: 2.0 KiB

Nachher

Breite:  |  Höhe:  |  Größe: 2.0 KiB

Datei anzeigen

Vorher

Breite:  |  Höhe:  |  Größe: 1.6 KiB

Nachher

Breite:  |  Höhe:  |  Größe: 1.6 KiB

Datei anzeigen

Vorher

Breite:  |  Höhe:  |  Größe: 744 B

Nachher

Breite:  |  Höhe:  |  Größe: 744 B

Datei anzeigen

Vorher

Breite:  |  Höhe:  |  Größe: 1.2 KiB

Nachher

Breite:  |  Höhe:  |  Größe: 1.2 KiB

Datei anzeigen

Vorher

Breite:  |  Höhe:  |  Größe: 5.2 KiB

Nachher

Breite:  |  Höhe:  |  Größe: 5.2 KiB

Datei anzeigen

Vorher

Breite:  |  Höhe:  |  Größe: 1.4 KiB

Nachher

Breite:  |  Höhe:  |  Größe: 1.4 KiB

47
manual/docs/en/index.md Normale Datei
Datei anzeigen

@ -0,0 +1,47 @@
# Welcome to Traintastic manual
**Traintastic** is model railroad control and automation software.
It allows you to control locomotives, switches, signals, and accessories from your computer, and automate your entire layout.
Whether youre building your first digital model railway or managing a large automated system, Traintastic helps you run trains smoothly and safely.
## Key features
- Control locomotives and accessories
- Supports multiple digital control systems
- Real-time monitoring of your layout
- Multi-user and network control
- Extensible using scripting
## How Traintastic works
Traintastic uses a **client/server architecture**:
- The **server** controls your layout and manages the automation.
- The **client** is the graphical interface you use to operate and monitor the layout.
Both can run on the same computer, or the server can run on one machine (for example a Raspberry Pi) while clients connect from other PCs on the network.
## Getting started
If youre new to Traintastic, start here:
- Installation Guide:
- [Windows](installation/windows.md)
- [Linux](installation/linux.md)
- Quick start series:
- [Create your first world](quickstart/world.md)
- [Connect to your command station](quickstart/command-station.md)
- [Add and control a train](quickstart/trains.md)
## Documentation structure
- **Beginner Guides** – step-by-step setup and first trains
- **Advanced Topics** – automation, scripts, and custom setups
- **Reference** – detailed descriptions of settings and commands
- **FAQ and Troubleshooting** – common problems and solutions
## Community and Support
The **[Traintastic Community Forum](https://discourse.traintastic.org)** is the primary place for support, discussion, and sharing ideas.
Join the community to ask questions, learn from other users, and help each other get the most out of Traintastic.
Additional resources:
- [Project Website](https://traintastic.org) – news, downloads, and general info
- [GitHub Repository](https://github.com/traintastic/traintastic) – source code, issue tracker, and development

Datei anzeigen

@ -0,0 +1,64 @@
# Linux Installation
Traintastic provides Debian packages for **Ubuntu (amd64/arm64)** and for **Raspberry Pi (armel/arm64)**.
There are three packages available:
- **traintastic-data** – required data package (must always be installed)
- **traintastic-server** – the main server, required if this machine will control your layout
- **traintastic-client** – graphical client application, optional for headless setups
## Installing
1. Download the `.deb` packages from [traintastic.org/download](https://traintastic.org/download).
2. Install the required packages using `apt` (preferred) or `dpkg`. For example:
```bash
sudo apt update
sudo apt install ./traintastic-data_<version>_all.deb \
./traintastic-server_<version>_<arch>.deb \
./traintastic-client_<version>_<arch>.deb
```
Replace `<version>` with the release number (e.g. `0.3.0`) and `<arch>` with your platform (`amd64`, `arm64`, `armhf`).
## Choosing packages
- If this computer will control your layout → install `traintastic-server` + `traintastic-data`.
- If this computer will run the graphical interface only → install `traintastic-client` + `traintastic-data`.
- For most desktop/laptop setups → install all three (`traintastic-server` + `traintastic-client` + `traintastic-data`).
## Running the server (systemd)
When installed via the Debian package, Traintastic server is set up as a systemd service, but it is disabled by default.
Start the server:
```bash
sudo systemctl start traintastic-server.service
```
Stop the server:
```bash
sudo systemctl stop traintastic-server.service
```
You need sufficient permissions (typically root) to manage systemd services.
### Autostart server on Boot
Enable the server to run automatically at system boot:
```bash
sudo systemctl enable traintastic-server.service
```
Disable automatic start:
```bash
sudo systemctl disable traintastic-server.service
```
## Running the client
If you installed the client package, you can start it from your desktop environments application launcher menu (look for Traintastic).
---
After installation, continue with: [The Quick Start series](../quickstart/index.md).

Datei anzeigen

@ -0,0 +1,68 @@
# Windows Installation
Installing Traintastic on Windows takes only a few minutes.
Youll download the installer, allow it through Windows Defender if prompted, and follow the setup wizard.
This guide walks you through every step with screenshots so you know exactly what to expect.
Download the latest release from [traintastic.org/download](https://traintastic.org/download), run the installer and follow the steps below.
---
## Step 1: Windows Defender
![](../assets/images/installation/windows/traintastic-setup-1.png)
Windows may warn you because Traintastic is not yet a signed application. This is expected — you can safely continue.
Click *More info* to show application information.
## Step 2: Windows Defender
![](../assets/images/installation/windows/traintastic-setup-2.png)
Click *Run anyway* to start the installer.
## Step 3: User Account Control
![](../assets/images/installation/windows/traintastic-setup-3.png)
Click *Yes* to allow running the installer.
## Step 4: Select Language
![](../assets/images/installation/windows/traintastic-setup-4.png)
Select the language for the installation wizard and click *OK*.
The chosen language will also be used by Traintastic when it starts.
## Step 5: License Agreement
![](../assets/images/installation/windows/traintastic-setup-5.png)
Select *I accept the agreement* and click *Next*.
## Step 6: Select Components
![](../assets/images/installation/windows/traintastic-setup-6.png)
Choose the installation type:
- **Client and Server** – select this if this PC will control your model railway.
- **Client only** – select this if this PC will only connect to another Traintastic server on your network.
Then click *Next*.
## Step 7: Desktop Shortcuts and Firewall Rules
![](../assets/images/installation/windows/traintastic-setup-7.png)
- Uncheck *Create a desktop shortcut* if you dont want shortcuts.
- Firewall rules will be added automatically to allow other PCs and devices to connect to Traintastic. You can uncheck this if not needed.
Then click *Next*.
## Step 8: Ready for Installation
![](../assets/images/installation/windows/traintastic-setup-8.png)
Click *Install* to start the installation of Traintastic.
## Step 9: Installation Finished
![](../assets/images/installation/windows/traintastic-setup-9.png)
Click *Finish* to exit the installer. Installation is now complete.
---
After installation, continue with: [The Quick Start series](../quickstart/index.md).

Datei anzeigen

@ -0,0 +1,69 @@
# Quick start: Connect to your command station
After creating your first world, the next step is to connect Traintastic to your **command station / digital system**.
This is done by creating an **interface**, which acts as the bridge between Traintastic and your hardware.
## Step 1: Open the interfaces list
1. Make sure you are in **edit mode** (pencil button in the top right).
2. In the main menu, go to **Objects → Hardware → Interfaces**.
3. An *Interfaces* dialog will appear. On a new installation, this list is empty.
## Step 2: Start the setup wizard
1. Click the **+** button to add a new interface.
2. In the menu that opens, select **Setup using wizard**.
- The menu also lists all supported interface types directly, but those entries are intended for experienced users who already know how to configure them manually.
## Step 3: Select your system
In the wizard, choose your **digital system / command station** from the list of hardware.
- The wizard covers the most common systems.
- Not all command stations are listed, but many other models may still work if you configure them manually from the *Interfaces* menu.
See the [Supported hardware appendix](../appendix/supported-hardware.md) for a complete overview of tested systems.
!!! note
If your command station is not listed in the wizard, please check the [community forum](https://discourse.traintastic.org).
Other users may already have experience with your hardware, and your feedback helps us improve Traintastic and expand the wizard with more systems.
Depending on your selection, Traintastic will ask additional questions, such as:
- **How it is connected**: serial, USB, or network (Ethernet or Wi-Fi).
*Tip: Wi-Fi is supported but not recommended for stability reasons.*
- **Device or port**: for example a COM port (Windows), `/dev/ttyUSB0` (Linux), or an IP address.
When you finish the wizard:
- The new interface will appear in the *Interfaces* list.
- A **status icon** will show in the status bar (right side of the window).
- Gray = offline
- Purple = initializing
- Green = online/connected
- Red = error
If you add multiple interfaces, each has its own status icon.
## Step 4: Connect and test
1. Press the **Connect** button (double arrow icon, top left on the toolbar)
or use the menu: **World → Connection → Connect**.
2. The status icon will change:
- Purple while initializing (up to a few seconds).
- Green if the connection succeeds.
- Red if an error occurs.
If an error occurs:
- Open the **server log** (hotkey `F12` or **View → Server log**) to see details.
- See [Common interface connection errors](../troubleshooting/interface-connection-errors.md) for steps to resolve typical issues.
- If youre unsure whether your system is supported, check the [Supported hardware appendix](../appendix/supported-hardware.md).
When the connection succeeds:
Toggle the track power using the **Power** button (left of *Connect* button).
If the command station responds, your connection is working!
---
With your command station connected, youre ready to [add and control locomotives](trains.md).

Datei anzeigen

@ -0,0 +1,8 @@
# Quick start
The quick start series will guide you through the basics of using Traintastic.
Each step introduces an essential concept and helps you get hands-on quickly.
1. [Create your first world](world.md)
2. [Connect to your command station](command-station.md)
3. [Add and run your first train](trains.md)

Datei anzeigen

@ -0,0 +1,65 @@
# Quick start: Add and control a train
In Traintastic, you dont directly control *locomotives*.
Instead, you control **trains**.
A **train** is a collection of vehicles (locomotives and wagons).
- A train may contain **one or more locomotives**.
- The same locomotive or wagon can belong to **different trains**, but it can only be **active in one train at a time**.
- To operate a train, you must **activate it**. Activation only succeeds if none of its locomotives or wagons are already active in another train.
This concept makes it easy to build and re-use different train compositions.
## Step 1: Open the trains and vehicles dialog
1. Make sure you are in **edit mode** (pencil button top right).
2. Open the dialog in one of two ways:
- From the main menu: **Objects → Trains**
- Or click the **Train icon** on the toolbar.
The dialog has three tabs:
- **Trains** — define and manage your trains
- **Rail vehicles** — define and manage locomotives and wagons
- **Throttle** — for controlling trains (covered later in the Quick Start series)
> Note: The *Throttle* tab is part of this dialog, but well come back to it after youve created and activated a train.
## Step 2: Create a locomotive
TODO: rethink decoder logic, how it works currently is hard to explain.
1. Switch to the **Rail vehicles** tab.
2. Click the **+** button and choose **Locomotive**.
3. Enter the locomotive details:
- **Name** (e.g. “BR 101” or “My Loco”)
- **Address** (the DCC/MM/mfx address used by your decoder)
- Optionally other properties such as function mapping.
4. Close the locomotive dialog.
The locomotive is now in the list of rail vehicles.
## Step 3: Create a train
1. Switch to the **Trains** tab.
2. Click the **+** button to create a new train.
3. Enter a name for the train (e.g. “InterCity” or “Freight Train”).
4. With the new train selected, go to the **Vehicles** tab inside the train dialog.
5. Click **+** and add your locomotive (and optionally wagons).
6. Close the train dialog.
The train is now defined but not yet active.
## Step 4: Activate the train
TODO: rewrite and rethink logic, how it works is hard to explain.
1. Switch to **operate mode** (toggle the pencil button off).
2. Double click on the train in the list, this will open a throttle and automatically activate the train.
If activation succeeds:
- A throttle window opens for controlling speed and direction.
- The trains status changes to *active*.
If activation fails:
- One of the vehicles may already be active in another train.
Deactivate the other train first, or adjust your composition.

Datei anzeigen

@ -0,0 +1,47 @@
# Quick start: Create your first world
In Traintastic, everything starts with a **world**.
A world is your project file — it represents your miniature railway, including locomotives, accessories, routes, and automation.
Each builder creates their own world, just like each model railway is its own miniature universe.
## Step 1: Start the server and client
1. Start the **server** on your computer (or device such as a Raspberry Pi).
2. Start the **client**. The client automatically searches for the server and connects.
If the server has no world loaded (the default on a fresh installation), the client shows three options:
- *New world* – create a new world using a wizard
- *Load world* – open an existing world saved on this server
- *Import world* – import a world file previously exported
## Step 2: Create a new world
Click *New world* to launch the wizard:
1. Enter a **name** for your world (for example *My First Layout*).
2. Choose the **scale** (H0, N, Z, etc.).
3. Finish the wizard to create your new, empty world.
## Step 3: Edit and operate modes
When a new world is created it opens in **edit mode**.
You can switch between modes using the pencil button in the top right corner:
- **Edit mode** – add or change objects and properties
- **Operate mode** – run trains and interact with the layout
Many properties can only be changed in edit mode. This prevents making accidental changes while operating your model railway.
## Step 4: Saving and sharing worlds
- Worlds are always saved on the **server** in a standard location.
- When saving a world, a backup is made automatically.
- To share your world (for example on the community forum or to use it on another system), use the *Export* function.
- The exported file can later be imported on any server.
---
**Your new world is ready!**
The next step is to [connect to your command station](command-station.md), so Traintastic can control your layout.

Datei anzeigen

@ -0,0 +1,3 @@
# Common interface connection errors
TODO

Datei anzeigen

@ -0,0 +1,27 @@
# Tiles
## Rail tiles
- ![](../assets/images/board/tiles/board_tile.rail.straight.png) Straight
- ![](../assets/images/board/tiles/board_tile.rail.buffer_stop.png) Buffer stop
- ![](../assets/images/board/tiles/board_tile.rail.tunnel.png) Tunnel
- ![](../assets/images/board/tiles/board_tile.rail.curve_45.png) Curve 45°
- ![](../assets/images/board/tiles/board_tile.rail.curve_90.png) Curve 90°
- ![](../assets/images/board/tiles/board_tile.rail.cross_45.png) Crossover 45°
- ![](../assets/images/board/tiles/board_tile.rail.cross_90.png) Crossover 90°
- ![](../assets/images/board/tiles/board_tile.rail.bridge_45_left.png) Bridge 45° (left)
- ![](../assets/images/board/tiles/board_tile.rail.bridge_45_right.png) Bridge 45° (right)
- ![](../assets/images/board/tiles/board_tile.rail.bridge_90.png) Bridge 90°
- ![](../assets/images/board/tiles/board_tile.rail.turnout_left_45.png) Turnout left 45°
- ![](../assets/images/board/tiles/board_tile.rail.turnout_left_90.png) Turnout left 90°
- ![](../assets/images/board/tiles/board_tile.rail.turnout_left_curved.png) Turnout left curved
- ![](../assets/images/board/tiles/board_tile.rail.turnout_right_45.png) Turnout right 45°
- ![](../assets/images/board/tiles/board_tile.rail.turnout_right_90.png) Turnout right 90°
- ![](../assets/images/board/tiles/board_tile.rail.turnout_right_curved.png) Turnout right curved
- ![](../assets/images/board/tiles/board_tile.rail.turnout_wye.png) Turnout wye
- ![](../assets/images/board/tiles/board_tile.rail.turnout_3way.png) Turnout 3-way
- ![](../assets/images/board/tiles/board_tile.rail.turnout_singleslip.png) Single slip
- ![](../assets/images/board/tiles/board_tile.rail.turnout_doubleslip.png) Double slip
- ![](../assets/images/board/tiles/board_tile.rail.block.png) Block
- ![](../assets/images/board/tiles/board_tile.rail.sensor.png) Sensor
- ![](../assets/images/board/tiles/board_tile.rail.signal_2_aspect.png) Signal (2 aspects)
- ![](../assets/images/board/tiles/board_tile.rail.signal_3_aspect.png) Signal (3 aspects)

Datei anzeigen

@ -1,4 +1,4 @@
# Decoder function {#decoder-function}
# Decoder function
## Id
@ -13,7 +13,7 @@ TODO
TODO
## Type {#decoder-function-type}
## Type
Function type:
@ -23,7 +23,7 @@ Function type:
- **AlwaysOff**: Forces a function to be off.
- **AlwaysOn**: Forces a function to be on.
## Function {#decoder-function-function}
## Function
Function function:

Datei anzeigen

@ -0,0 +1,625 @@
# Messages
Each log messages has its own unique code, it starts with a letter which indicates the level:
| Letter | Level | Description |
|--------|-----------------------|-----------------------------------------------------|
| **F** | [Fatal](#fatal) | |
| **C** | [Critical](#critical) | Critical messages indicate that action is required. |
| **E** | [Error](#error) | |
| **W** | [Warning](#warning) | |
| **N** | [Notice](#notice) | |
| **I** | [Info](#info) | |
| **D** | [Debug](#debug) | |
And a four digit number which indicates the message, the numeric range is divided into different categories for quickly indentifing the source of the message, see below:
| Numbers | Category |
|-------------|-----------------------------|
| 1000 … 1999 | Traintastic core components |
| 2000 … 2999 | Hardware interfacing |
| 3000 … 3999 | Trains and rail vehicles |
| 4000 … 8999 | *unused* |
| 9000 … 9999 | Lua scripting |
## Fatal {#fatal}
TODO
### F1001: Opening TCP socket failed (*reason*) {#f1001}
TODO
### F1002: TCP socket address reuse failed (*reason*) {#f1002}
TODO
### F1003: Binding TCP socket failed (*reason*) {#f1003}
TODO
### F1004: TCP socket listen failed (*reason*) {#f1004}
TODO
### F1005: Opening UDP socket failed (*reason*) {#f1005}
TODO
### F1006: UDP socket address reuse failed (*reason*) {#f1006}
TODO
### F1007: Binding udp socket failed (*reason*) {#f1007}
TODO
### F9001: Creating Lua state failed {#f9001}
TODO
### F9002: Running script failed (*reason*) {#f9002}
TODO
### F9003: Calling function failed (*reason*) {#f9003}
TODO
### F9999: *message* {#f9999}
Custom fatal message generated by a [Lua script](../advanced/scripting-basics.md).
## Critical {#critical}
Critical messages indicate that action is required.
### C1001: Loading world failed (*reason*) {#c1001}
TODO
### C1002: Creating client failed (*reason*) {#c1002}
TODO
### C1003: Can't write to settings file (*reason*) {#c1003}
TODO
### C1004: Reading world failed (*reason*) (*filename*) {#c1004}
TODO
### C1005: Saving world failed (*reason*) {#c1005}
TODO
### C1006: Creating world backup failed (*reason*) {#c1006}
TODO
### C1007: Creating world backup directory failed (*reason*) {#c1007}
TODO
### C1008: Creating backup directory failed (*reason*) {#c1008}
TODO
### C1009: Creating settings backup failed (*reason*) {#c1009}
TODO
### C1010: Exporting world failed (*reason*) {#c1010}
TODO
### C1011: Importing world failed (*reason*) {#c1011}
TODO
### C1012: Unknown class '*class id*', can't recreate object '*object id*' {#c1012}
TODO
### C1013: Can't load world saved with newer version, requires at least: Traintastic server *version* {#c1013}
**Cause:** The world is saved with a newer version of Traintastic server than currently is running.
**Solution:** Update Traintastic server (and client) to at least *version*.
### C2001: Address already used at #*object* {#c2001}
TODO
### C2002: DCC++ only supports the DCC protocol {#c2002}
**Cause:** The selected decoder protocol isn't supported by the DCC++ command station.
**Solution:** Change the decoder protocol to *DCC* or *Auto*.
### C2003: DCC++ doesn't support DCC long addresses below 128 {#c2003}
**Cause:** The DCC++ command station considers all addresses below 128 a DCC short address.
**Solution:** Change the locomotive decoder address.
### C2004: Can't get free slot {#c2004}
TODO
### C9999: *message* {#c9999}
Custom critical message generated by a [Lua script](../advanced/scripting-basics.md).
## Error {#error}
TODO
### E1001: Invalid world UUID: *uuid* {#e1001}
TODO
### E1002: World *uuid* doesn't exist {#e1002}
TODO
### E1003: UDP receive error (*reason*) {#e1003}
TODO
### E1004: TCP accept error (*reason*) {#e1004}
TODO
### E1005: Socket shutdown failed (*reason*) {#e1005}
TODO
### E1006: Socket write failed (*reason*) {#e1006}
TODO
### E1007: Socket read failed (*reason*) {#e1007}
TODO
### E1008: Socket acceptor cancel failed (*reason*) {#e1008}
TODO
### E2001: Serial write failed (*reason*) {#e2001}
TODO
### E2002: Serial read failed (*reason*) {#e2002}
TODO
### E2003: Make address failed (*reason*) {#e2003}
TODO
### E2004: Socket open failed (*reason*) {#e2004}
TODO
### E2005: Socket connect failed (*reason*) {#e2005}
TODO
### E2006: Socket bind failed (*reason*) {#e2006}
TODO
### E2007: Socket write failed (*reason*) {#e2007}
TODO
### E2008: Socket read failed (*reason*) {#e2008}
TODO
### E2009: Socket receive failed (*reason*) {#e2009}
TODO
### E2010: Serial port open failed (*reason*) {#e2010}
TODO
### E2011: Socket send failed (*reason*) {#e2011}
TODO
### E2012: Function number already in use {#e2012}
TODO
### E2013: Serial port set baudrate failed (*reason*) {#e2013}
TODO
### E2014: Serial port set data bits failed (*reason*) {#e2014}
TODO
### E2015: Serial port set stop bits failed (*reason*) {#e2015}
TODO
### E2016: Serial port set parity failed (*reason*) {#e2016}
TODO
### E2017: Serial port set flow control failed (*reason*) {#e2017}
TODO
### E2018: Timeout, no echo within *number*ms {#e2018}
TODO
### E2019: Timeout, no response within *number*ms {#e2019}
TODO
### E2020: Total number of modules may not exceed *number* {#e2020}
**Cause:** The maximum number of S88 modules connected to the HSI-88 may not exceed *number*, this is a hardware limitation.
**Solution:** Reduce the number of modules, the sum of *left* + *middle* + *right* must be equal or less than *number*.
### E9001: *error* (During execution of *name* event handler) {#e9001}
TODO
### E9999: *message* {#e9999}
Custom error message generated by a [Lua script](../advanced/scripting-basics.md).
## Warning {#warning}
TODO
### W1001: Discovery disabled, only allowed on port *number* {#w1001}
TODO
### W1002: Setting *name* doesnt exist {#w1002}
TODO
### W1003: Reading world *filename* failed (libarchive error *code*: *reason*) {#w1003}
TODO
### W2001: Received malformed data dropped *number* bytes {#w2001}
TODO
### W2002: Command station doesn't support functions above F*number* {#w2002}
**Cause:** The command station or interface can't control these function, e.g. due hardware or protocol limitations.
**Solution:** Check if command station is setup properly, some command stations have options which specify how to control additional functions.
If not, remapping decoder functions or using a different command station is the only solution.
### W2003: Command station doesn't support *number* speed steps, using *number* {#w2003}
**Cause:** The command station or interface can't control decoders using *number* speed steps, e.g. due hardware or protocol limitations.
**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*.
### W2006: Command station does not support loco slot *slot*
Slot *slot* can't be used with the connected command station, reduce the LocoNet *Locomotive slots* value.
### W2007: Command station does not support the fast clock slot
Fast clock can't be used with the connected command station, disable the LocoNet *Fast clock sync enabled* setting.
### W9999: *message* {#w9999}
Custom warning message generated by a [Lua script](../advanced/scripting-basics.md).
## Notice {#notice}
TODO
### N1001: Received signal: *name* {#n1001}
TODO
### N1002: Created new world {#n1002}
TODO
### N1003: Restaring {#n1003}
TODO
### N1004: Shutting down {#n1004}
TODO
### N1005: Discovery enabled {#n1005}
TODO
### N1006: Discovery disabled {#n1006}
TODO
### N1007: Listening at *address*:*port* {#n1007}
TODO
### N1008: Loaded settings {#n1008}
TODO
### N1009: Saved settings {#n1009}
TODO
### N1010: Edit mode: enabled {#n1010}
TODO
### N1011: Edit mode: disabled {#n1011}
TODO
### N1012: Communication: enabled {#n1012}
TODO
### N1013: Communication: disabled {#n1013}
TODO
### N1014: Power: on {#n1014}
TODO
### N1015: Power: off {#n1015}
TODO
### N1016: Running {#n1016}
TODO
### N1017: Stopped {#n1017}
TODO
### N1018: Mute: enabled {#n1018}
TODO
### N1019: Mute: disabled {#n1019}
TODO
### N1020: Smoke: enabled {#n1020}
TODO
### N1021: Smoke: disabled {#n1021}
TODO
### N1022: Saved world: *name* {#n1022}
TODO
### N1023: Simulation: disabled {#n1023}
TODO
### N1024: Simulation: enabled {#n1024}
TODO
### N1025: Exported world successfully {#n1025}
TODO
### N1026: Imported world successfully {#n1026}
TODO
### N2001: Simulation not supported {#n2001}
TODO
### N2002: No response from LNCV module *id* with address *address* {#n2002}
TODO
### N2003: Stopped sending fast clock sync {#n2003}
A fast clock sync message is no longer sent periodically, see [W2007](#w2007).
### N9001: Starting script {#n9001}
TODO
### N9999: *message* {#n9999}
Custom notice message generated by a [Lua script](../advanced/scripting-basics.md).
## Info {#info}
Informational messages
### I1001: Traintastic v*version* *codename* {#i1001}
TODO
### I1002: Settings file not found, using defaults {#i1002}
TODO
### I1003: Client connected {#i1003}
TODO
### I1004: Connection lost {#i1004}
TODO
### I1005: Building world index {#i1005}
TODO
### I1006: *boost version* {#i1006}
Version information about used boost library, e.g. *boost 1.71.0*.
### I1007: *nlohmann::json version* {#i1007}
Version information about used nlohmann::json library, e.g. *nlohmann::json 3.10.5*.
### I1008: *archive version* {#i1008}
Version information about used archive library, e.g. *libarchive 3.4.0 zlib/1.2.11 liblzma/5.2.4 bz2lib/1.0.8 liblz4/1.9.2 libzstd/1.4.4*.
### I2001: Unknown loco address: *address* {#i2001}
TODO
### I2002: Hardware type: *type* {#i2002}
TODO
### I2003: Firmware version: *version* {#i2003}
TODO
### I2004: HSI-88: *info* {#i2004}
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
### I9002: *lua version* {#i9002}
Version information about used Lua scripting engine, e.g. *Lua 5.3.3 Copyright (C) 1994-2016 Lua.org, PUC-Rio*.
### I9999: *message* {#i9999}
Custom info message generated by a [Lua script](../advanced/scripting-basics.md).
## Debug {#debug}
TODO
### D2001: TX: *data* {#d2001}
TODO
### D2002: RX: *data* {#d2002}
TODO
### D2003: Unknown xHeader 0x*value* {#d2003}
TODO
### D2004: *source* TX: *data* {#d2004}
TODO
### D2005: *source* RX: *data* {#d2005}
TODO
### D2006: Unknown message: *number* {#d2006}
TODO
### D2007: Input *number* = *value* {#d2007}
TODO
### D2008: Output *number* = *value* {#d2008}
TODO
### D2009: Slot *number* = *address* {#d2009}
TODO
### D2010: Slot *number* = Free {#d2010}
TODO
### D9999: *message* {#d9999}
Custom debug message generated by a [Lua script](../advanced/scripting-basics.md).

63
manual/docs/en/wip/trains.md Normale Datei
Datei anzeigen

@ -0,0 +1,63 @@
# Trains
In Traintastic, **only trains** can be directly controlled. Trains are composed of one or more vehicles, and understanding how trains and vehicles interact is crucial for setting up and operating a layout effectively.
## What is a Train?
A **train** in Traintastic is a logical grouping of vehicles that move together. A train can include:
- **Powered vehicles** such as locomotives or self-propelled railcars.
- **Unpowered vehicles** such as wagons or coaches.
Trains are **not static**: they can be created, modified, activated, deactivated, and reused dynamically.
## Vehicles vs Trains
Vehicles are managed **independently** from trains. This means:
- Vehicles are defined separately in the system.
- A **single vehicle** can be a part of **multiple train definitions**.
- A train is essentially a list of vehicle references, not a container that owns the vehicles.
This separation allows flexibility, such as creating alternate train consists using the same locomotive or rolling stock.
## Powered Vehicles
A train can include **one or more powered vehicles**. However, in order for a train to be activated and controlled, **at least one powered vehicle** is required.
Unpowered trains (e.g. a consist of only wagons) **cannot be activated** and will remain inactive in the system.
## Train Activation
Before a train can be controlled, it must be **activated**. Activation ensures that:
- None of its vehicles are already part of another **active** train.
- The train has at least one **powered vehicle**.
A vehicle can only belong to **one active train at a time**. Attempting to activate a train that includes a vehicle already in use will result in an error.
## Throttle Control
To control a train, it must be **acquired** by a **throttle**. A throttle provides the interface to:
- Set speed and direction
- Issue emergency stops
- Monitor train state
Important rules around throttle control:
- A train can have **zero or one active throttle** at any given time.
- A throttle can **steal control** from another throttle that currently holds it.
- Throttle control is required for manual or script-based operation.
## Throttle Types
Traintastic supports various throttle types, allowing both manual and automated control:
- **Client throttle**: The primary throttle used in the Traintastic client application.
- **Web throttle**: A web-based interface accessible via browsers.
- **Hardware throttles**: External devices like those using the **WiThrottle** protocol.
- **Lua script throttle**: A programmable throttle controlled by custom Lua scripts for automation and logic.
These different throttle types can coexist, and you can choose the one that best fits your control style or automation requirements.

Datei anzeigen

@ -1,4 +1,4 @@
# Zones {#zones}
# Zones
Zones allow you to apply common rules or restrictions to a group of blocks.
For example, you can create a **Staging zone** that mutes trains, or a **Shunting zone** with a speed limit.
@ -6,7 +6,7 @@ For example, you can create a **Staging zone** that mutes trains, or a **Shuntin
- A block can belong to multiple zones.
- A zone can include any blocks, even if they are not physically adjacent.
## Zone list {#zone-list}
## Zone list
Open the zone list from the main menu: *Objects**Zones*.
From here you can manage all zones in your world:
@ -15,17 +15,17 @@ From here you can manage all zones in your world:
- **Remove a zone** — Select a zone and click the **–** button to delete it.
- **Edit a zone** — Double-click a zone in the list or use the **pencil** button to open its properties.
## Zone properties {#zone-properties}
## Zone properties
Each zone has a set of properties that define its behavior.
### Identification
- **Name** — A short description of the zone, e.g. *Shunting Area* or *Staging Area*.
- **ID** — Unique identifier of the zone.
- Must be unique among all objects.
- Used for accessing the zone from the [Lua scripting engine](lua.md).
- Must start with a letter (`a–z`).
- May only contain lowercase letters (`a–z`), digits (`0–9`), and underscores (`_`).
- Must be unique among all objects.
- Used for accessing the zone from the [Lua scripting engine](../advanced/scripting-basics.md).
- Must start with a letter (`a–z`).
- May only contain lowercase letters (`a–z`), digits (`0–9`), and underscores (`_`).
### Block membership
- **Blocks** — List of blocks included in the zone.

599
manual/buildluadoc.py → manual/luadoc.py Ausführbare Datei → Normale Datei
Datei anzeigen

@ -1,5 +1,3 @@
#!/usr/bin/env python3
import sys
import string
import os
@ -9,23 +7,22 @@ import json
import operator
import shutil
import datetime
from traintasticmanualbuilder.utils import highlight_lua
class LuaDoc:
"""
Documentation genrator for Traintastic's builtin Lua scripting language
Documentation generator for Traintastic's builtin Lua scripting language
"""
DEFAULT_LANGUAGE = 'en-us'
FILENAME_INDEX = 'index.html'
FILENAME_GLOBALS = 'globals.html'
FILENAME_PV = 'pv.html'
FILENAME_ENUM = 'enum.html'
FILENAME_SET = 'set.html'
FILENAME_OBJECT = 'object.html'
FILENAME_EXAMPLE = 'example.html'
FILENAME_INDEX_AZ = 'index-az.html'
FILENAME_INDEX = 'index.md'
FILENAME_GLOBALS = 'globals.md'
FILENAME_PV = 'pv.md'
FILENAME_ENUM = 'enum.md'
FILENAME_SET = 'set.md'
FILENAME_OBJECT = 'object/index.md'
FILENAME_EXAMPLES = 'examples.md'
FILENAME_INDEX_AZ = 'index-az.md'
missing_terms = []
version = None
@ -46,15 +43,15 @@ class LuaDoc:
self._terms = LuaDoc._load_terms(language)
self.missing_terms = []
def _get_term(self, term: str) -> str:
def _get_term(self, term: str, optional: bool = False) -> str:
if term not in self._terms:
if optional:
return None
if term not in self.missing_terms:
self.missing_terms.append(term)
return '<span style="color:red">$' + term + '$</span>'
definition = self._terms[term]
definition = re.sub(r'`(.+?)`', r'<code>\1</code>', definition)
definition = re.sub(r'\[([^\]]+)]\(([^\)]+)\)', r'<a href="\2">\1</a>', definition)
definition = re.sub(r'{ref:([a-z0-9_\.]+?)(|#[a-z0-9_]+)(|\|.+?)}', self._ref_link, definition)
return definition
@ -93,29 +90,42 @@ class LuaDoc:
id = id.removeprefix('enum.')
for enum in self._enums:
if enum['lua_name'] == id:
return '<a href="' + enum['filename'] + fragment + '">' + (self._get_term(enum['name']) if title == '' else title) + '</a>'
return '[' + (self._get_term(enum['name']) if title == '' else title) + '](' + enum['filename'] + fragment + ')'
elif id.startswith('set.'):
id = id.removeprefix('set.')
for set in self._sets:
if set['lua_name'] == id:
return '<a href="' + set['filename'] + fragment + '">' + (self._get_term(set['name']) if title == '' else title) + '</a>'
return '[' + (self._get_term(set['name']) if title == '' else title) + '](' + set['filename'] + fragment + ')'
elif id.startswith('object.'):
for object in self._objects:
if object['lua_name'] == id:
return '<a href="' + object['filename'] + fragment + '">' + (self._get_term(object['name']) if title == '' else title) + '</a>'
return '[' + (self._get_term(object['name']) if title == '' else title) + '](' + object['filename'] + fragment + ')'
elif id == 'globals':
return '<a href="' + self.FILENAME_GLOBALS + fragment + '">' + (self._get_term('globals:title') if title == '' else title) + '</a>'
return '[' + (self._get_term('globals:title') if title == '' else title) + '](' + self.FILENAME_GLOBALS + fragment + ')'
elif id == 'enum':
return '<a href="' + self.FILENAME_ENUM + fragment + '">' + (self._get_term('enum:title') if title == '' else title) + '</a>'
return '[' + (self._get_term('enum:title') if title == '' else title) + '](' + self.FILENAME_ENUM + fragment + ')'
elif id == 'set':
return '<a href="' + self.FILENAME_SET + fragment + '">' + (self._get_term('set:title') if title == '' else title) + '</a>'
return '[' + (self._get_term('set:title') if title == '' else title) + '](' + self.FILENAME_SET + fragment + ')'
elif id == 'object':
return '<a href="' + self.FILENAME_OBJECT + fragment + '">' + (self._get_term('object:title') if title == '' else title) + '</a>'
return '[' + (self._get_term('object:title') if title == '' else title) + '](' + self.FILENAME_OBJECT + fragment + ')'
elif id == 'pv':
return '<a href="' + self.FILENAME_PV + fragment + '">' + (self._get_term('pv:title') if title == '' else title) + '</a>'
return '[' + (self._get_term('pv:title') if title == '' else title) + '](' + self.FILENAME_PV + fragment + ')'
return '<span style="color:red">' + m.group(0) + '</span>'
def _fix_links(md: str, cur_dir: str):
if cur_dir != '':
md = re.sub(r'\]\(((enum|set|object)/[^\)]+)\)', lambda m : LuaDoc._fix_link_helper(m, cur_dir), md)
return md
def _fix_link_helper(m: re.Match, cur_dir: str):
link = m[1]
if link.startswith(cur_dir + '/'):
link = link[len(cur_dir) + 1:]
else:
link = '../' + link
return '](' + link + ')'
def _load_data(items: list, filename: str) -> list:
data = json.loads(LuaDoc._read_file(filename))
@ -151,6 +161,7 @@ class LuaDoc:
def _write_file(filename: str, contents: str) -> None:
os.makedirs(os.path.dirname(filename), mode=0o755, exist_ok=True)
with codecs.open(filename, 'w', 'utf-8') as f:
f.write('[//]: # (This file is generated by luadoc.py, DO NOT EDIT)' + os.linesep + os.linesep)
f.write(contents)
def _copy_file(src: str, dst: str) -> None:
@ -201,7 +212,7 @@ class LuaDoc:
items = filter(lambda item: 'lua_name' in item, items)
enums.append({
'filename': 'enum.' + info.group(1).lower() + '.html',
'filename': os.path.join('enum', info.group(1).lower() + '.md'),
'name': 'enum.' + info.group(1).lower() + ':title',
'cpp_name': cpp_name,
'lua_name': info.group(1),
@ -232,7 +243,7 @@ class LuaDoc:
item['lua_name'] = m.group(1).upper()
sets.append({
'filename': 'set.' + info.group(1).lower() + '.html',
'filename': os.path.join('set', info.group(1).lower() + '.md'),
'name': 'set.' + info.group(1).lower() + ':title',
'cpp_name': cpp_name,
'lua_name': info.group(1),
@ -252,7 +263,7 @@ class LuaDoc:
for item_name in re.findall(r'"([a-z0-9_]+)"', args):
items.append(item_name)
libs[name] = {
'filename': name + '.html',
'filename': name + '.md',
'name': name + ':title',
'lua_name': name,
'items': items}
@ -264,7 +275,7 @@ class LuaDoc:
for item_name in re.findall(r'static\s+int\s+([a-z]+)\(\s*lua_State\s*\*\s*L\s*\)', log_hpp):
items.append(item_name)
libs[name] = {
'filename': name + '.html',
'filename': name + '.md',
'name': name + ':title',
'lua_name': name,
'items': items}
@ -273,7 +284,7 @@ class LuaDoc:
name = 'class'
items = []
class_hpp = LuaDoc._read_file(os.path.join(project_root, 'server', 'src', 'lua', 'class.hpp'))
for item_name in re.findall(r'static\s+int\s+([a-z][a-zA-Z_]+)\(\s*lua_State\s*\*\s*L\s*\)', class_hpp):
for item_name in re.findall(r'static\s+int\s+([a-zA-Z]+)\(\s*lua_State\s*\*\s*L\s*\)', class_hpp):
if item_name == 'getClass':
item_name = 'get'
items.append(item_name)
@ -283,7 +294,7 @@ class LuaDoc:
item_name = 'get'
items.append(item_name)
libs[name] = {
'filename': name + '.html',
'filename': name + '.md',
'name': name + ':title',
'lua_name': name,
'items': items}
@ -341,7 +352,7 @@ class LuaDoc:
items = LuaDoc._find_object_items(cpp_classes, cpp_name)
objects.append({
'filename': lua_name + '.html',
'filename': os.path.join('object', cpp_name.lower() + '.md'),
'lua_name': lua_name,
'name': lua_name + ':title',
'cpp_name': cpp_name,
@ -427,7 +438,7 @@ class LuaDoc:
examples[id] = {
'id': id,
'name': 'example.' + id + ':title',
'filename': 'example.' + id + '.html',
'filename': os.path.join('example', id + '.md'),
'code': LuaDoc._read_file(os.path.join(root,file))}
return examples
@ -448,292 +459,233 @@ class LuaDoc:
# reset missing terms
self.missing_terms = []
# create output dir
os.makedirs(args.output_dir, mode=0o755, exist_ok=True)
# css
LuaDoc._copy_file(os.path.join(os.path.dirname(__file__), 'traintasticmanual', 'css', 'pure-min.css'), os.path.join(output_dir, 'css'))
LuaDoc._copy_file(os.path.join(os.path.dirname(__file__), 'traintasticmanual', 'css', 'traintasticmanual.css'), os.path.join(output_dir, 'css'))
nav = [{'title': self._get_term('index:nav'), 'href': LuaDoc.FILENAME_INDEX}]
self._build_index(output_dir)
self._build_globals(output_dir, nav)
self._build_pv(output_dir, nav)
self._build_enums(output_dir, nav)
self._build_sets(output_dir, nav)
self._build_globals(output_dir)
self._build_pv(output_dir)
self._build_enums(output_dir)
self._build_sets(output_dir)
for _, lib in self._libs.items():
self._build_lib(output_dir, nav, lib)
self._build_objects(output_dir, nav)
self._build_examples(output_dir, nav)
self._build_index_az(output_dir, nav)
self._build_lib(output_dir, lib)
self._build_objects(output_dir)
self._build_examples(output_dir)
self._build_index_az(output_dir)
def _build_items_html(self, items: list, term_prefix: str, lua_prefix: str = '') -> str:
html = ''
def _build_items_md(self, items: list, term_prefix: str, lua_prefix: str = '') -> str:
md = ''
items = sorted(items, key=operator.itemgetter('lua_name'))
constants = [item for item in items if item['type'] == 'constant']
if len(constants) > 0:
html += '<h2 id="constants">' + self._get_term('constants') + '</h2>' + os.linesep
html += '<dl>' + os.linesep
md += '## ' + self._get_term('constants') + os.linesep + os.linesep
for item in constants:
item_term_prefix = item['term_prefix'] if 'term_prefix' in item else term_prefix
html += ' <dt id="' + item['lua_name'] + '"><code>' + lua_prefix + item['lua_name'] + '</code>'
if 'since' in item:
html += ' <span class="badge badge-since">&ge; ' + item['since'] + '</span>'
if 'is_lua_builtin' in item and item['is_lua_builtin']:
html += ' <span class="badge badge-lua">Lua</span>'
html += '</dt>' + os.linesep
html += ' <dd>' + self._get_term(item_term_prefix + item['lua_name'].lower() + ':description') + '</dd>' + os.linesep
html += '</dl>' + os.linesep
md += '### `' + lua_prefix + item['lua_name'] + '`' + os.linesep
md += self._get_term(item_term_prefix + item['lua_name'].lower() + ':description') + os.linesep + os.linesep
libraries = [item for item in items if item['type'] == 'library']
if len(libraries) > 0:
html += '<h2 id="libraries">' + self._get_term('libraries') + '</h2>' + os.linesep
html += '<dl>' + os.linesep
md += '## ' + self._get_term('libraries') + os.linesep + os.linesep
for item in libraries:
html += ' <dt id="' + item['lua_name'] + '"><code>' + lua_prefix + item['lua_name'] + '</code>'
if 'since' in item:
html += ' <span class="badge badge-since">&ge; ' + item['since'] + '</span>'
if 'is_lua_builtin' in item and item['is_lua_builtin']:
html += ' <span class="badge badge-lua">Lua</span>'
html += '</dt>' + os.linesep
md += '### `' + lua_prefix + item['lua_name'] + '`' + os.linesep
if item['lua_name'] == 'enum':
href = LuaDoc.FILENAME_ENUM
elif item['lua_name'] == 'set':
href = LuaDoc.FILENAME_SET
else:
href = self._libs[item['lua_name']]['filename']
html += ' <dd><a href="' + href + '">' + self._get_term(item['lua_name'] + ':title') + '</a></dd>' + os.linesep
html += '</dl>' + os.linesep
md += '[' + self._get_term(item['lua_name'] + ':title') + '](' + href + ')' + os.linesep + os.linesep
objects = [item for item in items if item['type'] == 'object']
if len(objects) > 0:
html += '<h2 id="objects">' + self._get_term('objects') + '</h2>' + os.linesep
html += '<dl>' + os.linesep
md += '## ' + self._get_term('objects') + os.linesep + os.linesep
for item in objects:
item_term_prefix = item['term_prefix'] if 'term_prefix' in item else term_prefix
html += ' <dt id="' + item['lua_name'] + '"><code>' + lua_prefix + item['lua_name'] + '</code>'
if 'since' in item:
html += ' <span class="badge badge-since">&ge; ' + item['since'] + '</span>'
html += '</dt>' + os.linesep
html += ' <dd>' + self._get_term(item_term_prefix + item['lua_name'].lower() + ':description') + '</dd>' + os.linesep
html += '</dl>' + os.linesep
md += '### `' + lua_prefix + item['lua_name'] + '`' + os.linesep
md += self._get_term(item_term_prefix + item['lua_name'].lower() + ':description') + os.linesep + os.linesep
properties = [item for item in items if item['type'] == 'property']
if len(properties) > 0:
html += '<h2 id="properties">' + self._get_term('properties') + '</h2>' + os.linesep
html += '<dl>' + os.linesep
md += '## ' + self._get_term('properties') + os.linesep + os.linesep
for item in properties:
item_term_prefix = item['term_prefix'] if 'term_prefix' in item else term_prefix
html += ' <dt id="' + item['lua_name'] + '"><code>' + lua_prefix + item['lua_name'] + '</code>'
if 'since' in item:
html += ' <span class="badge badge-since">&ge; ' + item['since'] + '</span>'
html += '</dt>' + os.linesep
html += ' <dd>' + self._get_term(item_term_prefix + item['lua_name'].lower() + ':description') + '</dd>' + os.linesep
html += '</dl>' + os.linesep
md += '### `' + lua_prefix + item['lua_name'] + '`' + os.linesep
md += ' ' + self._get_term(item_term_prefix + item['lua_name'].lower() + ':description') + os.linesep + os.linesep
for function_or_method in ['function', 'method']:
functions = [item for item in items if item['type'] == function_or_method]
if len(functions) > 0:
html += '<h2 id="' + function_or_method + 's">' + self._get_term(function_or_method + 's') + '</h2>'
md += '## ' + self._get_term(function_or_method + 's') + os.linesep + os.linesep
for item in functions:
item_term_prefix = item['term_prefix'] if 'term_prefix' in item else term_prefix
html += '<h3 id="' + item['lua_name'] + '"><code>'
md += '### `' + lua_prefix
if item['lua_name'] == '__get':
html += '[]'
md += '['
else:
html += item['lua_name']
if 'since' in item:
html += ' <span class="badge badge-since">&ge; ' + item['since'] + '</span>'
if 'is_lua_builtin' in item and item['is_lua_builtin']:
html += ' <span class="badge badge-lua">Lua</span>'
html += '</code></h3>' + os.linesep
html += '<code>' + lua_prefix
if item['lua_name'] == '__get':
html += '['
else:
html += item['lua_name'] + '('
md += item['lua_name'] + '('
optional = 0
for p in item['parameters']:
is_optional = 'optional' in p and p['optional']
is_first = p == item['parameters'][0]
if is_optional:
html += '[' if is_first else ' ['
md += '[' if is_first else ' ['
optional += 1
if not is_first:
html += ', '
html += p['name']
md += ', '
md += p['name']
if is_optional and 'default' in p:
html += ' = ' + str(p['default'])
html += ']' * optional
md += ' = ' + str(p['default'])
md += ']' * optional
if item['lua_name'] == '__get':
html += ']'
md += ']'
else:
html += ')'
html += '</code>' + os.linesep
md += ')'
md += '`' + os.linesep + os.linesep
html += '<p>' + self._get_term(item_term_prefix + item['lua_name'].lower() + ':description') + '</p>' + os.linesep
md += self._get_term(item_term_prefix + item['lua_name'].lower() + ':description') + os.linesep + os.linesep
if len(item['parameters']) > 0:
html += '<h4>' + self._get_term('parameters') + '</h4>' + os.linesep
html += '<dl>' + os.linesep
md += '**' + self._get_term('parameters') + ':**' + os.linesep + os.linesep
for param in item['parameters']:
html += ' <dt><code>' + param['name'] + '</code></dt>' + os.linesep
html += ' <dd>' + self._get_term(item_term_prefix + item['lua_name'] + '.parameter.' + param['name'] + ':description') + '</dd>' + os.linesep
html += '</dl>' + os.linesep
md += '- `' + param['name'] + '` ' + os.linesep
md += ' ' + self._get_term(item_term_prefix + item['lua_name'] + '.parameter.' + param['name'] + ':description') + os.linesep + os.linesep
if item['return_values'] > 0:
html += '<h4>' + self._get_term('return_values') + '</h4>' + os.linesep
html += '<p>' + self._get_term(item_term_prefix + item['lua_name'] + ':return_values') + '</p>' + os.linesep
md += '**' + self._get_term('return_values') + '** ' + os.linesep
md += self._get_term(item_term_prefix + item['lua_name'] + ':return_values') + os.linesep + os.linesep
if 'examples' in item and len(item['examples']) != 0:
html += '<h4>' + self._get_term('example' if len(item['examples']) == 1 else 'examples') + '</h4>' + os.linesep
md += '<h4>' + self._get_term('example' if len(item['examples']) == 1 else 'examples') + '</h4>' + os.linesep
for example in item['examples']:
html += '<pre lang="lua"><code>' + highlight_lua(example['code']) + '</code></pre>' + os.linesep
md += '```lua' + os.linesep + example['code'] + '```' + os.linesep + os.linesep + os.linesep
events = [item for item in items if item['type'] == 'event']
if len(events) > 0:
html += '<h2 id="events">' + self._get_term('events') + '</h2>'
md += '## ' + self._get_term('events') + os.linesep + os.linesep
if text := self._get_term(term_prefix.rstrip('.') + ':events_overview', optional=True):
md += text + os.linesep + os.linesep
for item in events:
item_term_prefix = item['term_prefix'] if 'term_prefix' in item else term_prefix
html += '<h3 id="' + item['lua_name'] + '"><code>' + item['lua_name'] + '</code>'
if 'since' in item:
html += ' <span class="badge badge-since">&ge; ' + item['since'] + '</span>'
html += '</h3>' + os.linesep
html += '<p>' + self._get_term(term_prefix + item['lua_name'].lower() + ':description') + '</p>' + os.linesep
md += '### `' + item['lua_name'] + '`' + os.linesep + os.linesep
md += self._get_term(term_prefix + item['lua_name'].lower() + ':description') + os.linesep + os.linesep
html += 'Handler: <code>function ('
md += '**Handler signature**' + os.linesep + os.linesep + '`function ('
for p in item['parameters']:
html += p['name'] + ', '
html += 'user_data)</code>'
md += p['name'] + ', '
md += 'user_data)`' + os.linesep + os.linesep
html += '<dl>' + os.linesep
md += '**' + self._get_term('arguments') + '**' + os.linesep + os.linesep
for param in item['parameters']:
html += ' <dt><code>' + param['name'] + '</code></dt>' + os.linesep
html += ' <dd>' + self._get_term(item_term_prefix + item['lua_name'] + '.parameter.' + param['name'] + ':description') + '</dd>' + os.linesep
html += ' <dt><code>user_data</code></dt>' + os.linesep
html += ' <dd>' + self._get_term('event.parameter.user_data:description') + '</dd>' + os.linesep
html += '</dl>' + os.linesep
md += '- `' + param['name'] + '` - ' + self._get_term(item_term_prefix + item['lua_name'] + '.parameter.' + param['name'] + ':description') + os.linesep + os.linesep
md += '- `user_data` - ' + self._get_term('event.parameter.user_data:description') + os.linesep + os.linesep
return html
return md
def _build_see_also_html(self, items: list):
def _build_see_also_md(self, items: list):
if len(items) == 0:
return ''
html = '<h2>' + self._get_term('see_also') + '</h2><ul>'
md = '## ' + self._get_term('see_also') + os.linesep + os.linesep
for item in items:
html += '<li>' + item + '</li>'
html += '</ul>'
return html
md += '- ' + item + os.linesep
md += os.linesep
return md
def _build_index(self, output_dir: str) -> None:
html = self._get_header(self._get_term('index:title'), [])
html = html.replace('<h1>', '<h1 class="title">')
md = '# ' + self._get_term('index:title') + os.linesep + os.linesep
if self.version is not None:
html += '<p class="center large">v' + self._version + '</p>'
html += '<p class="center dim small">' + datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + '</p>'
html += '<p>' + self._get_term('index:description') + '</p>' + os.linesep
html += '<ul>' + os.linesep
html += ' <li><a href="' + LuaDoc.FILENAME_GLOBALS + '">' + self._get_term('globals:title') + '</a></li>' + os.linesep
md += '<p class="center large">v' + self._version + os.linesep
md += '<p class="center dim small">' + datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + os.linesep
md += self._get_term('index:description') + os.linesep + os.linesep
md += '<ul>' + os.linesep
md += ' <li><a href="' + LuaDoc.FILENAME_GLOBALS + '">' + self._get_term('globals:title') + '</a></li>' + os.linesep
for k in sorted(list(self._libs.keys()) + ['enum', 'set']):
if k == 'enum':
html += ' <li><a href="' + LuaDoc.FILENAME_ENUM + '">' + self._get_term('enum:title') + '</a></li>' + os.linesep
md += ' <li><a href="' + LuaDoc.FILENAME_ENUM + '">' + self._get_term('enum:title') + '</a></li>' + os.linesep
elif k == 'set':
html += ' <li><a href="' + LuaDoc.FILENAME_SET + '">' + self._get_term('set:title') + '</a></li>' + os.linesep
md += ' <li><a href="' + LuaDoc.FILENAME_SET + '">' + self._get_term('set:title') + '</a></li>' + os.linesep
else:
lib = self._libs[k]
html += ' <li><a href="' + lib['filename'] + '">' + self._get_term(lib['name']) + '</a></li>' + os.linesep
html += ' <li><a href="' + LuaDoc.FILENAME_OBJECT + '">' + self._get_term('object:title') + '</a></li>' + os.linesep
html += ' <li><a href="' + LuaDoc.FILENAME_EXAMPLE + '">' + self._get_term('example:title') + '</a></li>' + os.linesep
html += ' <li><a href="' + LuaDoc.FILENAME_INDEX_AZ + '">' + self._get_term('index-az:title') + '</a></li>' + os.linesep
html += '</ul>' + os.linesep
html += '</div>' + os.linesep
LuaDoc._write_file(os.path.join(output_dir, LuaDoc.FILENAME_INDEX), html)
md += ' <li><a href="' + lib['filename'] + '">' + self._get_term(lib['name']) + '</a></li>' + os.linesep
md += ' <li><a href="' + LuaDoc.FILENAME_OBJECT + '">' + self._get_term('object:title') + '</a></li>' + os.linesep
md += ' <li><a href="' + LuaDoc.FILENAME_EXAMPLES + '">' + self._get_term('example:title') + '</a></li>' + os.linesep
md += ' <li><a href="' + LuaDoc.FILENAME_INDEX_AZ + '">' + self._get_term('index-az:title') + '</a></li>' + os.linesep
md += '</ul>' + os.linesep
md += '</div>' + os.linesep
LuaDoc._write_file(os.path.join(output_dir, LuaDoc.FILENAME_INDEX), md)
def _build_globals(self, output_dir: str, nav: list) -> None:
title = self._get_term('globals:title')
html = self._get_header(title, nav + [{'title': title, 'href': LuaDoc.FILENAME_GLOBALS}])
html += '<p>' + self._get_term('globals:description') + '</p>' + os.linesep
html += self._build_items_html(self._globals, 'globals.')
LuaDoc._write_file(os.path.join(output_dir, LuaDoc.FILENAME_GLOBALS), self._add_toc(html))
def _build_globals(self, output_dir: str) -> None:
md = '# ' + self._get_term('globals:title') + os.linesep + os.linesep
md += self._get_term('globals:description') + os.linesep + os.linesep
md += self._build_items_md(self._globals, 'globals.')
LuaDoc._write_file(os.path.join(output_dir, LuaDoc.FILENAME_GLOBALS), md)
def _build_pv(self, output_dir: str, nav: list) -> None:
title = self._get_term('pv:title')
html = self._get_header(title, nav + [{'title': title, 'href': LuaDoc.FILENAME_PV}])
html += '<p>' + self._get_term('pv:paragraph_1') + '</p>' + os.linesep
html += '<p>' + self._get_term('pv:paragraph_2') + '</p>' + os.linesep
def _build_pv(self, output_dir: str) -> None:
md = '# ' + self._get_term('pv:title') + os.linesep + os.linesep
md += LuaDoc._md_paragraph(self._get_term('pv:paragraph_1'))
md += LuaDoc._md_paragraph(self._get_term('pv:paragraph_2'))
html += '<h2 id="storing">' + self._get_term('pv.storing:title') + '</h2>' + os.linesep
html += '<p>' + self._get_term('pv.storing:paragraph_1') + '</p>' + os.linesep
html += '<pre lang="lua"><code>' + highlight_lua(LuaDoc._read_file(os.path.join(self._project_root, 'manual', 'luadoc', 'example', 'pv', 'storingpersistentdata.lua'))) + '</code></pre>'
md += '## ' + self._get_term('pv.storing:title') + os.linesep + os.linesep
md += LuaDoc._md_paragraph(self._get_term('pv.storing:paragraph_1'))
md += LuaDoc._md_lua_code(LuaDoc._read_file(os.path.join(self._project_root, 'manual', 'luadoc', 'example', 'pv', 'storingpersistentdata.lua')))
md += '!!! note' + os.linesep
md += ' ' + LuaDoc._md_paragraph(self._get_term('pv:storing_note'))
html += '<h2 id="retrieving">' + self._get_term('pv.retrieving:title') + '</h2>' + os.linesep
html += '<p>' + self._get_term('pv.retrieving:paragraph_1') + '</p>' + os.linesep
html += '<pre lang="lua"><code>' + highlight_lua(LuaDoc._read_file(os.path.join(self._project_root, 'manual', 'luadoc', 'example', 'pv', 'retrievingpersistentdata.lua'))) + '</code></pre>'
md += '## ' + self._get_term('pv.retrieving:title') + os.linesep + os.linesep
md += LuaDoc._md_paragraph(self._get_term('pv.retrieving:paragraph_1'))
md += LuaDoc._md_lua_code(LuaDoc._read_file(os.path.join(self._project_root, 'manual', 'luadoc', 'example', 'pv', 'retrievingpersistentdata.lua')))
html += '<h2 id="deleting">' + self._get_term('pv.deleting:title') + '</h2>' + os.linesep
html += '<p>' + self._get_term('pv.deleting:paragraph_1') + '</p>' + os.linesep
html += '<pre lang="lua"><code>' + highlight_lua(LuaDoc._read_file(os.path.join(self._project_root, 'manual', 'luadoc', 'example', 'pv', 'deletingpersistentdata.lua'))) + '</code></pre>'
md += '## ' + self._get_term('pv.deleting:title') + os.linesep + os.linesep
md += LuaDoc._md_paragraph(self._get_term('pv.deleting:paragraph_1'))
md += LuaDoc._md_lua_code(LuaDoc._read_file(os.path.join(self._project_root, 'manual', 'luadoc', 'example', 'pv', 'deletingpersistentdata.lua')))
html += '<h2 id="checking">' + self._get_term('pv.checking:title') + '</h2>' + os.linesep
html += '<p>' + self._get_term('pv.checking:paragraph_1') + '</p>' + os.linesep
html += '<pre lang="lua"><code>' + highlight_lua(LuaDoc._read_file(os.path.join(self._project_root, 'manual', 'luadoc', 'example', 'pv', 'checkingforpersistentdata.lua'))) + '</code></pre>'
md += '## ' + self._get_term('pv.checking:title') + os.linesep + os.linesep
md += LuaDoc._md_paragraph(self._get_term('pv.checking:paragraph_1'))
md += LuaDoc._md_lua_code(LuaDoc._read_file(os.path.join(self._project_root, 'manual', 'luadoc', 'example', 'pv', 'checkingforpersistentdata.lua')))
LuaDoc._write_file(os.path.join(output_dir, LuaDoc.FILENAME_PV), self._add_toc(html))
LuaDoc._write_file(os.path.join(output_dir, LuaDoc.FILENAME_PV), md)
def _build_enums(self, output_dir: str, nav: list) -> None:
title = self._get_term('enum:title')
nav_enums = nav + [{'title': title, 'href': LuaDoc.FILENAME_ENUM}]
html = self._get_header(title, nav_enums)
html += '<p>' + self._get_term('enum:description') + '</p>' + os.linesep
html += '<h2 id="constants">' + self._get_term('constants') + '</h2>' + os.linesep
html += '<dl>' + os.linesep
def _build_enums(self, output_dir: str) -> None:
md = '# ' + self._get_term('enum:title') + os.linesep + os.linesep
md += self._get_term('enum:description') + os.linesep + os.linesep
md += '## ' + self._get_term('constants') + os.linesep
md += '<dl>' + os.linesep
for enum in self._enums:
html += ' <dt id="' + enum['lua_name'] + '"><a href="' + enum['filename'] + '"><code>enum.' + enum['lua_name'] + '</code></a></dt>' + os.linesep
html += ' <dd>' + self._get_term(enum['name']) + '</dd>' + os.linesep
html += '</dl>' + os.linesep
html += self._get_footer()
LuaDoc._write_file(os.path.join(output_dir, LuaDoc.FILENAME_ENUM), self._add_toc(html))
md += ' <dt id="' + enum['lua_name'] + '"><a href="' + enum['filename'] + '"><code>enum.' + enum['lua_name'] + '</code></a></dt>' + os.linesep
md += ' <dd>' + self._get_term(enum['name']) + '</dd>' + os.linesep
md += '</dl>' + os.linesep
LuaDoc._write_file(os.path.join(output_dir, LuaDoc.FILENAME_ENUM), md)
for enum in self._enums:
self._build_lib(output_dir, nav_enums, enum, 'enum.')
self._build_lib(output_dir, enum, 'enum.')
def _build_sets(self, output_dir: str, nav: list) -> None:
title = self._get_term('set:title')
nav_sets = nav + [{'title': title, 'href': LuaDoc.FILENAME_SET}]
html = self._get_header(title, nav_sets)
html += '<p>' + self._get_term('set:description') + '</p>' + os.linesep
html += '<h2 id="constants">' + self._get_term('constants') + '</h2>' + os.linesep
html += '<dl>' + os.linesep
def _build_sets(self, output_dir: str) -> None:
md = '# ' + self._get_term('set:title') + os.linesep + os.linesep
md += self._get_term('set:description') + os.linesep + os.linesep
md += '## ' + self._get_term('constants') + os.linesep
md += '<dl>' + os.linesep
for set in self._sets:
html += ' <dt id="' + set['lua_name'] + '"><a href="' + set['filename'] + '"><code>' + set['lua_name'] + '</code></a></dt>' + os.linesep
html += ' <dd>' + self._get_term(set['name']) + '</dd>' + os.linesep
html += '</dl>' + os.linesep
html += self._get_footer()
LuaDoc._write_file(os.path.join(output_dir, LuaDoc.FILENAME_SET), self._add_toc(html))
md += ' <dt id="' + set['lua_name'] + '"><a href="' + set['filename'] + '"><code>' + set['lua_name'] + '</code></a></dt>' + os.linesep
md += ' <dd>' + self._get_term(set['name']) + '</dd>' + os.linesep
md += '</dl>' + os.linesep
LuaDoc._write_file(os.path.join(output_dir, LuaDoc.FILENAME_SET), md)
for set in self._sets:
self._build_lib(output_dir, nav_sets, set, 'set.')
self._build_lib(output_dir, set, 'set.')
def _build_lib(self, output_dir: str, nav: list, lib: dict, parent_lib: str = '') -> None:
title = self._get_term(lib['name'])
html = self._get_header(title, nav + [{'title': title, 'href': lib['filename']}])
html += '<p>' + self._get_term(parent_lib + lib['lua_name'] + ':description') + '</p>' + os.linesep
html += self._build_items_html(lib['items'], parent_lib + lib['lua_name'] + '.', parent_lib + lib['lua_name'] + '.')
def _build_lib(self, output_dir: str, lib: dict, parent_lib: str = '') -> None:
md = '# ' + self._get_term(lib['name']) + os.linesep + os.linesep
md += self._get_term(parent_lib + lib['lua_name'] + ':description') + os.linesep + os.linesep
md += self._build_items_md(lib['items'], parent_lib + lib['lua_name'] + '.', parent_lib + lib['lua_name'] + '.')
if 'see_also' in lib:
html += self._build_see_also_html(lib['see_also'])
LuaDoc._write_file(os.path.join(output_dir, lib['filename']), self._add_toc(html))
md += self._build_see_also_md(lib['see_also'])
LuaDoc._write_file(os.path.join(output_dir, lib['filename']), LuaDoc._fix_links(md, os.path.dirname(lib['filename'])))
def _build_objects(self, output_dir: str, nav: list) -> None:
title = self._get_term('object:title')
nav_objects = nav + [{'title': title, 'href': LuaDoc.FILENAME_OBJECT}]
html = self._get_header(title, nav_objects)
html += '<p>' + self._get_term('object:description') + '</p>' + os.linesep
def _build_objects(self, output_dir: str) -> None:
md = '# ' + self._get_term('object:title') + os.linesep + os.linesep
md += self._get_term('object:description') + os.linesep + os.linesep
items = []
for object in self._objects:
@ -753,58 +705,49 @@ class LuaDoc:
break
for category in sorted(categories.values(), key=operator.itemgetter('title')):
html += '<h2 id="' + key + '">' + category['title'] + '</h2>'
html += '<ul>' + os.linesep
md += '## ' + category['title'] + os.linesep
for item in sorted(category['items'], key=operator.itemgetter('title')):
html += ' <li><a href="' + item['href'] + '">' + item['title'] + '</a></li>' + os.linesep
html += '</ul>' + os.linesep
md += '- [' + item['title'] + '](' + item['href'] + ')' + os.linesep
md += os.linesep
if len(items) > 0:
html += '<h2>' + self._get_term('object.category.other:title') + '</h2>'
html += '<ul>' + os.linesep
md += '## ' + self._get_term('object.category.other:title') + os.linesep
for item in sorted(items, key=operator.itemgetter('title')):
html += ' <li><a href="' + item['href'] + '">' + item['title'] + '</a></li>' + os.linesep
html += '</ul>' + os.linesep
md += '- [' + item['title'] + '](' + item['href'] + ')' + os.linesep
md += os.linesep
html += self._get_footer()
LuaDoc._write_file(os.path.join(output_dir, LuaDoc.FILENAME_OBJECT), html)
LuaDoc._write_file(os.path.join(output_dir, LuaDoc.FILENAME_OBJECT), LuaDoc._fix_links(md, os.path.dirname(LuaDoc.FILENAME_OBJECT)))
for object in self._objects:
self._build_object(output_dir, nav_objects, object)
self._build_object(output_dir, object)
def _build_object(self, output_dir: str, nav: list, object: dict) -> None:
title = self._get_term(object['name'])
html = self._get_header(title, nav + [{'title': title, 'href': object['filename']}])
html += '<p>' + self._get_term(object['term_prefix'].rstrip('.') + ':description') + '</p>' + os.linesep
html += self._build_items_html(object['items'], object['term_prefix'])
LuaDoc._write_file(os.path.join(output_dir, object['filename']), self._add_toc(html))
def _build_object(self, output_dir: str, object: dict) -> None:
md = '# ' + self._get_term(object['name']) + os.linesep + os.linesep
md += self._get_term(object['term_prefix'].rstrip('.') + ':description') + os.linesep + os.linesep
md += self._build_items_md(object['items'], object['term_prefix'])
LuaDoc._write_file(os.path.join(output_dir, object['filename']), LuaDoc._fix_links(md, os.path.dirname(object['filename'])))
def _build_examples(self, output_dir: str, nav: list) -> None:
title = self._get_term('example:title')
nav_examples = nav + [{'title': title, 'href': LuaDoc.FILENAME_EXAMPLE}]
html = self._get_header(title, nav_examples)
html += '<p>' + self._get_term('example:description') + '</p>' + os.linesep
html += '<ul>' + os.linesep
def _build_examples(self, output_dir: str) -> None:
md = '# ' + self._get_term('example:title') + os.linesep + os.linesep
md += self._get_term('example:description') + os.linesep + os.linesep
items = []
for example in self._examples.values():
items.append({'href': example['filename'], 'title': self._get_term(example['name'])})
for item in sorted(items, key=operator.itemgetter('title')):
html += ' <li><a href="' + item['href'] + '">' + item['title'] + '</a></li>' + os.linesep
html += '</ul>' + os.linesep
html += self._get_footer()
LuaDoc._write_file(os.path.join(output_dir, LuaDoc.FILENAME_EXAMPLE), html)
md += '- [' + item['title'] + '](' + item['href'] + ')' + os.linesep
md += os.linesep
LuaDoc._write_file(os.path.join(output_dir, LuaDoc.FILENAME_EXAMPLES), md)
for example in self._examples.values():
self._build_example(output_dir, nav_examples, example)
self._build_example(output_dir, example)
def _build_example(self, output_dir: str, nav: list, example: dict) -> None:
title = self._get_term(example['name'])
html = self._get_header(title, nav + [{'title': title, 'href': example['filename']}])
html += '<p>' + self._get_term('example.' + example['id'] + ':description') + '</p>' + os.linesep
html += '<pre lang="lua"><code>' + highlight_lua(example['code']) + '</code></pre>'
LuaDoc._write_file(os.path.join(output_dir, example['filename']), html)
def _build_example(self, output_dir: str, example: dict) -> None:
md = '# ' + self._get_term(example['name']) + os.linesep + os.linesep
md += self._get_term('example.' + example['id'] + ':description') + os.linesep + os.linesep
md += LuaDoc._md_lua_code(example['code']) + os.linesep + os.linesep
LuaDoc._write_file(os.path.join(output_dir, example['filename']), md)
def _build_index_az(self, output_dir: str, nav: list) -> None:
def _build_index_az(self, output_dir: str) -> None:
alphabet = list(string.ascii_uppercase)
index = {}
for letter in alphabet:
@ -841,141 +784,35 @@ class LuaDoc:
if letter in index:
index[letter].append({'title': name, 'sub_title': '<code>object</code>', 'href': object['filename']})
title = self._get_term('index-az:title')
html = self._get_header(title, nav + [{'title': title, 'href': set['filename']}])
html += '<p>' + self._get_term('index-az:description') + '</p>' + os.linesep
html += '<ul class="index-az-nav">' + os.linesep
md = '# ' + self._get_term('index-az:title') + os.linesep + os.linesep
md += self._get_term('index-az:description') + os.linesep + os.linesep
md += '<ul class="index-az-nav">' + os.linesep
for letter in alphabet:
if len(index[letter]) != 0:
html += ' <li><a href="#' + letter + '">' + letter + '</a></li>' + os.linesep
md += ' <li><a href="#' + letter + '">' + letter + '</a></li>' + os.linesep
else:
html += ' <li class="dim">' + letter + '</li>' + os.linesep
md += ' <li class="dim">' + letter + '</li>' + os.linesep
html += '</ul>' + os.linesep
html += '<div class="index-az">' + os.linesep
md += '</ul>' + os.linesep
md += '<div class="index-az">' + os.linesep
for letter in alphabet:
html += '<h4 id="' + letter + '"'
md += '<h4 id="' + letter + '"'
if len(index[letter]) == 0:
html += ' class="dim"'
html += '>' + letter + '</h4>' + os.linesep
md += ' class="dim"'
md += '>' + letter + '</h4>' + os.linesep
if len(index[letter]) != 0:
html += '<ul>' + os.linesep
md += '<ul>' + os.linesep
for item in sorted(index[letter], key=operator.itemgetter('title', 'sub_title')):
html += ' <li><a href="' + item['href'] + '">' + item['title'] + '</a> <small class="dim">' + item['sub_title'] + '</small></li>' + os.linesep
html += '</ul>' + os.linesep
html += '</div>' + os.linesep
LuaDoc._write_file(os.path.join(output_dir, LuaDoc.FILENAME_INDEX_AZ), html)
md += ' <li><a href="' + item['href'] + '">' + item['title'] + '</a> <small class="dim">' + item['sub_title'] + '</small></li>' + os.linesep
md += '</ul>' + os.linesep
md += '</div>' + os.linesep
LuaDoc._write_file(os.path.join(output_dir, LuaDoc.FILENAME_INDEX_AZ), md)
def _add_toc(self, html: str) -> str:
toc = '<div class="toc toc-right"><span class="title" href="#">' + self._get_term('contents') + '</span>'
def _md_paragraph(text: str) -> str:
return text + os.linesep + os.linesep
current_depth = 0
for tag, id, title in re.findall(r'<(h2|h3|dt) id="(.+?)">(.+?)</\1>', html):
title = re.sub(r'<a[^>]*>(.*?)</a>', r'\1', title) # remove links
title = re.sub(r'^(<code>)[a-z0-9_\.]+\.', r'\1', title) # remove Lua lib stuff
title = re.sub(r'<span class="badge.+?</span>', '', title) # remove badges
title = title.strip() # remove leading/tailing spaces
depth = 2 if tag == 'h2' else 3
if depth > current_depth:
toc += '<ul>'
elif depth < current_depth:
toc += '</ul></li>'
else:
toc += '</li>'
toc += '<li><a href="#' + id + '">' + title + '</a>'
current_depth = depth
toc += '</li></ul>' * (current_depth - 1)
toc += '</div>'
def _md_list(items: list) -> str:
return ('- ' + (os.linesep + os.linesep + '- ').join(items) + os.linesetp + os.linesep) if len(items) > 0 else ''
return html.replace('<!--TOC-->', toc)
def _get_header(self, title: str, nav: list) -> str:
menu = ' <li><a href="' + LuaDoc.FILENAME_GLOBALS + '">' + self._get_term('globals:title') + '</a></li>' + os.linesep
menu += ' <li><a href="' + LuaDoc.FILENAME_PV + '">' + self._get_term('pv:title') + '</a></li>' + os.linesep
for k in sorted(list(self._libs.keys()) + ['enum', 'set']):
if k == 'enum':
menu += ' <li><a href="' + LuaDoc.FILENAME_ENUM + '">' + self._get_term('enum:title') + '</a></li>' + os.linesep
elif k == 'set':
menu += ' <li><a href="' + LuaDoc.FILENAME_SET + '">' + self._get_term('set:title') + '</a></li>' + os.linesep
else:
lib = self._libs[k]
menu += ' <li><a href="' + lib['filename'] + '">' + self._get_term(lib['name']) + '</a></li>' + os.linesep
menu += ' <li><a href="' + LuaDoc.FILENAME_OBJECT + '">' + self._get_term('object:title') + '</a></li>' + os.linesep
menu += ' <li><a href="' + LuaDoc.FILENAME_EXAMPLE + '">' + self._get_term('example:title') + '</a></li>' + os.linesep
menu += ' <li><a href="' + LuaDoc.FILENAME_INDEX_AZ + '">' + self._get_term('index-az:title') + '</a></li>' + os.linesep
nav_html = ''
if len(nav) > 0:
nav_html += '<nav><ul>' + os.linesep
for item in nav:
if item == nav[-1]: # last
nav_html += ' <li>' + item['title'] + '</li>' + os.linesep
else:
nav_html += ' <li><a href="' + item['href'] + '">' + item['title'] + '</a></li>' + os.linesep
nav_html += '</ul></nav>' + os.linesep
return '''<!doctype html>
<html lang="{language:s}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{title:s}</title>
<link rel="stylesheet" href="./css/pure-min.css">
<link rel="stylesheet" href="./css/traintasticmanual.css">
</head>
<body>
<div id="layout">
<div class="toc toc-left">
<span class="title">Menu</span>
<ul>
{menu:s}
</ul>
</div>
<!--TOC-->
<div id="main" class="luadoc">
{nav:s}
<div class="content">
<h1>{title:s}</h1>
'''.format(language=self._language, title=title, menu=menu, nav=nav_html)
def _get_footer(self):
return ''' </div>
</div>
</div>
</body>
</html>
'''
if __name__ == '__main__':
from argparse import ArgumentParser
from traintasticmanualbuilder.utils import detect_version
# Standard options:
parser = ArgumentParser()
parser.add_argument('--project-root', default=os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
parser.add_argument('--output-dir', default=os.path.abspath(os.path.join(os.path.dirname(__file__), 'build.luadoc')))
parser.add_argument('--language', default=[None], action='append')
parser.add_argument('--version', default=detect_version())
args = parser.parse_args(sys.argv[1:])
lua_doc = LuaDoc(args.project_root)
lua_doc.verion = args.version
for language in args.language:
if language is not None:
lua_doc.set_language(language)
lua_doc.build(args.output_dir)
if len(lua_doc.missing_terms) > 0:
for term in lua_doc.missing_terms:
print('Warning: missing or empty definition for term {:s}'.format(term))
print('Warning: missing or empty definition for {:d} terms.'.format(len(lua_doc.missing_terms)))
if False:
for term in lua_doc.missing_terms:
print(''' {
"term": "''' + term + '''",
"definition": ""
},''')
def _md_lua_code(code: str) -> str:
return '```lua' + os.linesep + code.rstrip('\n\r') + os.linesep + '```' + os.linesep + os.linesep

Datei anzeigen

@ -2,49 +2,89 @@
"assert": {
"is_lua_builtin": true,
"type": "function",
"parameters": [],
"parameters": [
{
"name": "condition"
},
{
"name": "message",
"optional": true
}
],
"return_values": 0,
"since": "0.1"
},
"pairs": {
"is_lua_builtin": true,
"type": "function",
"parameters": [],
"return_values": 0,
"parameters": [
{
"name": "table"
}
],
"return_values": 3,
"since": "0.1"
},
"ipairs": {
"is_lua_builtin": true,
"type": "function",
"parameters": [],
"return_values": 0,
"parameters": [
{
"name": "table"
}
],
"return_values": 3,
"since": "0.1"
},
"next": {
"is_lua_builtin": true,
"type": "function",
"parameters": [],
"return_values": 0,
"parameters": [
{
"name": "table"
},
{
"name": "index",
"optional": true
}
],
"return_values": 1,
"since": "0.1"
},
"tonumber": {
"is_lua_builtin": true,
"type": "function",
"parameters": [],
"return_values": 0,
"parameters": [
{
"name": "value"
},
{
"name": "base",
"optional": true
}
],
"return_values": 1,
"since": "0.1"
},
"tostring": {
"is_lua_builtin": true,
"type": "function",
"parameters": [],
"return_values": 0,
"parameters": [
{
"name": "value"
}
],
"return_values": 1,
"since": "0.1"
},
"type": {
"type": "function",
"parameters": [],
"return_values": 0,
"parameters": [
{
"name": "value"
}
],
"return_values": 2,
"since": "0.1"
},
"VERSION": {
@ -106,4 +146,4 @@
"type": "library",
"since": "0.1"
}
}
}

Datei anzeigen

@ -55,6 +55,10 @@
"term": "events",
"definition": "Events"
},
{
"term": "arguments",
"definition": "Arguments"
},
{
"term": "functions",
"definition": "Functions"
@ -431,6 +435,102 @@
"term": "globals:title",
"definition": "Globals"
},
{
"term": "globals:description",
"definition": "Global environment containing built-in constants, libraries, objects, and functions available in Traintastics Lua."
},
{
"term": "globals.assert:description",
"definition": "Raises an error if the given condition is false or nil; otherwise returns its argument(s)."
},
{
"term": "globals.assert.parameter.condition:description",
"definition": "A value that is tested for truth; if false or nil, the function errors."
},
{
"term": "globals.assert.parameter.message:description",
"definition": "Optional error message (string) to use if the assertion fails."
},
{
"term": "globals.ipairs:description",
"definition": "Returns an iterator function for array-like tables, iterating in order of numerical indices starting from 1."
},
{
"term": "globals.ipairs.parameter.table:description",
"definition": "A numerically indexed table."
},
{
"term": "globals.ipairs:return_values",
"definition": "Three values: an iterator function, the table itself, and the initial index (0)."
},
{
"term": "globals.next:description",
"definition": "Allows traversal of all key–value pairs in a table; returns the next key and its value."
},
{
"term": "globals.next.parameter.table:description",
"definition": "The table to iterate."
},
{
"term": "globals.next.parameter.index:description",
"definition": "The last key returned; iteration resumes after this key (optional, use nil to start)."
},
{
"term": "globals.next:return_values",
"definition": "The next key and its corresponding value in the table, or nil when no more entries."
},
{
"term": "globals.pairs:description",
"definition": "Returns an iterator function to traverse all key–value pairs in a table in no particular order."
},
{
"term": "globals.pairs.parameter.table:description",
"definition": "The table to iterate over."
},
{
"term": "globals.pairs:return_values",
"definition": "Three values: an iterator function, the table itself, and an initial nil key."
},
{
"term": "globals.tonumber:description",
"definition": "Tries to convert its argument to a number; returns the number or nil if conversion fails."
},
{
"term": "globals.tonumber.parameter.value:description",
"definition": "A string or number to convert."
},
{
"term": "globals.tonumber.parameter.base:description",
"definition": "An optional base (2–36) to interpret the string in (default is base 10)."
},
{
"term": "globals.tonumber:return_values",
"definition": "The converted number, or nil if the input is not a valid numeric representation."
},
{
"term": "globals.tostring:description",
"definition": "Returns a string representation of its argument."
},
{
"term": "globals.tostring.parameter.value:description",
"definition": "The value to convert to string."
},
{
"term": "globals.tostring:return_values",
"definition": "A string representation of the given value."
},
{
"term": "globals.type:description",
"definition": "Returns the type name of its argument as a string (such as 'nil', 'number', 'string', 'boolean', 'table', 'function', etc.)."
},
{
"term": "globals.type.parameter.value:description",
"definition": "The value whose type is to be returned."
},
{
"term": "globals.type:return_values",
"definition": "A string describing the type of the input value, or 'nil' if the value is nil."
},
{
"term": "class:title",
"definition": "Class library"
@ -467,6 +567,214 @@
"term": "math.abs:return_values",
"definition": "The absolute value of `number`."
},
{
"term": "math:description",
"definition": "Provides mathematical functions and constants. This library includes trigonometric, exponential, logarithmic, and other standard math operations, along with constants such as π."
},
{
"term": "math.huge:description",
"definition": "A special value representing positive infinity."
},
{
"term": "math.maxinteger:description",
"definition": "The maximum value that can be represented by an integer in Lua."
},
{
"term": "math.mininteger:description",
"definition": "The minimum value that can be represented by an integer in Lua."
},
{
"term": "math.pi:description",
"definition": "The value of π (pi), the ratio of a circle's circumference to its diameter."
},
{
"term": "math.acos.parameter.number:description",
"definition": "A number between -1 and 1."
},
{
"term": "math.acos:return_values",
"definition": "The arc cosine of the input number, in radians."
},
{
"term": "math.asin.parameter.number:description",
"definition": "A number between -1 and 1."
},
{
"term": "math.asin:return_values",
"definition": "The arc sine of the input number, in radians."
},
{
"term": "math.atan:description",
"definition": "Returns the arc tangent of a number, in radians."
},
{
"term": "math.atan.parameter.number:description",
"definition": "A numeric value representing a tangent."
},
{
"term": "math.atan:return_values",
"definition": "The arc tangent of the input number, in radians."
},
{
"term": "math.ceil.parameter.number:description",
"definition": "A numeric value to round up."
},
{
"term": "math.ceil:return_values",
"definition": "The smallest integer greater than or equal to the given number."
},
{
"term": "math.cos.parameter.number:description",
"definition": "An angle in radians."
},
{
"term": "math.cos:return_values",
"definition": "The cosine of the angle."
},
{
"term": "math.deg:description",
"definition": "Converts an angle from radians to degrees."
},
{
"term": "math.deg.parameter.number:description",
"definition": "An angle in radians."
},
{
"term": "math.deg:return_values",
"definition": "The angle converted to degrees."
},
{
"term": "math.exp:description",
"definition": "Returns the value of the constant e raised to the power of the given number."
},
{
"term": "math.exp.parameter.number:description",
"definition": "The exponent to raise e to."
},
{
"term": "math.exp:return_values",
"definition": "The result of e^number."
},
{
"term": "math.floor:description",
"definition": "Returns the largest integral value less than or equal to the given number."
},
{
"term": "math.floor.parameter.number:description",
"definition": "A numeric value to round down."
},
{
"term": "math.floor:return_values",
"definition": "The largest integer less than or equal to the given number."
},
{
"term": "math.fmod:description",
"definition": "Returns the remainder of the division of the first number by the second."
},
{
"term": "math.fmod.parameter.divided:description",
"definition": "The dividend."
},
{
"term": "math.fmod.parameter.divisor:description",
"definition": "The divisor."
},
{
"term": "math.fmod:return_values",
"definition": "The remainder of the division."
},
{
"term": "math.log:description",
"definition": "Returns the logarithm of a number in a given base. Defaults to natural logarithm if base is not provided."
},
{
"term": "math.log.parameter.number:description",
"definition": "The number to compute the logarithm for."
},
{
"term": "math.log.parameter.base:description",
"definition": "The logarithmic base. Optional; defaults to e."
},
{
"term": "math.max:description",
"definition": "Returns the largest value among its arguments."
},
{
"term": "math.max.parameter.number:description",
"definition": "The first number to compare."
},
{
"term": "math.max.parameter....:description",
"definition": "Additional numbers to compare."
},
{
"term": "math.min:description",
"definition": "Returns the smallest value among its arguments."
},
{
"term": "math.min.parameter.number:description",
"definition": "The first number to compare."
},
{
"term": "math.min.parameter....:description",
"definition": "Additional numbers to compare."
},
{
"term": "math.modf:description",
"definition": "Splits a number into its integer and fractional parts."
},
{
"term": "math.modf.parameter.number:description",
"definition": "The number to split."
},
{
"term": "math.modf:return_values",
"definition": "Two numbers: the integer part and the fractional part."
},
{
"term": "math.rad:description",
"definition": "Converts an angle from degrees to radians."
},
{
"term": "math.random:description",
"definition": "Generates a pseudo-random number. The behavior depends on the provided arguments."
},
{
"term": "math.random.parameter.m:description",
"definition": "The lower limit of the random number range (optional)."
},
{
"term": "math.random.parameter.n:description",
"definition": "The upper limit of the random number range (optional)."
},
{
"term": "math.randomseed:description",
"definition": "Sets the seed for the random number generator."
},
{
"term": "math.sin:description",
"definition": "Returns the sine of an angle (in radians)."
},
{
"term": "math.sqrt:description",
"definition": "Returns the square root of a number."
},
{
"term": "math.tan:description",
"definition": "Returns the tangent of an angle (in radians)."
},
{
"term": "math.tointeger:description",
"definition": "Converts a number to an integer if it is exactly representable."
},
{
"term": "math.type:description",
"definition": "Returns the type of a number: 'integer', 'float', or nil if not a number."
},
{
"term": "math.ult:description",
"definition": "Compares two unsigned integers and returns true if the first is less than the second."
},
{
"term": "globals:description",
"definition": ""
@ -1153,7 +1461,7 @@
},
{
"term": "event.parameter.user_data:description",
"definition": "User data that was set or `nil` if no user data was set during connect."
"definition": "optional user-provided value (see [Events](../events.md#user_data))."
},
{
"term": "object:title",
@ -1321,7 +1629,7 @@
},
{
"term": "object.idobject.id:description",
"definition": "Object id, each object has a unique id."
"definition": "Unique object identifier. Each object in Traintastic has a globally unique id that can be used in scripts (see {ref:object.world#get_object|`world.get_object`})."
},
{
"term": "object.board.name:description",
@ -2037,83 +2345,87 @@
},
{
"term": "object.zone:description",
"definition": "A group of blocks, this can for example be used to create a station or a staging area."
"definition": "A zone groups multiple blocks into a logical area, such as a station, yard, or staging area. Zones make it easier to script behaviors that depend on a trains location within a larger area, rather than tracking individual blocks."
},
{
"term": "object.zone.name:description",
"definition": "Zone name."
"definition": "The configured name of the zone."
},
{
"term": "object.zone:events_overview",
"definition": "Zone events fall into two categories:\n\n- **Assignment events**: `on_train_assigned` and `on_train_removed` are triggered when a train is manually linked to or unlinked from a block in the zone. These indicate identification changes made by the operator, not movement.\n\n- **Movement events**: `on_train_entering`, `on_train_entered`, `on_train_leaving`, and `on_train_left` are triggered as a train moves through the zone. They represent the lifecycle of a train entering, being inside, leaving, and fully leaving the zone."
},
{
"term": "object.zone.on_train_entering:description",
"definition": "Fired when a train reserves or enters the first block in the zone."
"definition": "Fired when a train starts moving into the zone by reserving or occupying its first block. This marks the point where the train begins to interact with the zone but is not yet fully inside it (see `on_train_entered`).\n\nUnlike `on_train_assigned`, which is triggered when a train is manually linked to a block (e.g., placed on the track and assigned by the operator), `on_train_entering` reflects actual movement or reservation during operation."
},
{
"term": "object.zone.on_train_entering.parameter.train:description",
"definition": "The {ref:object.train|train} that is entering the zone."
"definition": "the {ref:object.train|train} that is entering the zone."
},
{
"term": "object.zone.on_train_entering.parameter.zone:description",
"definition": "The zone."
"definition": "the {ref:object.zone|zone} firing the event."
},
{
"term": "object.zone.on_train_assigned:description",
"definition": "Fired when a train is assigned to the first block in the zone."
"definition": "Fired when a train is manually assigned to the first block of the zone. This happens after a train has been placed on the track and the operator links it to the block, allowing Traintastic to recognize which train it is.\n\nThis event is about train identification, not movement."
},
{
"term": "object.zone.on_train_assigned.parameter.train:description",
"definition": "The {ref:object.train|train} that is assigned to a block in the zone."
"definition": "the {ref:object.train|train} that is assigned to a block in the zone."
},
{
"term": "object.zone.on_train_assigned.parameter.zone:description",
"definition": "The zone."
"definition": "the {ref:object.zone|zone} firing the event."
},
{
"term": "object.zone.on_train_leaving:description",
"definition": "Fired when a train reserves or enters a block that is not part of the zone."
"definition": "Fired when a train starts to leave the zone by reserving or occupying a block that is not part of the zone. At this point the train still has blocks in the zone, but it is extending beyond its boundaries."
},
{
"term": "object.zone.on_train_leaving.parameter.train:description",
"definition": "The {ref:object.train|train} that is leaving the zone."
"definition": "the {ref:object.train|train} that is leaving the zone."
},
{
"term": "object.zone.on_train_leaving.parameter.zone:description",
"definition": "The zone."
"definition": "the {ref:object.zone|zone} firing the event."
},
{
"term": "object.zone.on_train_entered:description",
"definition": "Fired when a train is in the zone, a train is in the zone when it only occupies blocks that are part of the zone."
"definition": "Fired when a train is fully inside the zone. A train is considered 'in the zone' only if it exclusively occupies blocks that belong to the zone.\n\n!!! note\n If a zone is too small or the train too long, it may happen that while entering, the train simultaneously starts leaving the zone. In this case, the train is never fully inside, and `on_train_entered` will not be fired (only `on_train_entering` and `on_train_leaving` apply)."
},
{
"term": "object.zone.on_train_entered.parameter.train:description",
"definition": "The {ref:object.train|train} that is in the zone."
"definition": "the {ref:object.train|train} that is in the zone."
},
{
"term": "object.zone.on_train_entered.parameter.zone:description",
"definition": "The zone."
"definition": "the {ref:object.zone|zone} firing the event."
},
{
"term": "object.zone.on_train_left:description",
"definition": "Fired when a train is no longer in the zone, a train is not longer in the zone when it doesn't occupy any block that are part of the zone."
"definition": "Fired when a train is no longer in the zone, a train is no longer in the zone when it doesn't occupy any block that are part of the zone."
},
{
"term": "object.zone.on_train_left.parameter.train:description",
"definition": "The {ref:object.train|train} that has left the zone."
"definition": "the {ref:object.train|train} that has left the zone."
},
{
"term": "object.zone.on_train_left.parameter.zone:description",
"definition": "The zone."
"definition": "the {ref:object.zone|zone} firing the event."
},
{
"term": "object.zone.on_train_removed:description",
"definition": "Fired when a train is removed from the last block in the zone."
"definition": "Fired when a train is manually removed from the last block of the zone. This is the opposite of `on_train_assigned` and indicates the train is no longer linked to the zone.\n\nThis event is about train identification, not movement."
},
{
"term": "object.zone.on_train_removed.parameter.train:description",
"definition": "The {ref:object.train|train} that is removed from the zone."
"definition": "the {ref:object.train|train} that is removed from the zone."
},
{
"term": "object.zone.on_train_removed.parameter.zone:description",
"definition": "The zone."
"definition": "the {ref:object.zone|zone} firing the event."
},
{
"term": "object.trainzonestatus:title",
@ -2209,7 +2521,7 @@
},
{
"term": "object.zone.mute:description",
"definition": "If `true`, all trains in the zone are muted."
"definition": "If `true`, all sounds of trains in the zone are muted."
},
{
"term": "object.zone.no_smoke:description",
@ -2229,7 +2541,7 @@
},
{
"term": "pv:paragraph_1",
"definition": "Persistent variables allow you to store and retrieve data that remains available across multiple executions of the Lua script. This can be particularly useful for maintaining state information that needs to be retained beyond the current script's lifetime."
"definition": "Persistent variables allow you to store data that survives script restarts and world save/load. They are useful for keeping state between sessions, such as counters, configuration values, or custom train data."
},
{
"term": "pv:paragraph_2",
@ -2241,7 +2553,11 @@
},
{
"term": "pv.storing:paragraph_1",
"definition": "You can store data in {ref:globals#pv|`pv`} just like you would with a regular Lua table. Supported data types are numbers, strings, booleans, tables, {ref:enum|enums}, {ref:set|sets}, {ref:object|objects} and object methods."
"definition": "You can store data in {ref:globals#pv|`pv`} just like you would with a regular Lua table."
},
{
"term": "pv:storing_note",
"definition": "Persistent variables supports storing booleans, numbers, strings, tables, {ref:enum|enums}, {ref:set|sets}, {ref:object|objects} and object methods."
},
{
"term": "pv.retrieving:title",
@ -2261,11 +2577,11 @@
},
{
"term": "pv.checking:title",
"definition": "Checking for persistent data"
"definition": "Checking if data exists"
},
{
"term": "pv.checking:paragraph_1",
"definition": "To determine if a persistent variable has been set, use an `if` statement with `nil` checks. Variables in {ref:globals#pv|`pv`} that haven't been initialized or have been deleted will return `nil`. This pattern is useful for initializing default values or handling cases where the persistent variables are cleared."
"definition": "To determine if a persistent variable has been set, use a `nil` checks. This pattern is useful for initializing default values or handling cases where the persistent variables are cleared."
},
{
"term": "enum.zone_train_state:description",
@ -2293,11 +2609,11 @@
},
{
"term": "object.train.mute:description",
"definition": "`true` if the train is currently muted by the {ref:object.world|world} mute or a muted {ref:object.zone|zone}, `false` otherwise."
"definition": "If `true`, mutes all trains in the zone."
},
{
"term": "object.train.no_smoke:description",
"definition": "`true` if the train's smoke generators are disabled by the {ref:object.world|world} no smoke or a non smoking {ref:object.zone|zone}, `false` otherwise."
"definition": "If `true`, disables smoke generators of all trains in the zone.s"
},
{
"term": "object.train.zones:description",

Datei anzeigen

@ -0,0 +1,18 @@
.md-typeset .admonition.footnote,
.md-typeset details.footnote {
font-size: 0.85em;
color: var(--md-default-fg-color--light);
border: none;
background: none;
box-shadow: none;
padding-left: 0.6em;
margin: 0;
}
.md-typeset .admonition.footnote > p {
margin: 0;
}
.md-typeset .admonition.footnote>.admonition-title {
display: none;
}

Datei anzeigen

Vorher

Breite:  |  Höhe:  |  Größe: 295 B

Nachher

Breite:  |  Höhe:  |  Größe: 295 B

Datei anzeigen

Vorher

Breite:  |  Höhe:  |  Größe: 505 B

Nachher

Breite:  |  Höhe:  |  Größe: 505 B

Datei anzeigen

Vorher

Breite:  |  Höhe:  |  Größe: 660 B

Nachher

Breite:  |  Höhe:  |  Größe: 660 B

Datei anzeigen

Vorher

Breite:  |  Höhe:  |  Größe: 364 B

Nachher

Breite:  |  Höhe:  |  Größe: 364 B

Datei anzeigen

Vorher

Breite:  |  Höhe:  |  Größe: 379 B

Nachher

Breite:  |  Höhe:  |  Größe: 379 B

Datei anzeigen

Vorher

Breite:  |  Höhe:  |  Größe: 501 B

Nachher

Breite:  |  Höhe:  |  Größe: 501 B

Datei anzeigen

Vorher

Breite:  |  Höhe:  |  Größe: 629 B

Nachher

Breite:  |  Höhe:  |  Größe: 629 B

Datei anzeigen

Vorher

Breite:  |  Höhe:  |  Größe: 759 B

Nachher

Breite:  |  Höhe:  |  Größe: 759 B

Datei anzeigen

Vorher

Breite:  |  Höhe:  |  Größe: 610 B

Nachher

Breite:  |  Höhe:  |  Größe: 610 B

Datei anzeigen

Vorher

Breite:  |  Höhe:  |  Größe: 372 B

Nachher

Breite:  |  Höhe:  |  Größe: 372 B

2
manual/requirements.txt Normale Datei
Datei anzeigen

@ -0,0 +1,2 @@
mkdocs
mkdocs-material

Dateidiff unterdrückt, weil mindestens eine Zeile zu lang ist

Datei anzeigen

@ -1,458 +0,0 @@
#layout
{
position: relative;
right: 0;
padding-right: 0;
}
#main
{
margin-left: 16em;
}
#main.luadoc
{
margin-right: 16em;
}
img.img-float-right
{
float: right;
margin: 0 0 1rem 1rem;
}
/** TOC **********************************************************************/
.toc
{
color: #444;
padding-left: 1.5em;
padding-right: 1.5em;
position: fixed;
top: 1.5em;
width: 14em;
}
.toc.toc-right
{
border-left: solid 2px #bbb;
right: 0;
}
.toc.toc-left
{
border-right: solid 2px #bbb;
left: 0;
}
.toc span.title
{
font-size: 110%;
font-weight: bold;
display: inline-block;
}
.toc ul
{
margin: 1em 0;
padding: 0;
list-style-type: none;
font-size: 90%;
}
.toc ul:last-child
{
margin-bottom: 0;
}
.toc ul ul
{
margin: 0;
padding-left: 1em;
font-size: 90%;
}
.toc a
{
color: #888;
text-decoration: none;
}
.toc a:hover
{
text-decoration: underline;
}
.toc a:hover,
.toc a.selected
{
color: #000;
}
/** CONTENT ******************************************************************/
.content,
nav
{
margin: 0 auto;
padding: 0 2em;
max-width: 1000px;
line-height: 1.6em;
}
.content
{
margin-bottom: 50px;
}
.content .center
{
text-align: center;
}
.content .dim
{
color: #888;
}
.content p.large
{
font-size: 120%;
}
.content p.small
{
font-size: 80%;
}
.content h1:not(:first-child)
{
margin-top : 5em;
}
.content h1.title
{
font-size: 400%;
text-align: center;
margin-bottom: 0.5em;
line-height: 0.9;
}
.content h2.title
{
font-size: 250%;
text-align: center;
color: #444;
margin-top: 0.5em;
}
.content h2,
.content h3,
.content h4,
.content h5,
.content h6
{
margin: 1.5em 0 0.5em 0;
}
.content p
{
margin-top: 0.5em;
}
.content table
{
border-collapse: collapse;
}
.content table thead tr
{
background: #f4f4f4;
}
.content table thead th
{
border-bottom : solid 1px #888;
}
.content table tbody tr:nth-child(even)
{
background: #f4f4f4;
}
.content table th,
.content table td
{
padding: 0 0.75em;
}
.content img
{
max-width: 100%;
height: auto;
}
.content figure
{
margin-left: 0;
}
.content figcaption
{
line-height: 80%;
font-size: 80%;
}
/** Badge ********************************************************************/
span.badge
{
border-radius: .6rem;
display: inline-block;
font-size: small;
font-weight: 700;
line-height: 1;
margin-left: 1rem;
padding: .25em .4em;
text-align: center;
vertical-align: baseline;
white-space: nowrap;
}
span.badge:first-child,
span.badge ~ span.badge
{
margin-left: 0;
}
span.badge.badge-lua
{
background-color: #030380;
color: #fff;
}
span.badge.badge-since
{
background-color: #28a745;
color: #fff;
}
/** Note **/
p.note
{
border-left: solid 0.4em darkblue;
font-size: 85%;
line-height: normal;
padding: 0 0 0 0.4em;
}
p.note span.label
{
display: block;
font-weight: bold;
text-transform: uppercase;
}
/** LuaDoc *******************************************************************/
nav ul
{
list-style: none;
border-bottom: solid #808080 1px;
margin: 0;
padding: 0;
}
nav ul li
{
display: inline-block;
margin: 0;
padding: 0;
}
nav ul li:not(:last-child)::after
{
content: '\00BB';
display: inline-block;
}
nav ul li a
{
padding: 0 0.2em;
margin: 0;
}
div.index-az
{
column-count: 3;
}
ul.index-az-nav
{
list-style: none;
}
ul.index-az-nav li
{
display: inline-block;
margin: 0;
padding: 0;
}
ul.index-az-nav li::before
{
content: '\2022';
display: inline-block;
}
ul.index-az-nav li:last-child::after
{
content: '\2022';
display: inline-block;
}
ul.index-az-nav li a
{
padding: 0 0.2em 0 0;
margin: 0;
}
/** Lua **********************************************************************/
pre[lang="lua"],
pre[lang="bash"]
{
border: solid 1px darkgray;
background-color: #f8f8f8;
font-size: 80%;
line-height: normal;
padding: 0.4em;
}
pre[lang="lua"] code span.const
{
color: darkviolet;
}
pre[lang="lua"] code span.keyword
{
color: blue;
}
pre[lang="lua"] code span.number
{
color: magenta;
}
pre[lang="lua"] code span.global
{
color: chocolate;
}
pre[lang="lua"] code span.text
{
color: darkgreen;
}
pre[lang="lua"] code span.comment,
pre[lang="lua"] code span.comment a
{
color: gray;
}
pre[lang="lua"] code span.function
{
color: teal;
}
/** MEDIA BREAKPOINTS ********************************************************/
@media screen and (max-width: 991px)
{
#main
{
margin-right: 0;
margin-left: 0;
}
.toc
{
display: none;
}
}
/** Dark mode **/
@media (prefers-color-scheme: dark)
{
body
{
background-color: #111;
color: #eee;
}
a
{
color: rgb(47, 129, 247)
}
.toc
{
border-color: #555;
color: #ccc;
}
.toc a:hover,
.toc a.selected
{
color: #fff;
}
.content table thead tr
{
background: #222;
}
.content table tbody tr:nth-child(even)
{
background: #222;
}
p.note
{
border-left-color: blue;
}
pre[lang="lua"],
pre[lang="bash"]
{
border-color: gray;
background-color: #222;
}
pre[lang="lua"] code span.const
{
color: violet;
}
pre[lang="lua"] code span.text
{
color: lawngreen;
}
pre[lang="lua"] code span.keyword
{
color: cyan;
}
pre[lang="lua"] code span.global
{
color: orange;
}
pre[lang="lua"] code span.function
{
color: #66CDAA;
}
}

Datei anzeigen

@ -1,3 +0,0 @@
# Actions {#actions}
TODO

Datei anzeigen

@ -1,3 +0,0 @@
# Board {#board}
TODO

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden Mehr anzeigen