/********************************************************************* * Railuino - Hacking your Märklin * * Copyright (C) 2012 Joerg Pleumann * Copyright (C) 2024 christophe bobille * * This example is free software; you can redistribute it and/or * modify it under the terms of the Creative Commons Zero License, * version 1.0, as published by the Creative Commons Organisation. * This effectively puts the file into the public domain. * * This example is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * LICENSE file for more details. */ #ifndef TRACKCONTROLLER_H #define TRACKCONTROLLER_H #include #include "TrackMessage.h" #include "Config.h" // =================================================================== // === TrackController =============================================== // =================================================================== /** * Controls things on and connected to the track: locomotives, * turnouts and other accessories. While there are some low-level * methods for dealing with messages, you will normally want to use * the high-level methods that wrap most of the the nasty protocol * details. When addressing something, you have to tell the system the * type of address (or decoder) you are using by adding the proper * protocol base address. For instance, DCC locomotive 42 is properly * addressed as ADDR_DCC + 42. */ class TrackController { private: /** * Stores the hash of our controller. This must not conflict with * hashes of other devices in the setup (most likely the MS2 and * the connector box). */ uint16_t mHash; /** * Stores the debug flag. When debugging is on, all outgoing and * incoming messages are printed to the Serial console. */ bool mDebug; /** * Holds the loopback flag. When loopback is on, messages are * reflected by the CAN controller. No external communication * takes place. This is helpful for some test cases. */ bool mLoopback; uint64_t mTimeout; public: /** * Creates a new TrackController with default values. */ TrackController(); /** * Creates a new TrackController with modification of the default exchangeMessage timeout */ TrackController(uint64_t timeOut); /** * Creates a new TrackController with the given hash and debugging * flag and changing the default exchangeMessage timeout. * A zero hash will result in a unique hash begin generated. */ TrackController(uint16_t hash, bool debug, uint64_t timeOut); /** * Creates a new TrackController with the given hash and debugging * flag. A zero hash will result in a unique hash begin generated. */ TrackController(uint16_t hash, bool debug, uint64_t timeOut, bool loopback); /** * Is called when a TrackController is being destroyed. Does the * necessary cleanup. No need to call this manually. */ ~TrackController(); /** * Initializes the TrackController with the given values. This * should be called before begin, otherwise it will not take * effect. A zero hash will result in a unique hash begin * generated. */ void init(uint16_t hash, bool debug, bool loopback, uint64_t timeOut = 1000); /** * Queries the hash used by the TrackController. */ uint16_t getHash(); /** * Reflects whether the TrackController is in debug mode, * where all messages are dumped to the Serial console. */ bool isDebug(); /** * Reflects whether the TrackController is in debug mode, * where all messages are reflected by the CAN controller. */ bool isLoopback(); /** * Initializes the CAN hardware and starts receiving CAN * messages. CAN messages are put into an internal buffer of * limited size, so they don't get lost, but you have to take * care of them in time. Otherwise the buffer might overflow. * default CAN Rx pin is GPIO_NUM_5 and can_tx_pin is GPIO_NUM_4 */ // void begin(const gpio_num_t can_rx_pin = GPIO_NUM_5, // const gpio_num_t can_tx_pin = GPIO_NUM_4); void begin(const byte can_rx_pin = 5, const byte can_tx_pin = 4); /** * Generates a new hash and makes sure it does not conflict * with those of other devices in the setup. */ void generateHash(); /** * Stops receiving messages from the CAN hardware. Clears * the internal buffer. */ // void end(); /** * Sends a message and reports true on success. Internal method. * Normally you don't want to use this, but the more convenient * methods below instead. */ bool sendMessage(TrackMessage &message); /** * Receives an arbitrary message, if available, and reports true * on success. Does not block. Internal method. Normally you * don't want to use this, but the more convenient methods below * instead. */ bool receiveMessage(TrackMessage &message); /** * Sends a message and waits for the corresponding response, * returning true on success. Blocks until either a message with * the same command ID and the response marker arrives or the * timeout (in ms) expires. All non-matching messages are * skipped. Internal method. Normally you don't want to use this, * but the more convenient methods below instead. 'out' and 'in' * may be the same object. */ bool exchangeMessage(TrackMessage &out, TrackMessage &in, uint16_t timeout); /** * Controls power on the track. When passing false, all * locomotives will stop, but remember their previous directions * and speeds. When passing true, all locomotives will regain * their old directions and speeds. The system starts in * stopped mode in order to avoid accidents. The return value * reflects whether the call was successful. */ bool setPower(bool power); /** * Cette commande qui n'existait pas a l'origine a ete ajoutee * a partir de la v-9.01 * Commande système (0x00, dans CAN-ID : 0x00) * Arrêt d'urgence de la locomotive (0x02) * Toutes les locomotives reçoivent l'ordre de s'arrêter (vitesse 0), * y compris l'inertie de freinage. Le signal numérique reste sur les rails, * mais aucune autre commande n'est envoyée sur les rails. * L'énergie électrique reste disponible. */ // See https://streaming.maerklin.de/public-media/cs2/cs2CAN-Protokoll-2_0.pdf -> 2.3 Commande : System Halt bool systemHalt(const uint16_t address); /** * Cette commande qui n'existait pas a l'origine a ete ajoutee * a partir de la v-9.01 * Commande système (0x00, dans CAN-ID : 0x00) * Arrêt d'urgence de la locomotive (0x03) * Arrêt d'urgence ou arrêt immédiat de la locomotive, selon le protocole de voie. * Il faut spécifier une locomotive déjà ciblée par une commande. * Si cette locomotive n'est pas déjà dans le cycle, elle ne sera pas prise en compte par cette commande. */ /** * This command, which didn't exist originally, has been added from v-9.01 * System command (0x00, in CAN-ID: 0x00) * Locomotive emergency stop (0x03) * Emergency stop or immediate stop of locomotive, depending on track protocol. * A locomotive already targeted by a command must be specified. * If this locomotive is not already in the cycle, it will not be taken into account by this command. */ // See https://streaming.maerklin.de/public-media/cs2/cs2CAN-Protokoll-2_0.pdf -> 2.4 Commande : Arrêt d'urgence de la locomotive bool emergency(const uint16_t address); /** * Sets the direction of the given locomotive. Valid directions * are those specified by the DIR_* constants. The return value * reflects whether the call was successful. */ bool setLocoDirection(const uint16_t address, uint8_t direction); /** * Toggles the direction of the given locomotive. This normally * includes a full stop. */ bool toggleLocoDirection(const uint16_t address); /** * Sets the speed of the given locomotive. Valid speeds are * 0 to 1023 (inclusive), though the connector box will limit * all speeds beyond 1000 to 1000. The return value reflects * whether the call was successful. */ bool setLocoSpeed(const uint16_t address, uint16_t speed); // bool accelerateLoco(uint16_t address); // bool decelerateLoco(uint16_t address); /** * Sets the given function of the given locomotive (or simply a * function decoder). Valid functions are 0 to 31, with 0 * normally denoting the head/backlight. Valid values are, again, * 0 ("off") to 31, although not all protocols support values * beyond 1 (which then means "on"). The return value reflects * whether the call was successful. */ bool setLocoFunction(const uint16_t address, uint8_t function, uint8_t power); /** * Toggles the given function of the given locomotive. Valid * functions are 0 to 31, with 0 normally denoting the * head/backlight. */ bool toggleLocoFunction(const uint16_t address, uint8_t function); /** * Switches the given magnetic accessory. Valid position values * are those denoted by the ACC_* constants. Valid power values * are 0 ("off") to 31 (inclusive) although not all protocols * support values beyond 1 (which then means "on"). The final * parameter specifies the time (in ms) for which the accessory * will be active. A time of 0 means the accessory will only be * switched on. Some magnetic accessories must not be active for * too long, because they might burn out. A good timeout for * Marklin turnouts seems to be 20 ms. The return value reflects * whether the call was successful. */ bool setAccessory(const uint16_t address, uint8_t position, uint8_t power, uint16_t time); /** * Switches a turnout. This is actually a convenience function * around setAccessory() that uses default values for some * parameters. The return value reflects whether the call was * successful. */ bool setTurnout(const uint16_t address, bool straight); /** * Queries the direction of the given locomotive and writes it * into the referenced byte. The return value indicates whether * the call was successful and the direction is valid. */ bool getLocoDirection(const uint16_t address, uint8_t *direction); /** * Queries the speed of the given locomotive and writes it * into the referenced byte. The return value indicates whether * the call was successful and the speed is valid. */ bool getLocoSpeed(const uint16_t address, uint16_t *speed); /** * Queries the given function of the given locomotive and writes * it into the referenced byte. The return value indicates * whether the call was successful and the power is valid. Note * that the call will not reflect the original power value sent * to the function, but only 0 ("off") or 1 ("on"). */ bool getLocoFunction(const uint16_t address, uint8_t function, uint8_t *power); /** * Queries the given magnetic accessory's state and and writes * it into the referenced bytes. The return value indicates * whether the call was successful and the bytes are valid. Note * that the call will not reflect the original power value sent * to the function, but only 0 ("off") or 1 ("on"). */ bool getAccessory(const uint16_t address, uint8_t *position, uint8_t *power); /** * Writes the given value to the given config number of the given * locomotive. The return value reflects whether the call was * successful. */ /* ------------------------------------------------------------------- TrackController::writeConfig See https://streaming.maerklin.de/public-media/cs2/cs2CAN-Protokoll-2_0.pdf -> 3.8 Commande : Écrire Config ------------------------------------------------------------------- */ bool writeConfig(const uint16_t address, uint16_t number, uint8_t value); /** * Reads the given config number of the given locomotive into the * given value. */ //!\\ Les décodeurs MFX de première génération ne sont pas conçus pour cela et pourraient être endommagés //!\\ First-generation MFX decoders are not designed for this purpose and could be damaged. // See https://streaming.maerklin.de/public-media/cs2/cs2CAN-Protokoll-2_0.pdf -> 2.8 Befehl: Fast Read für mfx SID - Adresse bool readConfig(const uint16_t address, uint16_t number, uint8_t *value); /** * Queries the software version of the track format processor. */ bool getVersion(); //bool getVersion(uint8_t *high, uint8_t *low); /** * Processes commands received on the serial or TCP port */ void handleUserCommands(String); }; #endif // TRACKCONTROLLER_H