diff --git a/CMakeLists.txt b/CMakeLists.txt index e498419..f6683d2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ find_package(Doxygen) if(DOXYGEN_FOUND) message("Found Doxygen") set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/docs/Doxyfile.in) - set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.out) + set(DOXYGEN_OUT ${CMAKE_CURRENT_SOURCE_DIR}/build/Doxyfile.out) # request to configure the file configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY) diff --git a/README.md b/README.md index 2c79f7a..bf88762 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,8 @@ +# DCCEXProtocol + +For the full documentation, please refer to the [DCC-EX website](https://dcc-ex.com/DCCEXProtocol/index.html). + # Credits The delegate and connection code in this library is taken directly from the WiThrottle library by **Copyright © 2018-2019 Blue Knobby Systems Inc.** @@ -24,7 +28,7 @@ These patterns (Dependency Injection and Delegation) allow you to keep the diffe ### DCCEXProtocol Class -Full documentation of the classes is available via the [DCC-EX website](file:///C:/Code/DCCEXProtocol/docs/_build/html/index.html). +Full documentation of the classes is available via the [DCC-EX website](https://dcc-ex.com/DCCEXProtocol/index.html). The DCCEXProtocol class manages all relevant objects advertised by a DCC-EX EX-CommandStation and exposes simple methods to control these objects from the client software. @@ -32,7 +36,7 @@ These objects include: - Roster entries - Route entries -- Turnouts +- Turnouts/Points - Turntables (noting that these objects are only available in development versions) This means the client software does not need to explicitly manage the state of these objects whilever the ```check()``` method mentioned above is called appropriately. @@ -43,124 +47,6 @@ The DCCEXProtocolDelegate class enables the client software to respond to variou The events able to be managed via this class are over and above those managed by the DCCEXProtocol class and are entirely customisable by the client software to provide dynamic user experience updates such as displaying status changes to objects as they are broadcast from the DCC-EX EX-CommandStation. -## Included examples - -Note that all included examples use WiFi, but the protocol is equally suited to other connection types utilising the Arduino Stream base class including Ethernet and Serial. - -### DCCEXProtocol_Basic - -Basic example to implement a DCCEXProtocol client and connect to a server (with static IP). - -Change the WiFi settings and enter the IP address of the Arduino running EX-CommandStation: - -``` cpp -const char* ssid = "MySSID"; -const char* password = "MyPWD"; -IPAddress serverAddress(192,168,1,1); -``` - -Compile and run, you should see the client connect in the Serial monitor. - -### DCCEX_Delegate - -Example to show how to implement a delegate class to handle callbacks. - -Compile and run, you should see in the Serial monitor the server version printed. - -### DCCEXProtocol_Roster_etc - -Example to show how to retrieve the Roster, Turnouts/Point list, Routes/Automations List, and Turntable List for the Server. - -Compile and run, you should see in the Serial monitor the lists printed. - -### DCCEXProtocol_Loco_Control - -Example to show how to acquire and control locos. The Example assumes that you have a Roster on the EX-CommandStation with at least two entries. - -Compile and run, you should see in the Serial monitor, after 20 second delays, two locos on two throttles change speed, and have functions randomly change. - ----- - -# Usage - -## Throttles - -To simplify the handling of Consists/Multiple Unit Trains the library is implemented to behave in a similar manner to the WiThrottle(TM) protocol, in that it *requires* that locos are attached to a 'throttle'. - -The protocol provides for the throttle app to specify the number of throttles required ```DCCEXProtocol(int maxThrottles=6, bool server=false);``` - -For simple applications controlling a single loco, this adds a small amount of overhead, but the cons of this small overhead are far outweighed by the benefits of being able to manage multiple locos as consists without needing to program CVs every time you wish to assemble a consist (although speed matching is still obviously required). - -To acquire a loco on throttle 0 (zero) (the first throttle), you must specify a DDC address or a loco from the roster. - -From a DCC Address: - -```dccexProtocol.throttle[0].addFromEntry(11, FacingForward);``` will add a loco with a DCC address of 11 on throttle 0, facing forward. - -From a Roster Entry: - -To add the loco to the throttle use ```dccexProtocol.throttle[0].addFromRoster(dccexProtocol.getRosterEntryNo(1), FacingForward);``` to add a loco to Throttle 0, facing forward. - -Control the speed and direction of all the locos on Throttle 0 with ```dccexProtocol.sendThrottleAction(0, speed, Forward);``` - -## Rosters - -The Roster is stored as a Linked List. - -Retrieve the list with ```dccexProtocol.getRoster();``` - -Or with ```dccexProtocol.getLists(bool rosterRequired, bool turnoutListRequired, bool routeListRequired, bool turntableListRequired);``` - -The roster has been fully received when ```isRosterFullyReceived()``` is true. - -Retrieve the size of the list (number of locos) with ```dccexProtocol.getRosterCount()``` - -Retrieve a ```Loco*``` object from the list with ```dccexProtocol.getRosterEntryNo(listEntryNumber)``` - -## Turnouts/Points - -The List of defined Turnouts/Points is stored as a Linked List. - -Retrieve the list with ```dccexProtocol.getTurnouts();``` - -Or with ```dccexProtocol.getLists(bool rosterRequired, bool turnoutListRequired, bool routeListRequired, bool turntableListRequired);``` - -The list has been fully received when ```isTurnoutListFullyReceived()``` is true. - -Retrieve the size of the list with ```dccexProtocol.getTurnoutsCount()``` - -Retrieve a ```Turnout*``` object from the list with ```dccexProtocol.getTurnoutsEntryNo(listEntryNumber)``` - -## Routes/Automations - -The List of defined Routes/Automations is stored as a Linked List. - -Retrieve the list with ```dccexProtocol.getRoutes();``` - -Or with ```dccexProtocol.getLists(bool rosterRequired, bool turnoutListRequired, bool routeListRequired, bool turntableListRequired);``` - -The list has been fully received when ```isRouteListFullyReceived()``` is true. - -Retrieve the size of the list with ```dccexProtocol.getRoutesCount()``` - -Retrieve a ```Route*``` object from the list with ```dccexProtocol.getRoutesEntryNo(listEntryNumber)``` - -## Turntables - -The List of defined Turntables is stored as a Linked List. - -Retrieve the list with ```dccexProtocol.getTurntables();``` - -Or with ```dccexProtocol.getLists(bool rosterRequired, bool turnoutListRequired, bool routeListRequired, bool turntableListRequired);``` - -The list has been fully received when ```isTurntableListFullyReceived()``` is true. - -Retrieve the size of the list with ```dccexProtocol.getTurntablesCount()``` - -Retrieve a ```Turntable*``` object from the list with ```dccexProtocol.getTurntablesEntryNo(listEntryNumber)``` - ----- - # Documentation Documentation of the DCCEXProtocol library is available via the [DCC-EX website](file:///C:/Code/DCCEXProtocol/docs/_build/html/index.html). diff --git a/docs/conf.py b/docs/conf.py index ca03984..03aa6a6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -3,9 +3,9 @@ # For the full list of built-in configuration values, see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html -from sphinx.builders.html import StandaloneHTMLBuilder +# from sphinx.builders.html import StandaloneHTMLBuilder import subprocess -import os +# import os # Doxygen subprocess.call('doxygen Doxyfile.in', shell=True) @@ -21,7 +21,7 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration extensions = [ - # 'sphinx_sitemap', + 'sphinx_sitemap', 'sphinxcontrib.spelling', 'sphinx_rtd_dark_mode', 'breathe' @@ -32,8 +32,8 @@ # Don't make dark mode the user default default_dark_mode = False -spelling_lang='en_UK' -tokenizer_lang='en_UK' +spelling_lang = 'en_UK' +tokenizer_lang = 'en_UK' spelling_word_list_filename = ['spelling_wordlist.txt'] templates_path = ['_templates'] @@ -63,10 +63,10 @@ # 'titles_only': False, 'titles_only': True, 'collapse_navigation': False, - # 'navigation_depth': 3 - 'navigation_depth': -1 + # 'navigation_depth': 3, + 'navigation_depth': -1, } - + html_context = { 'display_github': True, 'github_user': 'DCC-EX', @@ -79,10 +79,16 @@ 'css/sphinx_design_overrides.css', ] +# Sphinx sitemap +html_baseurl = 'https://dcc-ex.com/DCCEXProtocol/' +html_extra_path = [ + 'robots.txt', +] + # -- Breathe configuration ------------------------------------------------- breathe_projects = { "DCCEXProtocol": "_build/xml/" } -breathe_default_project = "C++ Sphinx Doxygen Breathe" -breathe_default_members = ('members', 'undoc-members') +breathe_default_project = "DCCEXProtocol" +breathe_default_members = () diff --git a/docs/documentation.rst b/docs/documentation.rst new file mode 100644 index 0000000..96909b3 --- /dev/null +++ b/docs/documentation.rst @@ -0,0 +1,19 @@ +Documentation +============= + +This documentation is available via the `DCC-EX website `_. + +For contributors wishing to build local copies of the documentation while updating the library, here is the very high level process of the requirements to make this work on Windows: + +- Install `MSYS2 C++ `_ compilers +- Install `CMake `_ and ensure you select the option to add to your user path +- Install `Doxygen `_ and once complete, add to your user path +- Install the CMake Tools extension in VSCode +- Setup a Python virtual environment with "virtualenv venv" and activate with "venv\scripts\activate" +- Install required Python modules with "pip3 install -r requirements.txt" +- Change to the docs directory and run "make html" + +Credit for how to do this to the following: + +- Oliver K Ernst on `Medium `_ +- Sy Brand in her `Microsoft Blog `_ diff --git a/docs/examples.rst b/docs/examples.rst new file mode 100644 index 0000000..e577b0a --- /dev/null +++ b/docs/examples.rst @@ -0,0 +1,58 @@ +Examples +======== + +Several examples have been included to demonstrate functionality of the library. + +Note that all included examples use a WiFi connection, but the protocol is equally suited to other connections types utilising the Arduino Stream base class including Ethernet and Serial. + +To configure WiFi for your settings in all examples, you will need to copy the provided "config.example.h" to "config.h" and update the parameters to suit your environment: + +.. code-block:: cpp + + const char* ssid = "YOUR_SSID_HERE"; // WiFi SSID name here + const char* password = "YOUR_PASSWORD_HERE"; // WiFi password here + IPAddress serverAddress(192,168,4,1); // IP address of your EX-CommandStation + int serverPort = 2560; // Network port of your EX-CommandStation + +DCCEXProtocol_Basic +------------------- + +This example demonstrates the basics of creating a WiFi connection to your EX-CommandStation using the library, and monitoring for broadcasts and command responses. + +DCCEXProtocol_Delegate +---------------------- + +This example builds on the basic example and, in addition, demonstrates how to implement a custom DCCEXProtocolDelegate class to respond to broadcasts and command responses received from EX-CommandStation. + +DCCEXProtocol_Roster_etc +------------------------ + +This example demonstrates how to retrieve the object types from EX-CommandStation, and further demonstrates how to use the delegate to display these object lists when received. + +DCCEXProtocol_Loco_Control +-------------------------- + +This example demonstrates basic locomotive speed and function control using dummy DCC addresses, in addition to controlling track power and further use of the delegate to notify when updates to the locomotive have been received. + +DCCEXProtocol_Consist_Control +----------------------------- + +This example demonstrates how to setup a software based consist (similar to how this is accomplished in Engine Driver), with basic speed and function control of the configured dummy locomotives. The delegate is also used to notify when updates to the configured locomotives have been received. + +DCCEXProtocol_Turnout_control +----------------------------- + +This example demonstrates the basics of controlling turnouts (or points) with the library, including being notified via the delegate when turnout/point objects have been closed/thrown. + +DCCEXProtocol_Multi_Throttle_Control +------------------------------------ + +This example demonstrates how client throttle software may be written to control multiple locomotives (or consists for that matter) concurrently. + +What can't be demonstrated in this example is the control of speed and direction, which would typically be accomplished with the use of rotary encoders or similar. + +Note when setting speed and direction, these should be sent to the EX-CommandStation via the DCCEXProtocol library, and any local references to these should be set based on the response received, not directly by the input method in use. + +For example, when setting the speed based on the position of a rotary encoder, send that value via the protocol's `setThrottle()` method, but do not display that speed directly. Instead, utlise the delegate's `receivedLocoUpdate()` method to update the displayed speed. + +This ensures that the user of the throttle sees the accurate results of what the throttle is doing, and provides validation that the EX-CommandStation is responding to the user input. diff --git a/docs/index.rst b/docs/index.rst index 8ce0c12..759e2d3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,23 +1,33 @@ -.. DCCEXProtocol documentation master file, created by - sphinx-quickstart on Thu Nov 30 15:09:50 2023. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to DCCEXProtocol's documentation! -========================================= +Documentation for the DCC-EX Native command library - DCCEXProtocol +=================================================================== .. toctree:: - :maxdepth: 2 - :caption: Contents: + :maxdepth: 4 + :hidden: + + self + overview + examples + library + documentation + +Credits +------- + +The delegate and connection code in this library is taken directly from the WiThrottle library by **Copyright © 2018-2019 Blue Knobby Systems Inc.** +The rest of the code has been developed by Peter Akers (Flash62au), Peter Cole (peteGSX), and Chris Harlow (UKBloke). + +DCC-EX Native command protocol library +-------------------------------------- + +This library implements the DCC-EX Native command protocol (as used in EX-CommandStation ONLY), allowing a device to connect to the server and act as a client (such as a hardware based throttle). + +The implementation of this library is tested on ESP32 based devices running the Arduino framework. There's nothing in here that's specific to the ESP32, and little of Arduino that couldn't be replaced as needed. + +There has also been limited testing on STM32F103C8 Bluepill with a serial connection. Indices and tables ================== * :ref:`genindex` * :ref:`search` - -Library -======= - -.. doxygenindex:: - :project: DCCEXProtocol diff --git a/docs/library.rst b/docs/library.rst new file mode 100644 index 0000000..7432fe2 --- /dev/null +++ b/docs/library.rst @@ -0,0 +1,5 @@ +Library +======= + +.. doxygenindex:: + :project: DCCEXProtocol diff --git a/docs/overview.rst b/docs/overview.rst new file mode 100644 index 0000000..ca98599 --- /dev/null +++ b/docs/overview.rst @@ -0,0 +1,90 @@ +Library Design Principles +========================= + +First of all, this library implements the DCC-EX Native protocol in a non-blocking fashion. After creating a DCCEXProtocol object, you set up various necessities such as the network connection and a debug console. + +Then, you call the ```check()``` method as often as you can (ideally, once per invocation of the ```loop()``` method) and the library will manage the I/O stream, reading in/parsing commands and calling methods on the delegate as information is available. + +These patterns (Dependency Injection and Delegation) allow you to keep the different parts of your sketch from becoming too intertwined with each other. Nothing in the code that manages the pushbuttons or speed knobs needs to have any detailed knowledge of the DCC-EX native protocol. + +DCCEXProtocol Class +------------------- + +The DCCEXProtocol class manages all relevant objects advertised by a DCC-EX EX-CommandStation and exposes simple methods to control these objects from the client software. + +These objects include: + +- Roster entries +- Route entries +- Turnouts +- Turntables (noting that these objects are only available in development versions) + +This means the client software does not need to explicitly manage the state of these objects whilever the ```check()``` method mentioned above is called appropriately. + +DCCEXProtocolDelegate Class +--------------------------- + +The DCCEXProtocolDelegate class enables the client software to respond to various events generated by a DCC-EX EX-CommandStation as either broadcasts or responses to commands. + +The events able to be managed via this class are over and above those managed by the DCCEXProtocol class and are entirely customisable by the client software to provide dynamic user experience updates such as displaying status changes to objects as they are broadcast from the DCC-EX EX-CommandStation. + +Usage +===== + +Whilst this library extrapolates the need for understanding the specific DCC-EX native commands from a throttle developer, it is highly recommended to familiarise yourself with the concepts outlined in the ``_. + +Setup +----- + +Once the DCCEXProtocol object is instantiated, a connection must be made to the EX-CommandStation using the `connect(&stream)` method and providing a suitable Arduino Stream, such as a WiFi client or serial connection. + +It is also recommended to enable logging to an Arduino Stream using the `setLogStream(&stream)` method. + +As covered in the design principles above, you must include the `check()` method as often as possible to receive command responses and broadcasts and have these processed by the library and any event handlers defined in your custom DCCEXProtocolDelegate class. + +Refer to the :doc:`examples` to see how this may be implemented. + +Control and Inputs +------------------ + +It is up to the client software utilising this library to manage control and input methods to provide input into the protocol functions such as setting locomotive speed and direction, turning functions on and off, and controlling the various other objects and methods available. + +For example, multiple rotary encoders may be used to simultaneously control multiple locomotives or consists. + +There is, however, no need to instantiate more than one DCCEXProtocol or DCCEXProtocolDelegate object providing the client software is written appropriately, and we recommend creating a custom class that can take the DCCEXProtocol object as a parameter to enable this. + +See the DCCEXProtocol_Multi_Throttle_Control example for an idea of how this may be implemented. + +A further note is that controls and inputs should be passed to the protocol only, and should not update local references to object attributes (such as speed and direction), but rather that the responses to these inputs as received by the protocol and delegate events should be used to update local references. + +In this manner, the user of the throttle/client software will see the true results of their inputs which will reflect what EX-CommandStation is doing in response to those inputs. + +Retrieving and referring to object lists +---------------------------------------- + +To retrieve the various objects lists from EX-CommandStation, use the `getLists(bool rosterRequired, bool turnoutListRequired, bool routeListRequired, bool turntableListRequired)` method within your `loop()` function to ensure these are retrieved successfully. + +All objects are contained within linked lists and can be access via for loops: + +.. code-block:: cpp + + for (Loco* loco=dccexProtocol.roster->getFirst(); loco; loco=loco->getNext()) { + // loco methods are available here + } + + for (Turnout* turnout=dccexProtocol.turnouts->getFirst(); turnout; turnout=turnout->getNext()) { + // turnout methods are available here + } + + for (Route* route=dccexProtocol.route->getFirst(); route; route=route->getNext()) { + // route methods are available here + } + + for (Turntable* turntable=dccexProtocol.roster->getFirst(); turntable; turntable=turntable->getNext()) { + // turntable methods are available here + for (TurntableIndex* ttIndex=turntable->getFirstIndex(); ttIndex; ttIndex=ttIndex->getNextIndex()) { + // turntable index methods are available here + } + } + +Refer to the DCCEXProtocol_Roster_etc example for an idea of how this may be implemented. diff --git a/docs/robots.txt b/docs/robots.txt new file mode 100644 index 0000000..f1823dd --- /dev/null +++ b/docs/robots.txt @@ -0,0 +1,3 @@ +User-agent: * + +Sitemap: https://dcc-ex.com/DCCEXProtocol/sitemap.xml diff --git a/examples/DCCEXProtocol_Basic/DCCEXProtocol_Basic.ino b/examples/DCCEXProtocol_Basic/DCCEXProtocol_Basic.ino index 86157ac..991e157 100644 --- a/examples/DCCEXProtocol_Basic/DCCEXProtocol_Basic.ino +++ b/examples/DCCEXProtocol_Basic/DCCEXProtocol_Basic.ino @@ -32,7 +32,8 @@ void setup() { Serial.println("Connecting to WiFi.."); WiFi.begin(ssid, password); while(WiFi.status() != WL_CONNECTED) delay(1000); - Serial.print("Connected with IP: "); Serial.println(WiFi.localIP()); + Serial.print("Connected with IP: "); + Serial.println(WiFi.localIP()); // Connect to the server Serial.println("Connecting to the server..."); diff --git a/src/DCCEXProtocol.h b/src/DCCEXProtocol.h index 1c57391..6d063f7 100644 --- a/src/DCCEXProtocol.h +++ b/src/DCCEXProtocol.h @@ -55,7 +55,7 @@ enum TrackManagerMode { PROG, // Programming DCC track mode DC, // DC mode DCX, // Reverse polarity DC mode - OFF, // Track is off + NONE, // Track is unused }; /// @brief Nullstream class for initial DCCEXProtocol instantiation to direct streams to nothing @@ -64,30 +64,30 @@ class NullStream : public Stream { /// @brief Constructor for the NullStream object NullStream() {} - /// @brief - /// @return + /// @brief Dummy availability check + /// @return Returns false (0) always int available() { return 0; } - /// @brief + /// @brief Dummy flush method void flush() {} - /// @brief - /// @return + /// @brief Dummy peek method + /// @return Returns -1 always int peek() { return -1; } - /// @brief - /// @return + /// @brief Dummy read method + /// @return Returns -1 always int read() { return -1; } - /// @brief - /// @param c - /// @return + /// @brief Dummy write method for single int + /// @param c Number received + /// @return Returns 1 always size_t write(uint8_t c) { return 1; } - /// @brief - /// @param buffer - /// @param size - /// @return + /// @brief Dummy write method for buffered input + /// @param buffer Buffer received + /// @param size Size of buffer + /// @return Returns size of buffer always size_t write(const uint8_t *buffer, size_t size) { return size; } }; diff --git a/src/DCCEXTurnouts.h b/src/DCCEXTurnouts.h index 3e97126..2fe013f 100644 --- a/src/DCCEXTurnouts.h +++ b/src/DCCEXTurnouts.h @@ -34,13 +34,13 @@ /// @brief Class to contain and maintain the various Turnout/Point attributes and methods class Turnout { public: - /// @brief Constructor + /// @brief Constructor for a Turnout object /// @param id Turnout ID /// @param thrown true (thrown)|false (closed) Turnout(int id, bool thrown); /// @brief Set thrown state (true thrown, false closed) - /// @param isThrown true|false + /// @param thrown true|false void setThrown(bool thrown); /// @brief Set turnout name diff --git a/src/DCCEXTurntables.h b/src/DCCEXTurntables.h index d365934..e5826f9 100644 --- a/src/DCCEXTurntables.h +++ b/src/DCCEXTurntables.h @@ -106,8 +106,8 @@ class Turntable { /// @return ID of the current index int getIndex(); - /// @brief Set the number of indexes the turntable has defined - /// @param indexCount Count of the indexes defined + /// @brief Set the number of indexes the turntable has defined (from the \ command response) + /// @param numberOfIndexes Count of the indexes defined for the turntable including home void setNumberOfIndexes(int numberOfIndexes); /// @brief Get the number of indexes defined for the turntable @@ -134,8 +134,8 @@ class Turntable { /// @return Count of turntables int getCount(); - /// @brief Get the count of indexes added to the index list - /// @return Count of indexes received for this turntable + /// @brief Get the count of indexes added to the index list (counted from the \ command response) + /// @return Count of indexes received for this turntable including home int getIndexCount(); /// @brief Get the first turntable object