├── .github ├── FUNDING.yml └── workflows │ ├── label-sponsors.yml │ ├── main.yml │ └── sha.yml ├── GITHUB_SHA.h ├── .gitattributes ├── Release_Notes ├── DCC-EX v5.4 Release Notes.xlsx ├── duinoNodes.md ├── TCA8418.md ├── ThrottleAssists.md ├── TM1638.md ├── NeoPixel.md └── release_notes_v3.0.0.md ├── Release - Architecture Doc ├── CommandStation-EX-Arch-v1-0.pdf ├── CommandStation-EX-Arch-v1-0.vsd ├── Rough Release-Notes.md └── Prod-Release-Notes.md ├── CamParser.h ├── .gitignore ├── install_via_powershell.cmd ├── objdump.sh ├── objdump.bat ├── DIAG.h ├── DCCDecoder.h ├── DisplayInterface.cpp ├── LCN.h ├── TemplateForEnums.h ├── StringBuffer.h ├── EXRAIL.h ├── StringBuffer.cpp ├── WifiESP32.h ├── SerialManager.h ├── LocoTable.h ├── IO_Servo.cpp ├── EEStore.h ├── DCCEX.h ├── .travis.yml ├── IO_EncoderThrottle.h ├── EXRAILSensor.h ├── StringFormatter.h ├── Outputs.h ├── WifiInterface.h ├── RingStream.h ├── Sniffer.h ├── Display_Implementation.h ├── DCCRMT.h ├── DCCPacket.h ├── README.md ├── Display.h ├── LCN.cpp ├── IO_DCCAccessory.cpp ├── EthernetInterface.h ├── CommandDistributor.h ├── WifiInboundHandler.h ├── LiquidCrystal_I2C.h ├── SSD1306Ascii.h ├── myAutomation.example.h ├── CONTRIBUTING.md ├── IO_trainbrains.h ├── DCCEXParser.h ├── WiThrottle.h ├── CamParser.cpp ├── IO_MCP23008.h ├── DisplayInterface.h ├── KeywordHasher.h ├── Sensors.h ├── EEStore.cpp ├── IO_EXFastclock.h ├── FSH.h ├── EXRAILSensor.cpp ├── IO_TM1638.h ├── IO_PCF8574.h ├── IO_MCP23017.h ├── IO_PCA9555.h ├── IO_PCF8575.h ├── myEX-Turntable.example.h ├── installer.sh ├── DCCWaveform.h ├── LocoTable.cpp ├── SerialManager.cpp ├── TrackManager.h ├── IO_EncoderThrottle.cpp └── DCC.h /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: DCC-EX 2 | patreon: dccex 3 | -------------------------------------------------------------------------------- /GITHUB_SHA.h: -------------------------------------------------------------------------------- 1 | #define GITHUB_SHA "devel-202511252013Z" 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | *.svg -text 4 | -------------------------------------------------------------------------------- /Release_Notes/DCC-EX v5.4 Release Notes.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DCC-EX/CommandStation-EX/master/Release_Notes/DCC-EX v5.4 Release Notes.xlsx -------------------------------------------------------------------------------- /Release - Architecture Doc/CommandStation-EX-Arch-v1-0.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DCC-EX/CommandStation-EX/master/Release - Architecture Doc/CommandStation-EX-Arch-v1-0.pdf -------------------------------------------------------------------------------- /Release - Architecture Doc/CommandStation-EX-Arch-v1-0.vsd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DCC-EX/CommandStation-EX/master/Release - Architecture Doc/CommandStation-EX-Arch-v1-0.vsd -------------------------------------------------------------------------------- /CamParser.h: -------------------------------------------------------------------------------- 1 | #ifndef CamParser_H 2 | #define CamParser_H 3 | #include 4 | #include "IODevice.h" 5 | 6 | class CamParser { 7 | public: 8 | static bool parseN(Print * stream, byte paramCount, int16_t p[]); 9 | }; 10 | 11 | 12 | #endif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Release/* 2 | .ino.cpp 3 | .pioenvs 4 | .piolibdeps 5 | .clang_complete 6 | .gcc-flags.json 7 | .pio/ 8 | .vscode/ 9 | config.h 10 | mySetup.cpp 11 | myHal.cpp 12 | myFilter.cpp 13 | my*.h 14 | !my*.example.h 15 | compile_commands.json 16 | newcode.txt.old 17 | UserAddin.txt 18 | -------------------------------------------------------------------------------- /.github/workflows/label-sponsors.yml: -------------------------------------------------------------------------------- 1 | name: Label sponsors 2 | on: 3 | pull_request: 4 | types: [opened] 5 | issues: 6 | types: [opened] 7 | jobs: 8 | build: 9 | name: is-sponsor-label 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: JasonEtco/is-sponsor-label-action@v1.2.0 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | -------------------------------------------------------------------------------- /install_via_powershell.cmd: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | FOR /f "tokens=*" %%a IN ('powershell Get-ExecutionPolicy -Scope CurrentUser') DO SET PS_POLICY=%%a 4 | 5 | IF NOT %PS_POLICY=="Bypass" ( 6 | powershell Set-ExecutionPolicy -Scope CurrentUser Bypass 7 | ) 8 | 9 | powershell %~dp0%installer.ps1 10 | 11 | IF NOT %PS_POLICY=="Bypass" ( 12 | powershell Set-ExecutionPolicy -Scope CurrentUser %PS_POLICY% 13 | ) 14 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Install Python Wheel 13 | run: pip install wheel 14 | - name: Install PlatformIO Core 15 | run: pip install -U platformio 16 | - name: Copy generic config over 17 | run: cp config.example.h config.h 18 | - name: Compile Command Station (AVR) 19 | run: python -m platformio run 20 | -------------------------------------------------------------------------------- /objdump.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Find avr-objdump that matches the installed arduino binary 4 | ARDUINOBIN=$(ls -l $(type -p arduino)| awk '{print $NF ; exit 0}') 5 | PATH=$(dirname "$ARDUINOBIN")/hardware/tools/avr/bin:$PATH 6 | 7 | LASTBUILD=$(ls -tr /tmp/arduino_build_*/*.ino.elf | tail -1) 8 | avr-objdump --private=mem-usage "$LASTBUILD" 9 | 10 | for segment in .text .data .bss ; do 11 | echo '++++++++++++++++++++++++++++++++++' 12 | avr-objdump -x -C "$LASTBUILD" | awk '$2 == "'$segment'" && $3 != 0 {print $3,$2} ; $4 == "'$segment'" && $5 != 0 { print $5,$6}' | sort -r 13 | done 14 | -------------------------------------------------------------------------------- /objdump.bat: -------------------------------------------------------------------------------- 1 | ECHO ON 2 | FOR /F "delims=" %%i IN ('dir %TMP%\arduino_build_* /b /ad-h /t:c /od') DO SET a=%%i 3 | echo Most recent subfolder: %a% >%TMP%\OBJDUMP_%a%.txt 4 | SET ELF=%TMP%\%a%\CommandStation-EX.ino.elf 5 | set PATH="C:\Program Files (x86)\Arduino\hardware\tools\avr\bin\";%PATH% 6 | avr-objdump --private=mem-usage %ELF% >>%TMP%\OBJDUMP_%a%.txt 7 | ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt 8 | avr-objdump -x -C %ELF% | find ".text" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt 9 | ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt 10 | avr-objdump -x -C %ELF% | find ".data" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt 11 | ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt 12 | avr-objdump -x -C %ELF% | find ".bss" | sort /+25 /R >>%TMP%\OBJDUMP_%a%.txt 13 | ECHO ++++++++++++++++++++++++++++++++++ >>%TMP%\OBJDUMP_%a%.txt 14 | avr-objdump -D -S %ELF% >>%TMP%\OBJDUMP_%a%.txt 15 | %TMP%\OBJDUMP_%a%.txt 16 | EXIT 17 | -------------------------------------------------------------------------------- /DIAG.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021 Fred Decker 3 | * © 2020 Chris Harlow 4 | * All rights reserved. 5 | * 6 | * This file is part of CommandStation-EX 7 | * 8 | * This is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * It is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with CommandStation. If not, see . 20 | */ 21 | #ifndef DIAG_h 22 | #define DIAG_h 23 | 24 | #include "StringFormatter.h" 25 | #define DIAG StringFormatter::diag 26 | #define LCD StringFormatter::lcd 27 | #define SCREEN StringFormatter::lcd2 28 | #endif 29 | -------------------------------------------------------------------------------- /DCCDecoder.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2025 Harald Barth 3 | * 4 | * This file is part of CommandStation-EX 5 | * 6 | * This is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * It is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with CommandStation. If not, see . 18 | */ 19 | #ifdef ARDUINO_ARCH_ESP32 20 | #include 21 | #include "DCCPacket.h" 22 | 23 | class DCCDecoder { 24 | public: 25 | static bool parse(DCCPacket &p); 26 | static inline void onoff(bool on) {active = on;}; 27 | private: 28 | static bool active; 29 | }; 30 | #endif // ARDUINO_ARCH_ESP32 31 | -------------------------------------------------------------------------------- /DisplayInterface.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021 Neil McKechnie 3 | * © 2021 Chris Harlow 4 | * All rights reserved. 5 | * 6 | * This file is part of CommandStation-EX 7 | * 8 | * This is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * It is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with CommandStation. If not, see . 20 | */ 21 | 22 | #include "DisplayInterface.h" 23 | 24 | // Install null display driver initially - will be replaced if required. 25 | DisplayInterface *DisplayInterface::_displayHandler = new DisplayInterface(); 26 | 27 | uint8_t DisplayInterface::_selectedDisplayNo = 255; 28 | -------------------------------------------------------------------------------- /.github/workflows/sha.yml: -------------------------------------------------------------------------------- 1 | name: SHA 2 | 3 | # Run this workflow ever time code is pushed to a branch 4 | # other than `main` in your repository 5 | on: push 6 | 7 | jobs: 8 | # Set the job key. The key is displayed as the job name 9 | # when a job name is not provided 10 | sha: 11 | # Name the Job 12 | name: Commit SHA 13 | # Set the type of machine to run on 14 | runs-on: ubuntu-latest 15 | 16 | if: github.ref == 'refs/heads/master' 17 | steps: 18 | # Checks out a copy of your repository on the ubuntu-latest machine 19 | - name: Checkout code 20 | uses: actions/checkout@v2 21 | 22 | - name: Create SHA File 23 | run: | 24 | sha=$(git rev-parse --short "$GITHUB_SHA") 25 | echo "#define GITHUB_SHA \"$sha\"" > GITHUB_SHA.h 26 | 27 | - uses: EndBug/add-and-commit@v8 # You can change this to use a specific version 28 | with: 29 | add: 'GITHUB_SHA.h' 30 | message: 'Committing a SHA' 31 | commit: --amend 32 | 33 | env: 34 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Leave this line unchanged 35 | -------------------------------------------------------------------------------- /LCN.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021 Harald Barth 3 | * © 2021 Fred Decker 4 | * All rights reserved. 5 | * 6 | * This file is part of CommandStation-EX 7 | * 8 | * This is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * It is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with CommandStation. If not, see . 20 | */ 21 | #ifndef LCN_h 22 | #define LCN_h 23 | #include 24 | 25 | class LCN { 26 | public: 27 | static void init(Stream & lcnstream); 28 | static void loop(); 29 | static void send(char opcode, int id, bool state); 30 | private : 31 | static bool firstLoop; 32 | static Stream * stream; 33 | static int id; 34 | }; 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /TemplateForEnums.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2024, Harald Barth. All rights reserved. 3 | * 4 | * This file is part of DCC-EX 5 | * 6 | * This is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * It is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with CommandStation. If not, see . 18 | */ 19 | #ifndef TemplateForEnums 20 | #define TemplateForEnums 21 | template inline T operator~ (T a) { return (T)~(int)a; } 22 | template inline T operator| (T a, T b) { return (T)((int)a | (int)b); } 23 | template inline T operator& (T a, T b) { return (T)((int)a & (int)b); } 24 | template inline T operator^ (T a, T b) { return (T)((int)a ^ (int)b); } 25 | #endif 26 | 27 | -------------------------------------------------------------------------------- /StringBuffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2022 Chris Harlow 3 | * All rights reserved. 4 | * 5 | * This file is part of DCC++EX 6 | * 7 | * This is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * It is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with CommandStation. If not, see . 19 | */ 20 | 21 | #ifndef StringBuffer_h 22 | #define StringBuffer_h 23 | #include 24 | 25 | class StringBuffer : public Print { 26 | public: 27 | StringBuffer(); 28 | // Override Print default 29 | virtual size_t write(uint8_t b); 30 | void flush(); 31 | char * getString(); 32 | private: 33 | static const int buffer_max=64; // enough for long text msgs to throttles 34 | int16_t _pos_write; 35 | char _buffer[buffer_max+2]; 36 | }; 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /EXRAIL.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021 Fred Decker 3 | * All rights reserved. 4 | * 5 | * This file is part of CommandStation-EX 6 | * 7 | * This is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * It is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with CommandStation. If not, see . 19 | */ 20 | 21 | #ifndef EXRAIL_H 22 | #define EXRAIL_H 23 | 24 | #if defined(EXRAIL_ACTIVE) 25 | #include "EXRAIL2.h" 26 | 27 | class RMFT { 28 | public: 29 | static void inline begin() {RMFT2::begin();} 30 | static void inline loop() {RMFT2::loop();} 31 | }; 32 | 33 | #include "EXRAILMacros.h" 34 | 35 | #else 36 | // Dummy RMFT 37 | class RMFT { 38 | public: 39 | static void inline begin() {} 40 | static void inline loop() {} 41 | }; 42 | #endif 43 | #endif 44 | -------------------------------------------------------------------------------- /StringBuffer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2022 Chris Harlow 3 | * All rights reserved. 4 | * 5 | * This file is part of DCC-EX CommandStation-EX 6 | * 7 | * This is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * It is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with CommandStation. If not, see . 19 | */ 20 | 21 | #include "StringBuffer.h" 22 | #include "DIAG.h" 23 | 24 | StringBuffer::StringBuffer() { 25 | flush(); 26 | }; 27 | 28 | char * StringBuffer::getString() { 29 | return _buffer; 30 | } 31 | 32 | void StringBuffer::flush() { 33 | _pos_write=0; 34 | _buffer[0]='\0'; 35 | } 36 | 37 | size_t StringBuffer::write(uint8_t b) { 38 | if (_pos_write>=buffer_max) return 0; 39 | _buffer[_pos_write] = b; 40 | ++_pos_write; 41 | _buffer[_pos_write]='\0'; 42 | return 1; 43 | } 44 | -------------------------------------------------------------------------------- /WifiESP32.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021 Harald Barth 3 | * © 2023 Nathan Kellenicki 4 | * 5 | * This file is part of CommandStation-EX 6 | * 7 | * This is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * It is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with CommandStation. If not, see . 19 | */ 20 | 21 | #if defined(ARDUINO_ARCH_ESP32) 22 | #ifndef WifiESP32_h 23 | #define WifiESP32_h 24 | 25 | #include 26 | #include "FSH.h" 27 | 28 | class WifiESP 29 | { 30 | 31 | public: 32 | static bool setup(const char *wifiESSID, 33 | const char *wifiPassword, 34 | const char *hostname, 35 | const int port, 36 | const byte channel, 37 | const bool forceAP); 38 | static void loop(); 39 | private: 40 | static void teardown(); 41 | static bool wifiUp; 42 | static WiFiServer *server; 43 | }; 44 | #endif //WifiESP8266_h 45 | #endif //ESP8266 46 | -------------------------------------------------------------------------------- /SerialManager.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021 Chris Harlow 3 | * All rights reserved. 4 | * 5 | * This file is part of DCC++EX 6 | * 7 | * This is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * It is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with CommandStation. If not, see . 19 | */ 20 | 21 | #ifndef SerialManager_h 22 | #define SerialManager_h 23 | 24 | #include "Arduino.h" 25 | #include "defines.h" 26 | 27 | 28 | #ifndef COMMAND_BUFFER_SIZE 29 | #define COMMAND_BUFFER_SIZE 100 30 | #endif 31 | 32 | class SerialManager { 33 | public: 34 | static void init(); 35 | static void loop(); 36 | static void broadcast(char * stringBuffer); 37 | 38 | private: 39 | static SerialManager * first; 40 | SerialManager(Stream * myserial); 41 | void loop2(); 42 | void broadcast2(char * stringBuffer); 43 | Stream * serial; 44 | SerialManager * next; 45 | byte bufferLength; 46 | byte buffer[COMMAND_BUFFER_SIZE]; 47 | byte inCommandPayload; 48 | }; 49 | #endif 50 | -------------------------------------------------------------------------------- /LocoTable.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2023 Harald Barth 2 | * 3 | * This source is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This source is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this software. If not, see 15 | * . 16 | */ 17 | #include 18 | 19 | #include "DCC.h" // fetch MAX_LOCOS from there 20 | 21 | class LocoTable { 22 | public: 23 | void forgetLoco(int cab) { 24 | int reg=lookupSpeedTable(cab, false); 25 | if (reg>=0) speedTable[reg].loco=0; 26 | } 27 | static int lookupSpeedTable(int locoId, bool autoCreate); 28 | static bool updateLoco(int loco, byte speedCode); 29 | static bool updateFunc(int loco, byte func, int shift); 30 | static void dumpTable(Stream *output); 31 | 32 | private: 33 | struct LOCO 34 | { 35 | int loco; 36 | byte speedCode; 37 | byte groupFlags; 38 | unsigned long functions; 39 | unsigned int funccounter; 40 | unsigned int speedcounter; 41 | }; 42 | static LOCO speedTable[MAX_LOCOS]; 43 | static int highestUsedReg; 44 | }; 45 | -------------------------------------------------------------------------------- /IO_Servo.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2023, Neil McKechnie. All rights reserved. 3 | * 4 | * This file is part of DCC++EX API 5 | * 6 | * This is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * It is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with CommandStation. If not, see . 18 | */ 19 | 20 | #include "IO_Servo.h" 21 | #include "FSH.h" 22 | 23 | // Profile for a bouncing signal or turnout 24 | // The profile below is in the range 0-100% and should be combined with the desired limits 25 | // of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here, 26 | // i.e. the bounce is the same on the down action as on the up action. First entry isn't used. 27 | // 28 | // Note: This has been put into its own .CPP file to ensure that duplicates aren't created 29 | // if the IO_Servo.h library is #include'd in multiple source files. 30 | // 31 | const uint8_t FLASH Servo::_bounceProfile[30] = 32 | {0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100}; 33 | -------------------------------------------------------------------------------- /EEStore.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021 Neil McKechnie 3 | * © 2021 Fred Decker 4 | * © 2020-2021 Harald Barth 5 | * © 2020 Chris Harlow 6 | * All rights reserved. 7 | * 8 | * This file is part of CommandStation-EX 9 | * 10 | * This is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * It is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with CommandStation. If not, see . 22 | */ 23 | #ifndef DISABLE_EEPROM 24 | #ifndef EEStore_h 25 | #define EEStore_h 26 | 27 | #include 28 | 29 | #if defined(ARDUINO_ARCH_SAMC) 30 | #include 31 | extern ExternalEEPROM EEPROM; 32 | #else 33 | #include 34 | #endif 35 | 36 | #define EESTORE_ID "DCC++1" 37 | 38 | struct EEStoreData{ 39 | char id[sizeof(EESTORE_ID)]; 40 | uint16_t nTurnouts; 41 | uint16_t nSensors; 42 | uint16_t nOutputs; 43 | }; 44 | 45 | struct EEStore{ 46 | static EEStore *eeStore; 47 | EEStoreData data; 48 | static int eeAddress; 49 | static void init(); 50 | static void reset(); 51 | static int pointer(); 52 | static void advance(int); 53 | static void store(); 54 | static void clear(); 55 | static void dump(int); 56 | }; 57 | 58 | #endif 59 | #endif // DISABLE_EEPROM 60 | -------------------------------------------------------------------------------- /DCCEX.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021 Fred Decker 3 | * © 2020-2021 Harald Barth 4 | * © 2020-2021 Chris Harlow 5 | * All rights reserved. 6 | * 7 | * This file is part of CommandStation-EX 8 | * 9 | * This is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * It is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with CommandStation. If not, see . 21 | */ 22 | 23 | // This include is intended to visually simplify the .ino for the end users. 24 | // If there were any #ifdefs required they are much better handled in here. 25 | 26 | #ifndef DCCEX_h 27 | #define DCCEX_h 28 | 29 | #include "defines.h" 30 | #include "DCC.h" 31 | #include "DIAG.h" 32 | #include "DCCEXParser.h" 33 | #include "SerialManager.h" 34 | #include "version.h" 35 | #ifndef ARDUINO_ARCH_ESP32 36 | #include "WifiInterface.h" 37 | #else 38 | #include "WifiESP32.h" 39 | #endif 40 | #if ETHERNET_ON == true 41 | #include "EthernetInterface.h" 42 | #endif 43 | #include "Display_Implementation.h" 44 | #include "LCN.h" 45 | #include "IODevice.h" 46 | #include "Turnouts.h" 47 | #include "Sensors.h" 48 | #include "Outputs.h" 49 | #include "CommandDistributor.h" 50 | #include "TrackManager.h" 51 | #include "DCCTimer.h" 52 | #include "KeywordHasher.h" 53 | #include "EXRAIL.h" 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /Release_Notes/duinoNodes.md: -------------------------------------------------------------------------------- 1 | Using Lew's Duino Gear boards: 2 | 3 | 1. DNIN8 Input 4 | This is a shift-register implementation of a digital input collector. 5 | Multiple DNIN8 may be connected in sequence but it is IMPORTANT that the software 6 | configuratuion correctly represents the number of boards connected otherwise the results will be meaningless. 7 | 8 | Use in myAnimation.h 9 | 10 | HAL(IO_DNIN8, firstVpin, numPins, clockPin, latchPin, dataPin) 11 | e.g. 12 | HAL(IO_DNIN8, 400, 16, 40, 42, 44) 13 | 14 | OR Use in myHal.cpp 15 | IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin) 16 | 17 | 18 | 19 | This will create virtaul pins 400-415 using two DNIN8 boards connected in sequence. 20 | Vpins 400-407 will be on the first board (closest to the CS) and 408-415 on the second. 21 | 22 | Note: 16 pins uses two boards. You may specify a non-multiple-of-8 pins but this will be rounded up to a multiple of 8 and you must connect ONLY the number of boards that this takes. 23 | 24 | This example uses Arduino GPIO pins 40,42,44 as these are conveniently side-by-side on a Mega which is easier when you are using a 3 strand cable. 25 | 26 | The DNIN8K module works the same but you must use DNIN8K in the HAL setup instead of DNIN8. NO you cant mix 8 and 8k versions in the same string of boards but you can create another string of boards. 27 | 28 | 29 | DNOU8 works the same way, 30 | Use in myAnimation.h 31 | 32 | HAL(IO_DNOU8, firstVpin, numPins, clockPin, latchPin, dataPin) 33 | e.g. 34 | HAL(IO_DNIN8, 450, 16, 45, 47, 49) 35 | 36 | OR Use in myHal.cpp 37 | IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin) 38 | 39 | This creates a string of input pins 450-465. Note the clock/latch/data pins must be different to any DNIN8/k pins. 40 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < https://docs.platformio.org/page/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < https://docs.platformio.org/page/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < https://docs.platformio.org/page/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choose one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # - platformio update 39 | # 40 | # script: 41 | # - platformio run 42 | 43 | 44 | # 45 | # Template #2: The project is intended to be used as a library with examples. 46 | # 47 | 48 | # language: python 49 | # python: 50 | # - "2.7" 51 | # 52 | # sudo: false 53 | # cache: 54 | # directories: 55 | # - "~/.platformio" 56 | # 57 | # env: 58 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 59 | # - PLATFORMIO_CI_SRC=examples/file.ino 60 | # - PLATFORMIO_CI_SRC=path/to/test/directory 61 | # 62 | # install: 63 | # - pip install -U platformio 64 | # - platformio update 65 | # 66 | # script: 67 | # - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N 68 | -------------------------------------------------------------------------------- /IO_EncoderThrottle.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2024, Chris Harlow. All rights reserved. 3 | * 4 | * This file is part of EX-CommandStation 5 | * 6 | * This is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * It is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with CommandStation. If not, see . 18 | */ 19 | 20 | /* 21 | * The IO_EncoderThrottle device driver uses a rotary encoder connected to vpins 22 | * to drive a loco. 23 | * Loco id is selected by writeAnalog. 24 | */ 25 | 26 | #ifndef IO_EncoderThrottle_H 27 | #define IO_EncoderThrottle_H 28 | #include "IODevice.h" 29 | 30 | class EncoderThrottle : public IODevice { 31 | public: 32 | 33 | static void create(VPIN firstVpin, int dtPin, int clkPin, int clickPin, byte notch=10); 34 | 35 | private: 36 | int _dtPin,_clkPin,_clickPin, _locoid, _notch,_prevpinstate; 37 | enum {xrSTOP,xrFWD,xrREV} _stopState; 38 | byte _rocoState; 39 | 40 | // Constructor 41 | EncoderThrottle(VPIN firstVpin, int dtPin, int clkPin, int clickPin, byte notch); 42 | 43 | void _loop(unsigned long currentMicros) override ; 44 | 45 | // Selocoid as analog value to start drive 46 | // use 47 | void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override; 48 | 49 | void _display() override ; 50 | 51 | }; 52 | 53 | #endif 54 | -------------------------------------------------------------------------------- /EXRAILSensor.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2024 Chris Harlow 3 | * All rights reserved. 4 | * 5 | * This file is part of CommandStation-EX 6 | * 7 | * This is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * It is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with CommandStation. If not, see . 19 | */ 20 | 21 | #ifndef EXRAILSensor_h 22 | #define EXRAILSensor_h 23 | #include "IODevice.h" 24 | class EXRAILSensor { 25 | static EXRAILSensor * firstSensor; 26 | static EXRAILSensor * readingSensor; 27 | static unsigned long lastReadCycle; 28 | 29 | public: 30 | static void checkAll(); 31 | 32 | EXRAILSensor(VPIN _pin, int _progCounter, bool _onChange); 33 | bool check(); 34 | 35 | private: 36 | static const unsigned int cycleInterval = 10000; // min time between consecutive reads of each sensor in microsecs. 37 | // should not be less than device scan cycle time. 38 | static const byte minReadCount = 4; // number of additional scans before acting on change 39 | // E.g. 1 means that a change is ignored for one scan and actioned on the next. 40 | // Max value is 63 41 | 42 | EXRAILSensor* nextSensor; 43 | VPIN pin; 44 | int progCounter; 45 | bool active; 46 | bool inputState; 47 | bool onChange; 48 | byte latchDelay; 49 | }; 50 | #endif 51 | -------------------------------------------------------------------------------- /StringFormatter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2020, Chris Harlow. All rights reserved. 3 | * 4 | * This file is part of Asbelos DCC API 5 | * 6 | * This is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * It is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with CommandStation. If not, see . 18 | */ 19 | #ifndef StringFormatter_h 20 | #define StringFormatter_h 21 | #include 22 | #include "FSH.h" 23 | #include "RingStream.h" 24 | #include "Display.h" 25 | class Diag { 26 | public: 27 | static bool ACK; 28 | static bool CMD; 29 | static bool WIFI; 30 | static bool WITHROTTLE; 31 | static bool ETHERNET; 32 | static bool LCN; 33 | static bool SNIFFER; 34 | }; 35 | 36 | class StringFormatter 37 | { 38 | public: 39 | static void send(Print * serial, const FSH* input...); 40 | static void send(Print & serial, const FSH* input...); 41 | 42 | static void printEscapes(Print * serial,char * input); 43 | static void printEscapes(Print * serial,const FSH* input); 44 | static void printEscape(Print * serial, char c); 45 | 46 | // DIAG support 47 | static void diag( const FSH* input...); 48 | static void lcd(byte row, const FSH* input...); 49 | static void lcd2(uint8_t display, byte row, const FSH* input...); 50 | static void printEscapes(char * input); 51 | static void printEscape( char c); 52 | static void printHex(Print * stream,uint16_t value); 53 | 54 | private: 55 | static void send2(Print * serial, const FSH* input,va_list args); 56 | static void printPadded(Print* stream, long value, byte width, bool formatLeft); 57 | }; 58 | #endif 59 | -------------------------------------------------------------------------------- /Outputs.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021 Harald Barth 3 | * © 2021 Fred Decker 4 | * © 2020 Chris Harlow 5 | * All rights reserved. 6 | * 7 | * This file is part of Asbelos DCC API 8 | * 9 | * This is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * It is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with CommandStation. If not, see . 21 | */ 22 | #ifndef Outputs_h 23 | #define Outputs_h 24 | 25 | #include 26 | #include "IODevice.h" 27 | 28 | struct OutputData { 29 | union { 30 | uint8_t oStatus; // (Bit 0=Invert, Bit 1=Set state to default, Bit 2=default state, Bit 7=active) 31 | struct { 32 | unsigned int flags : 7; // Bit 0=Invert, Bit 1=Set state to default, Bit 2=default state 33 | unsigned int : 1; 34 | }; 35 | struct { 36 | unsigned int invert : 1; 37 | unsigned int setDefault : 1; 38 | unsigned int defaultValue : 1; 39 | unsigned int: 4; 40 | unsigned int active : 1; 41 | }; 42 | }; 43 | uint16_t id; 44 | VPIN pin; 45 | }; 46 | 47 | 48 | class Output{ 49 | public: 50 | void activate(uint16_t s); 51 | bool isActive(); 52 | static Output* get(uint16_t); 53 | static bool remove(uint16_t); 54 | #ifndef DISABLE_EEPROM 55 | static void load(); 56 | static void store(); 57 | #endif 58 | static Output *create(uint16_t, VPIN, int, int=0); 59 | static Output *firstOutput; 60 | struct OutputData data; 61 | Output *nextOutput; 62 | static void printAll(Print *); 63 | private: 64 | uint16_t num; // EEPROM address of oStatus in OutputData struct, or zero if not stored. 65 | 66 | }; // Output 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /WifiInterface.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2020-2021 Chris Harlow 3 | * © 2020, Harald Barth. 4 | * © 2023 Nathan Kellenicki 5 | * All rights reserved. 6 | * 7 | * This file is part of CommandStation-EX 8 | * 9 | * This is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * It is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with CommandStation. If not, see . 21 | */ 22 | #ifndef WifiInterface_h 23 | #define WifiInterface_h 24 | #include "FSH.h" 25 | #include "DCCEXParser.h" 26 | #include 27 | //#include 28 | 29 | enum wifiSerialState { WIFI_NOAT, WIFI_DISCONNECTED, WIFI_CONNECTED }; 30 | 31 | class WifiInterface 32 | { 33 | 34 | public: 35 | static bool setup(long serial_link_speed, 36 | const FSH *wifiESSID, 37 | const FSH *wifiPassword, 38 | const FSH *hostname, 39 | const int port, 40 | const byte channel, 41 | const bool forceAP); 42 | static void loop(); 43 | static void ATCommand(HardwareSerial * stream,const byte *command); 44 | 45 | private: 46 | static wifiSerialState setup(Stream &setupStream, const FSH *SSSid, const FSH *password, 47 | const FSH *hostname, int port, byte channel, bool forceAP); 48 | static Stream *wifiStream; 49 | static DCCEXParser parser; 50 | static wifiSerialState setup2(const FSH *SSSid, const FSH *password, 51 | const FSH *hostname, int port, byte channel, bool forceAP); 52 | static bool checkForOK(const unsigned int timeout, bool echo, bool escapeEcho = true); 53 | static bool checkForOK(const unsigned int timeout, const FSH *waitfor, bool echo, bool escapeEcho = true); 54 | static bool connected; 55 | }; 56 | #endif 57 | -------------------------------------------------------------------------------- /Release - Architecture Doc/Rough Release-Notes.md: -------------------------------------------------------------------------------- 1 | # CommandStation-EX Release Notes 2 | 3 | ## v3.0.0 4 | 5 | - **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients. 6 | - **Withrottle Integrations** - Act as a host for four WiThrottle clients concurrently. 7 | - **Add LCD/OLED support** - OLED supported on Mega only 8 | - **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second. 9 | - **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers 10 | - **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts. 11 | - **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs 12 | - **New, simpler function command** - `````` command allows setting functions based on their number, not based on a code as in `````` 13 | - **Function reminders** - Function reminders are sent in addition to speed reminders 14 | - **Functions to F28** - All NMRA functions are now supported 15 | - **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them 16 | - **Diagnostic `````` commands** - See documentation for a full list of new diagnostic commands 17 | - **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM 18 | - **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers 19 | - **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code 20 | - **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM 21 | - **Fix EEPROM bugs** 22 | - **Support for more decoders** - Support for 20 (Uno) or 50 (Mega) mobile decoders, number automaticlaly recognized by JMRI. 23 | - **Automatic slot managment** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. `````` command added to release locos from memory. 24 | -------------------------------------------------------------------------------- /RingStream.h: -------------------------------------------------------------------------------- 1 | #ifndef RingStream_h 2 | #define RingStream_h 3 | /* 4 | * © 2020-2021 Chris Harlow 5 | * All rights reserved. 6 | * 7 | * This file is part of DCC-EX CommandStation-EX 8 | * 9 | * This is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * It is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with CommandStation. If not, see . 21 | */ 22 | 23 | #include 24 | #include "FSH.h" 25 | 26 | class RingStream : public Print { 27 | 28 | public: 29 | RingStream( const uint16_t len); 30 | static const int THIS_IS_A_RINGSTREAM=777; 31 | virtual size_t write(uint8_t b); 32 | 33 | // This availableForWrite function is subverted from its original intention so that a caller 34 | // can destinguish between a normal stream and a RingStream. 35 | // The Arduino compiler does not support runtime dynamic cast to perform 36 | // an instranceOf check. 37 | // This is necessary since the Print functions are mostly not virtual so 38 | // we cant override the print(__FlashStringHelper *) function. 39 | virtual int availableForWrite() override; 40 | using Print::write; 41 | size_t printFlash(const FSH * flashBuffer); 42 | int read(); 43 | int count(); 44 | int freeSpace(); 45 | void mark(uint8_t b); 46 | bool commit(); 47 | uint8_t peekTargetMark(); 48 | void flush(); 49 | void info(); 50 | byte readRawByte(); 51 | inline int peek() { 52 | if ((_pos_read==_pos_write) && !_overflow) return -1; // empty 53 | return _buffer[_pos_read]; 54 | }; 55 | static const byte NO_CLIENT=255; 56 | private: 57 | int _len; 58 | int _pos_write; 59 | int _pos_read; 60 | bool _overflow; 61 | int _mark; 62 | int _count; 63 | byte * _buffer; 64 | char * _flashInsert; 65 | byte _ringClient = NO_CLIENT; 66 | }; 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /Sniffer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2025 Harald Barth 3 | * 4 | * This file is part of CommandStation-EX 5 | * 6 | * This is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * It is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with CommandStation. If not, see . 18 | */ 19 | #ifdef ARDUINO_ARCH_ESP32 20 | #include 21 | #include 22 | #include "driver/mcpwm.h" 23 | #include "soc/mcpwm_struct.h" 24 | #include "soc/mcpwm_reg.h" 25 | 26 | #define MAXDCCPACKETLEN 8 27 | #include "DCCPacket.h" 28 | 29 | class Sniffer { 30 | public: 31 | Sniffer(byte snifferpin); 32 | void IRAM_ATTR processInterrupt(int32_t capticks, bool posedge); 33 | inline int32_t getTicks() { 34 | noInterrupts(); 35 | int32_t i = diffticks; 36 | interrupts(); 37 | return i; 38 | }; 39 | inline int64_t getDebug() { 40 | noInterrupts(); 41 | int64_t i = debugfield; 42 | interrupts(); 43 | return i; 44 | }; 45 | inline DCCPacket fetchPacket() { 46 | // if there is no new data, this will create a 47 | // packet with length 0 (which is no packet) 48 | DCCPacket p; 49 | noInterrupts(); 50 | if (!outpacket.empty()) { 51 | p = outpacket.front(); 52 | outpacket.pop_front(); 53 | } 54 | interrupts(); 55 | return p; 56 | }; 57 | bool inputActive(); 58 | private: 59 | // keep these vars in processInterrupt only 60 | uint64_t bitfield = 0; 61 | uint64_t debugfield = 0; 62 | int32_t diffticks; 63 | int32_t lastticks; 64 | bool lastedge; 65 | byte currentbyte = 0; 66 | byte dccbytes[MAXDCCPACKETLEN]; 67 | byte dcclen = 0; 68 | bool inpacket = false; 69 | // these vars are used as interface to other parts of sniffer 70 | byte halfbitcounter = 0; 71 | std::list outpacket; 72 | DCCPacket prevpacket; 73 | volatile unsigned long lastendofpacket = 0; // timestamp millis 74 | 75 | }; 76 | #endif 77 | -------------------------------------------------------------------------------- /Display_Implementation.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021, Chris Harlow, Neil McKechnie. All rights reserved. 3 | * 4 | * This file is part of CommandStation-EX 5 | * 6 | * This is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * It is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with CommandStation. If not, see . 18 | */ 19 | 20 | //////////////////////////////////////////////////////////////////////////////////// 21 | // This implementation is designed to be #included ONLY ONCE in the .ino 22 | // 23 | // It will create a driver implemntation and a shim class implementation. 24 | // This means that other classes can reference the shim without knowing 25 | // which library is involved. 26 | //////////////////////////////////////////////////////////////////////////////////// 27 | 28 | #ifndef LCD_Implementation_h 29 | #define LCD_Implementation_h 30 | #include "DisplayInterface.h" 31 | #include "SSD1306Ascii.h" 32 | #include "LiquidCrystal_I2C.h" 33 | 34 | 35 | // Implement the Display shim class as a singleton. 36 | // The DisplayInterface class implements a display handler with no code (null device); 37 | // The Display class sub-classes DisplayInterface to provide the common display code; 38 | // Then Display class talks to the specific device type classes: 39 | // SSD1306AsciiWire for I2C OLED driver with SSD1306 or SH1106 controllers; 40 | // LiquidCrystal_I2C for I2C LCD driver for HD44780 with PCF8574 'backpack'. 41 | 42 | #if defined(OLED_DRIVER) 43 | #define DISPLAY_START(xxx) { \ 44 | DisplayInterface *t = new Display(new SSD1306AsciiWire(OLED_DRIVER)); \ 45 | t->begin(); \ 46 | xxx; \ 47 | t->refresh(); \ 48 | } 49 | 50 | #elif defined(LCD_DRIVER) 51 | #define DISPLAY_START(xxx) { \ 52 | DisplayInterface *t = new Display(new LiquidCrystal_I2C(LCD_DRIVER)); \ 53 | t->begin(); \ 54 | xxx; \ 55 | t->refresh();} 56 | #else 57 | #define DISPLAY_START(xxx) { \ 58 | xxx; \ 59 | } 60 | 61 | #endif 62 | #endif // LCD_Implementation_h 63 | -------------------------------------------------------------------------------- /DCCRMT.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021-2022, Harald Barth. 3 | * 4 | * This file is part of DCC-EX 5 | * 6 | * This is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * It is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with CommandStation. If not, see . 18 | */ 19 | 20 | #if defined(ARDUINO_ARCH_ESP32) 21 | #pragma once 22 | #include 23 | #include "driver/rmt.h" 24 | #include "soc/rmt_reg.h" 25 | #include "soc/rmt_struct.h" 26 | #include "MotorDriver.h" // for class pinpair 27 | 28 | // make calculations easy and set up for microseconds 29 | #define RMT_CLOCK_DIVIDER 80 30 | #define DCC_1_HALFPERIOD 58 //4640 // 1 / 80000000 * 4640 = 58us 31 | #define DCC_0_HALFPERIOD 100 //8000 32 | 33 | class RMTChannel { 34 | public: 35 | RMTChannel(pinpair pins, bool isMain); 36 | bool addPin(byte pin, bool inverted=0); 37 | bool addPin(pinpair pins); 38 | void IRAM_ATTR RMTinterrupt(); 39 | void RMTprefill(); 40 | //int RMTfillData(dccPacket packet); 41 | int RMTfillData(const byte buffer[], byte byteCount, byte repeatCount); 42 | inline bool busy() { 43 | if (dataRepeat > 0) // we have still old work to do 44 | return true; 45 | return dataReady; 46 | }; 47 | inline void waitForDataCopy() { 48 | while(1) { // do nothing and wait for interrupt clearing dataReady to happen 49 | if (dataReady == false) 50 | break; 51 | } 52 | }; 53 | inline uint32_t packetCount() { return packetCounter; }; 54 | 55 | private: 56 | 57 | rmt_channel_t channel; 58 | // 3 types of data to send, preamble and then idle or data 59 | // if this is prog track, idle will contain reset instead 60 | rmt_item32_t *idle; 61 | byte idleLen; 62 | rmt_item32_t *preamble; 63 | byte preambleLen; 64 | rmt_item32_t *data; 65 | byte dataLen; 66 | byte maxDataLen; 67 | uint32_t packetCounter = 0; 68 | // flags 69 | volatile bool dataReady = false; // do we have real data available or send idle 70 | volatile byte dataRepeat = 0; 71 | }; 72 | #endif //ESP32 73 | -------------------------------------------------------------------------------- /DCCPacket.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2025 Harald Barth 3 | * 4 | * This file is part of CommandStation-EX 5 | * 6 | * This is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * It is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with CommandStation. If not, see . 18 | */ 19 | #include 20 | #ifndef DCCPacket_h 21 | #define DCCPacket_h 22 | #include 23 | #include "defines.h" 24 | 25 | class DCCPacket { 26 | public: 27 | DCCPacket() { 28 | _len = 0; 29 | _data = NULL; 30 | }; 31 | DCCPacket(byte *d, byte l) { 32 | _len = l; 33 | _data = new byte[_len]; 34 | for (byte n = 0; n<_len; n++) 35 | _data[n] = d[n]; 36 | }; 37 | DCCPacket(const DCCPacket &old) { 38 | _len = old._len; 39 | _data = new byte[_len]; 40 | for (byte n = 0; n<_len; n++) 41 | _data[n] = old._data[n]; 42 | }; 43 | DCCPacket &operator=(const DCCPacket &rhs) { 44 | if (this == &rhs) 45 | return *this; 46 | delete[]_data; 47 | _len = rhs._len; 48 | _data = new byte[_len]; 49 | for (byte n = 0; n<_len; n++) 50 | _data[n] = rhs._data[n]; 51 | return *this; 52 | }; 53 | ~DCCPacket() { 54 | if (_len) { 55 | delete[]_data; 56 | _len = 0; 57 | _data = NULL; 58 | } 59 | }; 60 | inline bool operator==(const DCCPacket &right) { 61 | if (_len != right._len) 62 | return false; 63 | if (_len == 0) 64 | return true; 65 | return (bcmp(_data, right._data, _len) == 0); 66 | }; 67 | void print() { 68 | static const char hexchars[]="0123456789ABCDEF"; 69 | USB_SERIAL.print(F("<* DCCPACKET ")); 70 | for (byte n = 0; n< _len; n++) { 71 | USB_SERIAL.print(hexchars[_data[n]>>4]); 72 | USB_SERIAL.print(hexchars[_data[n] & 0x0f]); 73 | USB_SERIAL.print(' '); 74 | } 75 | USB_SERIAL.print(F("*>\n")); 76 | }; 77 | inline byte len() {return _len;}; 78 | inline byte *data() {return _data;}; 79 | private: 80 | byte _len = 0; 81 | byte *_data = NULL; 82 | }; 83 | #endif 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What is DCC-EX? 2 | DCC-EX is a team of dedicated enthusiasts producing open source DCC & DC solutions for you to run your complete model railroad layout. Our easy to use, do-it-yourself, and free open source products run on off-the-shelf Arduino technology and are supported by numerous third party hardware and apps like JMRI, Engine Driver, wiThrottle, Rocrail and more. 3 | 4 | Currently, our products include the following: 5 | 6 | * [EX-CommandStation](https://github.com/DCC-EX/CommandStation-EX/releases) 7 | * [EX-WebThrottle](https://github.com/DCC-EX/exWebThrottle) 8 | * [EX-Installer](https://github.com/DCC-EX/EX-Installer) 9 | * [EX-MotoShield8874](https://dcc-ex.com/reference/hardware/motorboards/ex-motor-shield-8874.html#gsc.tab=0) 10 | * [EX-DCCInspector](https://github.com/DCC-EX/DCCInspector-EX) 11 | * [EX-Toolbox](https://github.com/DCC-EX/EX-Toolbox) 12 | * [EX-Turntable](https://github.com/DCC-EX/EX-Turntable) 13 | * [EX-IOExpander](https://github.com/DCC-EX/EX-IOExpander) 14 | * [EX-FastClock](https://github.com/DCC-EX/EX-FastClock) 15 | * [DCCEXProtocol](https://github.com/DCC-EX/DCCEXProtocol) 16 | 17 | Details of these projects can be found on [our web site](https://dcc-ex.com/). 18 | 19 | # What’s in this Repository? 20 | 21 | This repository, CommandStation-EX, contains a complete DCC-EX *EX-CommmandStation* sketch designed for compiling and uploading into an Arduino Uno, Mega, or Nano. 22 | 23 | To utilize this sketch, you can use the following: 24 | 25 | 1. (recommended for all levels of user) our [automated installer](https://github.com/DCC-EX/EX-Installer) 26 | 2. (intermediate) download the latest version from the [releases page](https://github.com/DCC-EX/CommandStation-EX/releases) 27 | 3. (advanced) use git clone on this repository 28 | 29 | Refer to [our web site](https://https://dcc-ex.com/ex-commandstation/get-started/index.html#/) for the hardware required for this project. 30 | 31 | **We seriously recommend using the EX-Installer**, however if you choose not to use the installer... 32 | 33 | * Open the file ``CommandStation-EX.ino`` in the Arduino IDE or Visual Studio Code (VSC). Please do not rename the folder containing the sketch code, nor add any files in that folder. The Arduino IDE relies on the structure and name of the folder to properly display and compile the code. 34 | * Rename or copy ``config.example.h`` to ``config.h``. 35 | * You must edit ``config.h`` according to the help texts in ``config.h``. 36 | 37 | # More information 38 | You can learn more at the [DCC-EX website](https://dcc-ex.com/) 39 | 40 | -------------------------------------------------------------------------------- /Display.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021, Chris Harlow, Neil McKechnie. All rights reserved. 3 | * 4 | * This file is part of CommandStation-EX 5 | * 6 | * This is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * It is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with CommandStation. If not, see . 18 | */ 19 | #ifndef Display_h 20 | #define Display_h 21 | #include 22 | #include "defines.h" 23 | #include "DisplayInterface.h" 24 | 25 | // Allow maximum message length to be overridden from config.h 26 | #if !defined(MAX_MSG_SIZE) 27 | #define MAX_MSG_SIZE 20 28 | #endif 29 | 30 | // Set default scroll mode (overridable in config.h) 31 | #if !defined(SCROLLMODE) 32 | #define SCROLLMODE 1 33 | #endif 34 | 35 | // This class is created in Display_Implementation.h 36 | 37 | class Display : public DisplayInterface { 38 | public: 39 | Display(DisplayDevice *deviceDriver); 40 | #if !defined (MAX_CHARACTER_ROWS) 41 | static const int MAX_CHARACTER_ROWS = 8; 42 | #endif 43 | static const int MAX_CHARACTER_COLS = MAX_MSG_SIZE; 44 | static const long DISPLAY_SCROLL_TIME = 3000; // 3 seconds 45 | 46 | private: 47 | DisplayDevice *_deviceDriver; 48 | 49 | unsigned long lastScrollTime = 0; 50 | uint8_t hotRow = 0; 51 | uint8_t hotCol = 0; 52 | uint8_t slot = 0; 53 | uint8_t rowFirst = 0; 54 | uint8_t rowCurrent = 0; 55 | uint8_t charIndex = 0; 56 | char buffer[MAX_CHARACTER_COLS + 1]; 57 | char* bufferPointer = 0; 58 | bool noMoreRowsToDisplay = false; 59 | uint16_t numScreenRows; 60 | uint16_t numScreenColumns = MAX_CHARACTER_COLS; 61 | 62 | char rowBuffer[MAX_CHARACTER_ROWS][MAX_CHARACTER_COLS+1]; 63 | 64 | public: 65 | void begin() override; 66 | void _clear() override; 67 | void _setRow(uint8_t line) override; 68 | size_t _write(uint8_t b) override; 69 | void _refresh() override; 70 | void _displayLoop() override; 71 | Display *loop2(bool force); 72 | bool findNonBlankRow(); 73 | bool isCurrentRowBlank(); 74 | void moveToNextRow(); 75 | uint8_t countNonBlankRows(); 76 | 77 | }; 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /LCN.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021, Chris Harlow. All rights reserved. 3 | * 4 | * This file is part of DCC-EX CommandStation-EX 5 | * 6 | * This is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * It is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with CommandStation. If not, see . 18 | */ 19 | 20 | #include "LCN.h" 21 | #include "DIAG.h" 22 | #include "Turnouts.h" 23 | #include "Sensors.h" 24 | 25 | int LCN::id = 0; 26 | Stream * LCN::stream=NULL; 27 | bool LCN::firstLoop=true; 28 | 29 | void LCN::init(Stream & lcnstream) { 30 | stream=&lcnstream; 31 | DIAG(F("LCN connection setup")); 32 | } 33 | 34 | 35 | // Inbound LCN traffic is postfix notation... nnnX where nnn is an id, X is the opcode 36 | void LCN::loop() { 37 | if (!stream) return; 38 | if (firstLoop) { 39 | firstLoop=false; 40 | stream->println('X'); 41 | return; 42 | } 43 | 44 | while (stream->available()) { 45 | int ch = stream->read(); 46 | if (ch >= '0' && ch <= '9') { // accumulate id value 47 | id = 10 * id + ch - '0'; 48 | } 49 | else if (ch == 't' || ch == 'T') { // Turnout opcodes 50 | if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch); 51 | if (!Turnout::exists(id)) LCNTurnout::create(id); 52 | Turnout::setClosedStateOnly(id,ch=='t'); 53 | id = 0; 54 | } 55 | else if (ch == 'y' || ch == 'Y') { // Turnout opcodes 56 | if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch); 57 | Turnout::setClosed(id,ch=='y'); 58 | id = 0; 59 | } 60 | else if (ch == 'S' || ch == 's') { 61 | if (Diag::LCN) DIAG(F("LCN IN %d%c"),id,(char)ch); 62 | Sensor * ss = Sensor::get(id); 63 | if (!ss) ss = Sensor::create(id, VPIN_NONE, 0); // impossible pin 64 | ss->setState(ch == 'S'); 65 | id = 0; 66 | } 67 | else id = 0; // ignore any other garbage from LCN 68 | } 69 | } 70 | 71 | void LCN::send(char opcode, int id, bool state) { 72 | if (stream) { 73 | StringFormatter::send(stream,F("%c/%d/%d"), opcode, id , state); 74 | if (Diag::LCN) DIAG(F("LCN OUT %c/%d/%d"), opcode, id , state); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /IO_DCCAccessory.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021, Neil McKechnie. All rights reserved. 3 | * 4 | * This file is part of DCC++EX API 5 | * 6 | * This is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * It is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with CommandStation. If not, see . 18 | */ 19 | 20 | #include "DCC.h" 21 | #include "IODevice.h" 22 | #include "DIAG.h" 23 | #include "defines.h" 24 | 25 | #define PACKEDADDRESS(addr, subaddr) (((addr) << 2) + (subaddr)) 26 | #define ADDRESS(packedaddr) ((packedaddr) >> 2) 27 | #define SUBADDRESS(packedaddr) ((packedaddr) % 4) 28 | 29 | void DCCAccessoryDecoder::create(VPIN firstVpin, int nPins, int DCCAddress, int DCCSubaddress) { 30 | if (checkNoOverlap(firstVpin,nPins)) new DCCAccessoryDecoder(firstVpin, nPins, DCCAddress, DCCSubaddress); 31 | } 32 | 33 | // Constructors 34 | DCCAccessoryDecoder::DCCAccessoryDecoder(VPIN vpin, int nPins, int DCCAddress, int DCCSubaddress) { 35 | _firstVpin = vpin; 36 | _nPins = nPins; 37 | _packedAddress = PACKEDADDRESS(DCCAddress, DCCSubaddress); 38 | addDevice(this); 39 | } 40 | 41 | void DCCAccessoryDecoder::_begin() { 42 | #if defined(DIAG_IO) 43 | _display(); 44 | #endif 45 | } 46 | 47 | // Device-specific write function. State 1=closed, 0=thrown. Adjust for RCN-213 compliance 48 | void DCCAccessoryDecoder::_write(VPIN id, int state) { 49 | int packedAddress = _packedAddress + id - _firstVpin; 50 | #if defined(HAL_ACCESSORY_COMMAND_REVERSE) 51 | state = !state; 52 | #ifdef DIAG_IO 53 | DIAG(F("DCC Write Linear Address:%d State:%d (inverted)"), packedAddress, state); 54 | #endif 55 | #else 56 | #ifdef DIAG_IO 57 | DIAG(F("DCC Write Linear Address:%d State:%d"), packedAddress, state); 58 | #endif 59 | #endif 60 | DCC::setAccessory(ADDRESS(packedAddress), SUBADDRESS(packedAddress), state); 61 | } 62 | 63 | void DCCAccessoryDecoder::_display() { 64 | int endAddress = _packedAddress + _nPins - 1; 65 | DIAG(F("DCCAccessoryDecoder Configured on Vpins:%u-%u Addresses %d/%d-%d/%d)"), _firstVpin, _firstVpin+_nPins-1, 66 | ADDRESS(_packedAddress), SUBADDRESS(_packedAddress), ADDRESS(endAddress), SUBADDRESS(endAddress)); 67 | } 68 | -------------------------------------------------------------------------------- /EthernetInterface.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2023-2024 Paul M. Antoine 3 | * © 2021 Neil McKechnie 4 | * © 2021 Mike S 5 | * © 2021 Fred Decker 6 | * © 2020-2022 Harald Barth 7 | * © 2020-2024 Chris Harlow 8 | * © 2020 Gregor Baues 9 | * All rights reserved. 10 | * 11 | * This file is part of DCC-EX/CommandStation-EX 12 | * 13 | * 14 | * This is free software: you can redistribute it and/or modify 15 | * it under the terms of the GNU General Public License as published by 16 | * the Free Software Foundation, either version 3 of the License, or 17 | * (at your option) any later version. 18 | * 19 | * It is distributed in the hope that it will be useful, 20 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | * GNU General Public License for more details. 23 | * 24 | * You should have received a copy of the GNU General Public License 25 | * along with CommandStation. If not, see . 26 | * 27 | * Ethernet Interface added by Gregor Baues 28 | */ 29 | 30 | #ifndef EthernetInterface_h 31 | #define EthernetInterface_h 32 | 33 | #include "defines.h" 34 | #include "DCCEXParser.h" 35 | #include 36 | //#include 37 | #if defined (ARDUINO_TEENSY41) 38 | #include //TEENSY Ethernet Treiber 39 | #include 40 | #define MAX_SOCK_NUM 4 41 | #elif defined (ARDUINO_NUCLEO_F429ZI) || defined (ARDUINO_NUCLEO_F439ZI) || defined (ARDUINO_NUCLEO_F4X9ZI) 42 | #include 43 | // #include "STM32lwipopts.h" 44 | #include 45 | #include 46 | extern "C" struct netif gnetif; 47 | #define STM32_ETHERNET 48 | #define MAX_SOCK_NUM 8 49 | #else 50 | #include "Ethernet.h" 51 | #endif 52 | #include "RingStream.h" 53 | 54 | /** 55 | * @brief Network Configuration 56 | * 57 | */ 58 | 59 | #define MAX_ETH_BUFFER 128 60 | #define OUTBOUND_RING_SIZE 2048 61 | 62 | class EthernetInterface { 63 | 64 | public: 65 | 66 | static void setup(); 67 | static void loop(); 68 | 69 | private: 70 | static bool connected; 71 | static EthernetServer * server; 72 | static EthernetClient clients[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield 73 | static bool inUse[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield 74 | static uint8_t buffer[MAX_ETH_BUFFER+1]; // buffer used by TCP for the recv 75 | static RingStream * outboundRing; 76 | static void acceptClient(); 77 | static void dropClient(byte socketnum); 78 | 79 | }; 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /Release_Notes/TCA8418.md: -------------------------------------------------------------------------------- 1 | ## TCA8418 ## 2 | 3 | The TCA8418 IC from Texas Instruments is a low cost and very capable GPIO and keyboard scanner. Used as a keyboard scanner, it has 8 rows of 10 columns of IO pins which allow encoding of up to 80 buttons. The IC is available on an Adafruit board with Qwiic I2C interconnect called the "Adafruit TCA8418 Keypad Matrix and GPIO Expander Breakout" and available here for the modest sum of $US6 or so: https://www.adafruit.com/product/4918 4 | 5 | The great advantage of this IC is that the keyboard scanning is done continuously, and it has a 10-element event queue, so even if you don't get to the interrupt immediately, keypress and release events will be held for you. Since it's I2C its very easy to use with any DCC-EX command station. 6 | 7 | The TCA8418 driver presently configures the IC in the full 8x10 keyboard scanning mode, and then maps each key down/key up event to the state of a single vpin for extremely easy use from within EX-RAIL and JMRI as each key looks like an individual sensor. 8 | 9 | This is ideal for mimic panels where you may need a lot of buttons, but with this board you can use just 18 wires to handle as many as 80 buttons. 10 | 11 | By adding a simple HAL statement to myAutomation.h it creates between 1 and 80 buttons it will report back. 12 | 13 | `HAL(TCA8418, firstVpin, numPins, I2CAddress, interruptPin)` 14 | 15 | For example: 16 | 17 | `HAL(TCA8418, 300, 80, 0x34)` 18 | 19 | Creates VPINs 300-379 which you can monitor with EX-RAIL, JMRI sensors etc. 20 | 21 | With an 8x10 key event matrix, the events are numbered using the Rn row pins and Cn column pins as such: 22 | 23 | C0 C1 C2 C3 C4 C5 C6 C7 C8 C9 24 | ======================================== 25 | R0| 0 1 2 3 4 5 6 7 8 9 26 | R1| 10 11 12 13 14 15 16 17 18 19 27 | R2| 20 21 22 23 24 25 26 27 28 29 28 | R3| 30 31 32 33 34 35 36 37 38 39 29 | R4| 40 41 42 43 44 45 46 47 48 49 30 | R5| 50 51 52 53 54 55 56 57 58 59 31 | R6| 60 61 62 63 64 65 66 67 68 69 32 | R7| 70 71 72 73 74 75 76 77 78 79 33 | 34 | So if you start with the first pin definition being VPIN 300, R0/C0 will be 300 + 0, and R7/C9 will be 300+79 or 379. 35 | 36 | Use something like this on a multiplexor, and with up to 8 of the 8-way multiplexors you could have 64 different TCA8418 boards: 37 | 38 | `HAL(TCA8418, 400, 80, {SubBus_1, 0x34})` 39 | 40 | And if needing an Interrupt pin to speed up operations: 41 | `HAL(TCA8418, 300, 80, 0x34, 21)` 42 | 43 | Note that using an interrupt pin speeds up button press acquisition considerably (less than a millisecond vs 10-100), but even with interrupts enabled the code presently checks every 100ms in case the interrupt pin becomes disconnected. Use any available Arduino pin for interrupt monitoring. 44 | 45 | -------------------------------------------------------------------------------- /CommandDistributor.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2022 Harald Barth 3 | * © 2020-2021 Chris Harlow 4 | * © 2020 Gregor Baues 5 | * © 2022 Colin Murdoch 6 | * 7 | * All rights reserved. 8 | * 9 | * This file is part of CommandStation-EX 10 | * 11 | * This is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, either version 3 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * It is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with CommandStation. If not, see . 23 | */ 24 | #ifndef CommandDistributor_h 25 | #define CommandDistributor_h 26 | #include "DCCEXParser.h" 27 | #include "RingStream.h" 28 | #include "StringBuffer.h" 29 | #include "defines.h" 30 | #include "EXRAIL2.h" 31 | 32 | #if WIFI_ON | ETHERNET_ON 33 | // Command Distributor must handle a RingStream of clients 34 | #define CD_HANDLE_RING 35 | #endif 36 | 37 | class CommandDistributor { 38 | public: 39 | enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE}; 40 | private: 41 | static void broadcastToClients(clientType type); 42 | static StringBuffer * broadcastBufferWriter; 43 | #ifdef CD_HANDLE_RING 44 | static RingStream * ring; 45 | static clientType clients[8]; 46 | #endif 47 | public : 48 | static void parse(byte clientId,byte* buffer, RingStream * ring); 49 | static void broadcastLoco(byte slot); 50 | static void broadcastForgetLoco(int16_t loco); 51 | static void broadcastSensor(int16_t id, bool value); 52 | static void broadcastTurnout(int16_t id, bool isClosed); 53 | static void broadcastTurntable(int16_t id, uint8_t position, bool moving); 54 | static void broadcastClockTime(int16_t time, int8_t rate); 55 | static void setClockTime(int16_t time, int8_t rate, byte opt); 56 | static int16_t retClockTime(); 57 | static void broadcastPower(); 58 | static void broadcastRaw(clientType type,char * msg); 59 | static void broadcastTrackState(const FSH* format,byte trackLetter, const FSH* modename, int16_t dcAddr); 60 | template static void broadcastReply(clientType type, Targs... msg); 61 | static void forget(byte clientId); 62 | static void broadcastRouteState(int16_t routeId,byte state); 63 | static void broadcastRouteCaption(int16_t routeId,const FSH * caption); 64 | static void broadcastMessage(char * message); 65 | 66 | // Handling code for virtual LCD receiver. 67 | static Print * getVirtualLCDSerial(byte screen, byte row); 68 | static void commitVirtualLCDSerial(); 69 | static void setVirtualLCDSerial(Print * stream); 70 | private: 71 | static Print * virtualLCDSerial; 72 | static byte virtualLCDClient; 73 | static byte rememberVLCDClient; 74 | }; 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /WifiInboundHandler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021 Harald Barth 3 | * © 2021 Fred Decker 4 | * (c) 2021 Fred Decker. All rights reserved. 5 | * (c) 2020 Chris Harlow. All rights reserved. 6 | * 7 | * This file is part of CommandStation-EX 8 | * 9 | * This is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * It is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with CommandStation. If not, see . 21 | */ 22 | #ifndef WifiInboundHandler_h 23 | #define WifiInboundHandler_h 24 | 25 | #include "RingStream.h" 26 | #include "WiThrottle.h" 27 | #include "DIAG.h" 28 | 29 | class WifiInboundHandler { 30 | public: 31 | static void setup(Stream * ESStream); 32 | static void loop(); 33 | 34 | private: 35 | 36 | static WifiInboundHandler * singleton; 37 | 38 | 39 | enum INBOUND_STATE : byte { 40 | INBOUND_BUSY, // keep calling in loop() 41 | INBOUND_IDLE // Nothing happening, outbound may xcall CIPSEND 42 | }; 43 | 44 | enum LOOP_STATE : byte { 45 | ANYTHING, // ready for +IPD, n CLOSED, n CONNECTED, busy etc... 46 | SKIPTOEND, // skip to newline 47 | 48 | // +IPD,client,length:data 49 | IPD, // got + 50 | IPD1, // got +I 51 | IPD2, // got +IP 52 | IPD3, // got +IPD 53 | IPD4_CLIENT, // got +IPD, reading cient id 54 | IPD5, // got +IPD,c 55 | IPD6_LENGTH, // got +IPD,c, reading length 56 | IPD_DATA, // got +IPD,c,ll,: collecting data 57 | IPD_IGNORE_DATA, // got +IPD,c,ll,: ignoring the data that won't fit inblound Ring 58 | 59 | GOT_CLIENT_ID, // clientid prefix to CONNECTED / CLOSED 60 | GOT_CLIENT_ID2 // clientid prefix to CONNECTED / CLOSED 61 | }; 62 | 63 | 64 | WifiInboundHandler(Stream * ESStream); 65 | void loop1(); 66 | INBOUND_STATE loop2(); 67 | void purgeCurrentCIPSEND(); 68 | Stream * wifiStream; 69 | 70 | static const int INBOUND_RING = 512; 71 | static const int OUTBOUND_RING = sizeof(void*)==2?2048:8192; 72 | 73 | static const int CIPSENDgap=100; // millis() between retries of cipsend. 74 | 75 | RingStream * inboundRing; 76 | RingStream * outboundRing; 77 | 78 | LOOP_STATE loopState=ANYTHING; 79 | int runningClientId; // latest client inbound processing data or CLOSE 80 | int dataLength; // dataLength of +IPD 81 | int clientPendingCIPSEND=-1; 82 | int currentReplySize; 83 | bool pendingCipsend; 84 | uint32_t lastCIPSEND=0; // millis() of previous cipsend 85 | 86 | }; 87 | #endif 88 | -------------------------------------------------------------------------------- /LiquidCrystal_I2C.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021, Neil McKechnie. All rights reserved. 3 | * Based on the work by DFRobot, Frank de Brabander and Marco Schwartz. 4 | * 5 | * This file is part of CommandStation-EX 6 | * 7 | * This is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * It is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with CommandStation. If not, see . 19 | */ 20 | 21 | #ifndef LiquidCrystal_I2C_h 22 | #define LiquidCrystal_I2C_h 23 | 24 | #include 25 | #include "Display.h" 26 | #include "I2CManager.h" 27 | 28 | // commands 29 | #define LCD_CLEARDISPLAY 0x01 30 | #define LCD_ENTRYMODESET 0x04 31 | #define LCD_DISPLAYCONTROL 0x08 32 | #define LCD_FUNCTIONSET 0x20 33 | #define LCD_SETCGRAMADDR 0x40 34 | #define LCD_SETDDRAMADDR 0x80 35 | 36 | // flags for display entry mode 37 | #define LCD_ENTRYRIGHT 0x00 38 | #define LCD_ENTRYLEFT 0x02 39 | #define LCD_ENTRYSHIFTINCREMENT 0x01 40 | #define LCD_ENTRYSHIFTDECREMENT 0x00 41 | 42 | // flags for display on/off control 43 | #define LCD_DISPLAYON 0x04 44 | #define LCD_CURSOROFF 0x00 45 | #define LCD_BLINKOFF 0x00 46 | 47 | // flags for function set 48 | #define LCD_4BITMODE 0x00 49 | #define LCD_2LINE 0x08 50 | #define LCD_1LINE 0x00 51 | #define LCD_5x8DOTS 0x00 52 | 53 | // Bit mapping onto PCF8574 port 54 | #define BACKPACK_Rs_BIT 0 55 | #define BACKPACK_Rw_BIT 1 56 | #define BACKPACK_En_BIT 2 57 | #define BACKPACK_BACKLIGHT_BIT 3 58 | #define BACKPACK_DATA_BITS 4 // Bits 4-7 59 | // Equivalent mask bits 60 | #define LCD_BACKLIGHT (1 << BACKPACK_BACKLIGHT_BIT) // Backlight enable 61 | #define En (1 << BACKPACK_En_BIT) // Enable bit 62 | #define Rw (1 << BACKPACK_Rw_BIT) // Read/Write bit 63 | #define Rs (1 << BACKPACK_Rs_BIT) // Register select bit 64 | 65 | class LiquidCrystal_I2C : public DisplayDevice { 66 | public: 67 | LiquidCrystal_I2C(I2CAddress lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows); 68 | bool begin() override; 69 | void clearNative() override; 70 | void setRowNative(byte line) override; 71 | size_t writeNative(uint8_t c) override; 72 | // I/O is synchronous, so if this is called we're not busy! 73 | bool isBusy() override; 74 | 75 | void display(); 76 | void noBacklight(); 77 | void backlight(); 78 | 79 | void command(uint8_t); 80 | uint16_t getNumCols() { return lcdCols; } 81 | uint16_t getNumRows() { return lcdRows; } 82 | 83 | 84 | private: 85 | void send(uint8_t, uint8_t); 86 | void write4bits(uint8_t); 87 | void expanderWrite(uint8_t); 88 | uint8_t lcdCols=0, lcdRows=0; 89 | I2CAddress _Addr; 90 | uint8_t _displayfunction; 91 | uint8_t _displaycontrol; 92 | uint8_t _displaymode; 93 | uint8_t _backlightval = 0; 94 | 95 | uint8_t outputBuffer[4]; 96 | I2CRB rb; 97 | }; 98 | 99 | #endif 100 | -------------------------------------------------------------------------------- /SSD1306Ascii.h: -------------------------------------------------------------------------------- 1 | /* Based on Arduino SSD1306Ascii Library, Copyright (C) 2015 by William Greiman 2 | * Modifications (C) 2021 Neil McKechnie 3 | * 4 | * This file is part of CommandStation-EX 5 | * 6 | * This Library is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This Library is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this software. If not, see 18 | * . 19 | */ 20 | 21 | #ifndef SSD1306Ascii_h 22 | #define SSD1306Ascii_h 23 | 24 | #include "Arduino.h" 25 | #include "FSH.h" 26 | #include "Display.h" 27 | 28 | #include "I2CManager.h" 29 | #include "DIAG.h" 30 | #include "DisplayInterface.h" 31 | 32 | // Uncomment to remove lower-case letters to save 108 bytes of flash 33 | //#define NOLOWERCASE 34 | 35 | 36 | 37 | //------------------------------------------------------------------------------ 38 | // Constructor 39 | class SSD1306AsciiWire : public DisplayDevice { 40 | public: 41 | 42 | // Constructors 43 | SSD1306AsciiWire(int width, int height); // Auto-detects I2C address 44 | SSD1306AsciiWire(I2CAddress address, int width, int height); 45 | 46 | // Initialize the display controller. 47 | bool begin(); 48 | 49 | // Clear the display and set the cursor to (0, 0). 50 | void clearNative() override; 51 | 52 | // Set cursor to start of specified text line 53 | void setRowNative(byte line) override; 54 | 55 | // Write one character to OLED 56 | size_t writeNative(uint8_t c) override; 57 | 58 | bool isBusy() override { return requestBlock.isBusy(); } 59 | uint16_t getNumCols() { return m_charsPerRow; } 60 | uint16_t getNumRows() { return m_charsPerColumn; } 61 | 62 | private: 63 | // Cursor column. 64 | uint8_t m_col; 65 | // Cursor RAM row. 66 | uint8_t m_row; 67 | // Display width. 68 | uint8_t m_displayWidth; 69 | // Display height. 70 | uint8_t m_displayHeight; 71 | // Display width in characters 72 | uint8_t m_charsPerRow; 73 | // Display height in characters 74 | uint8_t m_charsPerColumn; 75 | // Column offset RAM to SEG. 76 | uint8_t m_colOffset = 0; 77 | // Current font. 78 | const uint8_t* const m_font = System6x8; 79 | // Flag to prevent calling begin() twice 80 | uint8_t m_initialised = false; 81 | 82 | // Only fixed size 6x8 fonts in a 6x8 cell are supported. 83 | static const uint8_t fontWidth = 6; 84 | static const uint8_t fontHeight = 8; 85 | static const uint8_t m_fontFirstChar = 0x20; 86 | static const uint8_t m_fontCharCount; 87 | 88 | I2CAddress m_i2cAddr = 0; 89 | 90 | I2CRB requestBlock; 91 | uint8_t outputBuffer[fontWidth+1]; 92 | 93 | static const uint8_t blankPixels[]; 94 | 95 | static const uint8_t System6x8[]; 96 | static const uint8_t FLASH Adafruit128xXXinit[]; 97 | static const uint8_t FLASH SH1106_132x64init[]; 98 | }; 99 | 100 | #endif // SSD1306Ascii_h 101 | -------------------------------------------------------------------------------- /myAutomation.example.h: -------------------------------------------------------------------------------- 1 | /* This is an automation example file. 2 | * The presence of a file called "myAutomation.h" brings EX-RAIL code into 3 | * the command station. 4 | * The automation may have multiple concurrent tasks. 5 | * A task may 6 | * - Act as a ROUTE setup macro for a user to drive over 7 | * - drive a loco through an AUTOMATION 8 | * - automate some cosmetic part of the layout without any loco. 9 | * 10 | * At startup, a single task is created to execute the startup sequence. 11 | * This task may simply follow a route, or may START 12 | * further tasks (that is.. send a loco out along a route). 13 | * 14 | * Where the loco id is not known at compile time, a new task 15 | * can be created with the command: 16 | * 17 | * 18 | * A ROUTE, AUTOMATION or SEQUENCE are internally identical in ExRail terms 19 | * but are just represented differently to a Withrottle user: 20 | * ROUTE(n,"name") - as Route_n .. to setup a route through a layout 21 | * AUTOMATION(n,"name") as Auto_n .. to send the current loco off along an automated journey 22 | * SEQUENCE(n) is not visible to Withrottle. 23 | * 24 | */ 25 | 26 | // This is the startup sequence, 27 | AUTOSTART 28 | POWERON // turn on track power 29 | SENDLOCO(3,1) // send loco 3 off along route 1 30 | SENDLOCO(10,2) // send loco 10 off along route 2 31 | DONE // This just ends the startup thread, leaving 2 others running. 32 | 33 | /* SEQUENCE(1) is a simple shuttle between 2 sensors 34 | * S20 and S21 are sensors on arduino pins 20 and 21 35 | * S20 S21 36 | * === START->================ 37 | */ 38 | SEQUENCE(1) 39 | DELAY(10000) // wait 10 seconds 40 | FON(3) // Set Loco Function 3, Horn on 41 | DELAY(1000) // wait 1 second 42 | FOFF(3) // Horn off 43 | FWD(80) // Move forward at speed 80 44 | AT(21) // until we hit sensor id 21 45 | STOP // then stop 46 | DELAY(5000) // Wait 5 seconds 47 | FON(2) // ring bell 48 | REV(60) // reverse at speed 60 49 | AT(20) // until we get to S20 50 | STOP // then stop 51 | FOFF(2) // Bell off 52 | FOLLOW(1) // and follow sequence 1 again 53 | 54 | /* SEQUENCE(2) is an automation example for a single loco Y shaped journey 55 | * S31,S32,S33 are sensors, T4 is a turnout 56 | * 57 | * S33 T4 S31 58 | * ===-START->============================================= 59 | * // 60 | * S32 // 61 | * ======================// 62 | * 63 | * Train runs from START to S31, back to S32, again to S31, Back to start. 64 | */ 65 | SEQUENCE(2) 66 | FWD(60) // go forward at DCC speed 60 67 | AT(31) STOP // when we get to sensor 31 68 | DELAY(10000) // wait 10 seconds 69 | THROW(4) // throw turnout for route to S32 70 | REV(45) // go backwards at speed 45 71 | AT(32) STOP // until we arrive at sensor 32 72 | DELAY(5000) // wait 5 seconds 73 | FWD(50) // go forwards at speed 50 74 | AT(31) STOP // and stop at sensor 31 75 | DELAY(5000) // wait 5 seconds 76 | CLOSE(4) // set turnout closed 77 | REV(50) // reverse back to S3 78 | AT(33) STOP 79 | DELAY(20000) // wait 20 seconds 80 | FOLLOW(2) // follow sequence 2... ie repeat the process 81 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for considering contributing to our project. Here is a guide for how to get started and and a list of our conventions. We will also walk you through the Github command line and Desktop commands necessary to download the code, make changes, and get it included in a next version of the sofware. 4 | 5 | Before contributing to this repository, please first discuss the change you wish to make via issue, or any other method with the owners of this repository before making a change. 6 | 7 | Find us on our website at https://dcc-ex.com, on our Discord https://discord.gg/y2sB4Fp or on Trainboard: https://www.trainboard.com/highball/index.php?threads/dcc-update-project-2020.130071/ 8 | 9 | # Development Environment 10 | 11 | We recommend using PlatformIO IDE for VSCode. If you haven't yet used it, it is an easy to learn and easy to use IDE that really shines for embedded development and the Arduino based hardware we use. For more information go to https://platformio.org/ 12 | 13 | * Download and install the latest version of the Arduino IDE 14 | * Download and install the latest version of Visual Studio Code from Microsoft 15 | * Run VSCode and click on the "extensions" icon on the left. Install "PlatformIO IDE for VSCode" and the "Arduino Framework" support 16 | 17 | If you don't see C/C++ Installed in the list, install that too. We also recomment installing the Gitlens extension to make working with Git and GitHub even easier. 18 | 19 | You may ask if you can use the Arduino IDE, Visual Studio, or even a text editor and the answer is "of course" if you know what you are doing. Since you are just changing text files, you can use whatever you like as long as your commits and pull requests can be merged in GitHub. However, it will be much easier to follow our coding standards if you have an IDE that can automatically format things for you. 20 | 21 | # Coding Style Guidelines 22 | 23 | We have adopted the Google style guidlines. In particular please make sure to adhere to these standards: 24 | 25 | 1. All header files should have #define guards to prevent multiple inclusion. 26 | 2. Use Unix style line endings 27 | 3. We indent using two spaces (soft tabs) 28 | 4. Braces 29 | 30 | For more information just check our code or read https://google.github.io/styleguide/cppguide.html#C++_Version 31 | 32 | ## Using the Repository 33 | 34 | 1. Clone the repository on your local machine 35 | 2. Create a working branch using the format "username-featurename" ex: "git branch -b frightrisk-turnouts" 36 | 3. Commit offen, ex: "git add ." and then "git commit -m "description of your changes" 37 | 4. Push your changes to our repository "git push" 38 | 5. When you are ready, issue a pull request for your changes to be merged into the main branch 39 | 40 | ## Pull Request Process 41 | 42 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a build. 43 | 44 | ## Code of Conduct 45 | 46 | Be Nice 47 | 48 | ### Enforcement 49 | 50 | Contributors who do not follow the be nice rule in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 51 | 52 | ## How Can I Contribute? 53 | 54 | The DCC-EX Team has several projects and sub teams where you can help donate your epertise. See the sections below for the project or projects you are interested in. 55 | 56 | ### Development 57 | ### Documentation 58 | ### WebThrottle-EX 59 | ### Web Support 60 | ### Organization/Coordination 61 | 62 | Links to external documentation goes here XXX 63 | -------------------------------------------------------------------------------- /IO_trainbrains.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2023, Chris Harlow. All rights reserved. 3 | * © 2021, Neil McKechnie. All rights reserved. 4 | * 5 | * This file is part of DCC++EX API 6 | * 7 | * This is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * It is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with CommandStation. If not, see . 19 | */ 20 | 21 | #ifndef io_trainbrains_h 22 | #define io_trainbrains_h 23 | 24 | #include "IO_GPIOBase.h" 25 | #include "FSH.h" 26 | 27 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 28 | /* 29 | * IODevice subclass for trainbrains 3-block occupancy detector. 30 | * For details see http://trainbrains.eu 31 | */ 32 | 33 | enum TrackUnoccupancy 34 | { 35 | TRACK_UNOCCUPANCY_UNKNOWN = 0, 36 | TRACK_OCCUPIED = 1, 37 | TRACK_UNOCCUPIED = 2 38 | }; 39 | 40 | class Trainbrains02 : public GPIOBase { 41 | public: 42 | static void create(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress) { 43 | if (checkNoOverlap(vpin, nPins, i2cAddress)) new Trainbrains02(vpin, nPins, i2cAddress); 44 | } 45 | 46 | private: 47 | // Constructor 48 | Trainbrains02(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) 49 | : GPIOBase((FSH *)F("Trainbrains02"), vpin, nPins, i2cAddress, interruptPin) 50 | { 51 | requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer), 52 | outputBuffer, sizeof(outputBuffer)); 53 | 54 | outputBuffer[0] = (uint8_t)_I2CAddress; // strips away the mux part. 55 | outputBuffer[1] =14; 56 | outputBuffer[2] =1; 57 | outputBuffer[3] =0; // This is the channel updated at each poling call 58 | outputBuffer[4] =0; 59 | outputBuffer[5] =0; 60 | outputBuffer[6] =0; 61 | outputBuffer[7] =0; 62 | outputBuffer[8] =0; 63 | outputBuffer[9] =0; 64 | } 65 | 66 | void _writeGpioPort() override {} 67 | 68 | void _readGpioPort(bool immediate) override { 69 | // cycle channel on device each time 70 | outputBuffer[3]=channelInProgress+1; // 1-origin 71 | channelInProgress++; 72 | if(channelInProgress>=_nPins) channelInProgress=0; 73 | 74 | if (immediate) { 75 | _processCompletion(I2CManager.read(_I2CAddress, inputBuffer, sizeof(inputBuffer), 76 | outputBuffer, sizeof(outputBuffer))); 77 | } else { 78 | // Queue new request 79 | requestBlock.wait(); // Wait for preceding operation to complete 80 | // Issue new request to read GPIO register 81 | I2CManager.queueRequest(&requestBlock); 82 | } 83 | } 84 | 85 | // This function is invoked when an I/O operation on the requestBlock completes. 86 | void _processCompletion(uint8_t status) override { 87 | if (status != I2C_STATUS_OK) inputBuffer[6]=TRACK_UNOCCUPANCY_UNKNOWN; 88 | if (inputBuffer[6] == TRACK_UNOCCUPIED ) _portInputState |= 0x01 <. 21 | */ 22 | #ifndef DCCEXParser_h 23 | #define DCCEXParser_h 24 | #include 25 | #include "FSH.h" 26 | #include "RingStream.h" 27 | #include "defines.h" 28 | 29 | typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int16_t p[]); 30 | typedef void (*AT_COMMAND_CALLBACK)(HardwareSerial * stream,const byte * command); 31 | 32 | struct DCCEXParser 33 | { 34 | 35 | static void parse(Print * stream, byte * command, RingStream * ringStream); 36 | static void parse(const FSH * cmd); 37 | static void parseOne(Print * stream, byte * command, RingStream * ringStream); 38 | static void setFilter(FILTER_CALLBACK filter); 39 | static void setRMFTFilter(FILTER_CALLBACK filter); 40 | static void setAtCommandCallback(AT_COMMAND_CALLBACK filter); 41 | static const int MAX_COMMAND_PARAMS=10; // Must not exceed this 42 | static bool funcmap(int16_t cab, byte value, byte fstart, byte fstop); 43 | 44 | private: 45 | 46 | static const int16_t MAX_BUFFER=50; // longest command sent in 47 | static int16_t splitValues( int16_t result[MAX_COMMAND_PARAMS], byte * command, bool usehex); 48 | 49 | static bool parseT(Print * stream, int16_t params, int16_t p[]); 50 | static bool parseZ(Print * stream, int16_t params, int16_t p[]); 51 | static bool parseS(Print * stream, int16_t params, int16_t p[]); 52 | static bool parsef(Print * stream, int16_t params, int16_t p[]); 53 | static bool parseC(Print * stream, int16_t params, int16_t p[]); 54 | static bool parseD(Print * stream, int16_t params, int16_t p[]); 55 | #ifndef IO_NO_HAL 56 | static bool parseI(Print * stream, int16_t params, int16_t p[]); 57 | #endif 58 | 59 | static Print * getAsyncReplyStream(); 60 | static void commitAsyncReplyStream(); 61 | 62 | static bool stashBusy; 63 | static byte stashTarget; 64 | static Print * stashStream; 65 | static RingStream * stashRingStream; 66 | 67 | static int16_t stashP[MAX_COMMAND_PARAMS]; 68 | static bool stashCallback(Print * stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream); 69 | static void callback_W(int16_t result); 70 | static void callback_W4(int16_t result); 71 | static void callback_B(int16_t result); 72 | static void callback_R(int16_t result); 73 | static void callback_Rloco(int16_t result); 74 | static void callback_Wloco(int16_t result); 75 | static void callback_Wconsist(int16_t result); 76 | static void callback_Vbit(int16_t result); 77 | static void callback_Vbyte(int16_t result); 78 | static FILTER_CALLBACK filterCallback; 79 | static FILTER_CALLBACK filterRMFTCallback; 80 | static AT_COMMAND_CALLBACK atCommandCallback; 81 | static void sendFlashList(Print * stream,const int16_t flashList[]); 82 | 83 | }; 84 | 85 | #endif 86 | -------------------------------------------------------------------------------- /WiThrottle.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021 Mike S 3 | * © 2020-2021 Chris Harlow 4 | * All rights reserved. 5 | * 6 | * This file is part of Asbelos DCC API 7 | * 8 | * This is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * It is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with CommandStation. If not, see . 20 | */ 21 | #ifndef WiThrottle_h 22 | #define WiThrottle_h 23 | 24 | #include "RingStream.h" 25 | 26 | struct MYLOCO { 27 | char throttle; //indicates which throttle letter on client, often '0','1' or '2' 28 | int cab; //address of this loco 29 | bool broadcastPending; 30 | uint32_t functionMap; 31 | uint32_t functionToggles; 32 | }; 33 | 34 | class WiThrottle { 35 | public: 36 | static void loop(RingStream * stream); 37 | void parse(RingStream * stream, byte * cmd); 38 | static WiThrottle* getThrottle( int wifiClient); 39 | static void markForBroadcast(int cab); 40 | static void forget(byte clientId); 41 | static void findUniqThrottle(int id, char *u); 42 | 43 | private: 44 | WiThrottle( int wifiClientId); 45 | ~WiThrottle(); 46 | 47 | static const int MAX_MY_LOCO=10; // maximum number of locos assigned to a single client 48 | static const int HEARTBEAT_SECONDS=10; // heartbeat at 10 secs to provide messaging transport 49 | static const int HEARTBEAT_PRELOAD=2; // request fast callback when connecting multiple messages 50 | static const int ESTOP_SECONDS=20; // eStop if no incoming messages for more than 8secs 51 | static WiThrottle* firstThrottle; 52 | static int getInt(byte * cmd); 53 | static int getLocoId(byte * cmd); 54 | static char LorS(int cab); 55 | static bool isThrottleInUse(int cab); 56 | static void setSendTurnoutList(); 57 | bool areYouUsingThrottle(int cab); 58 | WiThrottle* nextThrottle; 59 | int clientid; 60 | char uniq[17] = ""; 61 | 62 | MYLOCO myLocos[MAX_MY_LOCO]; 63 | bool heartBeatEnable; 64 | unsigned long heartBeat; 65 | bool introSent=false; 66 | bool turnoutsSent=false; 67 | bool rosterSent=false; 68 | bool routesSent=false; 69 | bool heartrateSent=false; 70 | uint16_t mostRecentCab; 71 | bool lastPowerState; // last power state sent to this client 72 | 73 | int DCCToWiTSpeed(int DCCSpeed); 74 | int WiTToDCCSpeed(int WiTSpeed); 75 | void multithrottle(RingStream * stream, byte * cmd); 76 | void locoAction(RingStream * stream, byte* aval, char throttleChar, int cab); 77 | void accessory(RingStream *, byte* cmd); 78 | void checkHeartbeat(RingStream * stream); 79 | void markForBroadcast2(int cab); 80 | void sendIntro(Print * stream); 81 | void sendTurnouts(Print * stream); 82 | void sendRoster(Print * stream); 83 | void sendRoutes(Print * stream); 84 | void sendFunctions(Print* stream, byte loco); 85 | // callback stuff to support prog track acquire 86 | static RingStream * stashStream; 87 | static WiThrottle * stashInstance; 88 | static byte stashClient; 89 | static char stashThrottleChar; 90 | static void getLocoCallback(int16_t locoid); 91 | 92 | }; 93 | #endif 94 | -------------------------------------------------------------------------------- /CamParser.cpp: -------------------------------------------------------------------------------- 1 | 2 | //sensorCAM parser.cpp version 3.03 Sep 2024 3 | #include "CamParser.h" 4 | #include "FSH.h" 5 | #include "IO_EXSensorCAM.h" 6 | 7 | #ifndef SENSORCAM_VPIN //define CAM vpin (700?) in config.h 8 | #define SENSORCAM_VPIN 0 9 | #endif 10 | #define CAM_VPIN SENSORCAM_VPIN 11 | #ifndef SENSORCAM2_VPIN 12 | #define SENSORCAM2_VPIN CAM_VPIN 13 | #endif 14 | #ifndef SENSORCAM3_VPIN 15 | #define SENSORCAM3_VPIN 0 16 | #endif 17 | const int CAMVPINS[] = {CAM_VPIN,SENSORCAM_VPIN,SENSORCAM2_VPIN,SENSORCAM3_VPIN}; 18 | const int16_t ver=30177; 19 | const int16_t ve =2899; 20 | 21 | VPIN EXSensorCAM::CAMBaseVpin = CAM_VPIN; 22 | 23 | bool CamParser::parseN(Print * stream, byte paramCount, int16_t p[]) { 24 | (void)stream; // probably unused parameter 25 | VPIN vpin=EXSensorCAM::CAMBaseVpin; //use current CAM selection 26 | 27 | if (paramCount==0) { 28 | DIAG(F("vpin:%d EXSensorCAMs defined at Vpins #1@ %d #2@ %d #3@ %d"),vpin,CAMVPINS[1],CAMVPINS[2],CAMVPINS[3]); 29 | return true; 30 | } 31 | uint8_t camop=p[0]; // cam oprerator 32 | int param1=0; 33 | int16_t param3=9999; // =0 could invoke parameter changes. & -1 gives later errors 34 | 35 | if(camop=='C'){ 36 | if(p[1]>=100) EXSensorCAM::CAMBaseVpin=p[1]; 37 | if(p[1]<4) EXSensorCAM::CAMBaseVpin=CAMVPINS[p[1]]; 38 | DIAG(F("CAM base Vpin: %c %d "),p[0],EXSensorCAM::CAMBaseVpin); 39 | return true; 40 | } 41 | if (camop<100) { //switch CAM# if p[1] dictates 42 | if(p[1]>=100 && p[1]<400) { //limits to CAM# 1 to 3 for now 43 | vpin=CAMVPINS[p[1]/100]; 44 | EXSensorCAM::CAMBaseVpin=vpin; 45 | DIAG(F("switching to CAM %d baseVpin:%d"),p[1]/100,vpin); 46 | p[1]=p[1]%100; //strip off CAM # 47 | } 48 | } 49 | if (EXSensorCAM::CAMBaseVpin==0) return false; // no cam defined 50 | 51 | 52 | // send UPPER case to sensorCAM to flag binary data from a DCCEX-CS parser 53 | switch(paramCount) { 54 | case 1: // produces '^' 55 | if((p[0] == ve) || (p[0] == ver) || (p[0] == 'V')) camop='^'; 56 | if (STRCHR_P((const char *)F("EFGMQRVW^"),camop) == nullptr) return false; 57 | if (camop=='Q') param3=10; // for activation state of all 10 banks of sensors 58 | if (camop=='F') camop=']'; // for Reset/Finish webCAM. 59 | break; // F Coded as ']' else conflicts with 60 | 61 | case 2: // 62 | if (STRCHR_P((const char *)F("ABFILMNOPQRSTUV"),camop)==nullptr) return false; 63 | param1=p[1]; 64 | break; 65 | 66 | case 3: // or 67 | camop=p[0]; 68 | if (p[0]>=100) { //vpin - i.e. NOT 'A' through 'Z' 69 | if (p[1]>236 || p[1]<0) return false; //row 70 | if (p[2]>316 || p[2]<0) return false; //column 71 | camop=0x80; // special 'a' case for IO_SensorCAM 72 | vpin = p[0]; 73 | }else if (STRCHR_P((const char *)F("IJMNT"),camop) == nullptr) return false; 74 | param1 = p[1]; 75 | param3 = p[2]; 76 | break; 77 | 78 | case 4: // 79 | if (camop!='A') return false; //must start with 'a' 80 | if (p[3]>316 || p[3]<0) return false; 81 | if (p[2]>236 || p[2]<0) return false; 82 | if (p[1]>97 || p[1]<0) return false; //treat as bsNo. 83 | vpin = vpin + (p[1]/10)*8 + p[1]%10; //translate p[1] 84 | camop=0x80; // special 'a' case for IO_SensorCAM 85 | param1=p[2]; // row 86 | param3=p[3]; // col 87 | break; 88 | 89 | default: 90 | return false; 91 | } 92 | DIAG(F("CamParser: %d %c %d %d"),vpin,camop,param1,param3); 93 | IODevice::writeAnalogue(vpin,param1,camop,param3); 94 | return true; 95 | } -------------------------------------------------------------------------------- /IO_MCP23008.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2022 Paul M Antoine 3 | * © 2021, Neil McKechnie. All rights reserved. 4 | * 5 | * This file is part of DCC++EX API 6 | * 7 | * This is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * It is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with CommandStation. If not, see . 19 | */ 20 | 21 | #ifndef IO_MCP23008_H 22 | #define IO_MCP23008_H 23 | 24 | #include "IO_GPIOBase.h" 25 | 26 | class MCP23008 : public GPIOBase { 27 | public: 28 | static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) { 29 | if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new MCP23008(firstVpin, nPins, i2cAddress, interruptPin); 30 | } 31 | 32 | private: 33 | // Constructor 34 | MCP23008(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) 35 | : GPIOBase((FSH *)F("MCP23008"), firstVpin, nPins, i2cAddress, interruptPin) { 36 | 37 | requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer), 38 | outputBuffer, sizeof(outputBuffer)); 39 | outputBuffer[0] = REG_GPIO; 40 | } 41 | 42 | void _writeGpioPort() override { 43 | I2CManager.write(_I2CAddress, 2, REG_GPIO, _portOutputState); 44 | } 45 | void _writePullups() override { 46 | // Set pullups only for in-use pins. This prevents pullup being set for a pin that 47 | // is intended for use as an output but hasn't been written to yet. 48 | I2CManager.write(_I2CAddress, 2, REG_GPPU, _portPullup & _portInUse); 49 | } 50 | void _writePortModes() override { 51 | // Write 0 to IODIR for in-use pins that are outputs, 1 for others. 52 | uint8_t temp = ~(_portMode & _portInUse); 53 | I2CManager.write(_I2CAddress, 2, REG_IODIR, temp); 54 | // Enable interrupt-on-change for in-use pins that are inputs (_portMode=0) 55 | temp = ~_portMode & _portInUse; 56 | I2CManager.write(_I2CAddress, 2, REG_INTCON, 0x00); 57 | I2CManager.write(_I2CAddress, 2, REG_GPINTEN, temp); 58 | } 59 | void _readGpioPort(bool immediate) override { 60 | if (immediate) { 61 | uint8_t buffer; 62 | I2CManager.read(_I2CAddress, &buffer, 1, 1, REG_GPIO); 63 | _portInputState = buffer | _portMode; 64 | } else { 65 | // Queue new request 66 | requestBlock.wait(); // Wait for preceding operation to complete 67 | // Issue new request to read GPIO register 68 | I2CManager.queueRequest(&requestBlock); 69 | } 70 | } 71 | // This function is invoked when an I/O operation on the requestBlock completes. 72 | void _processCompletion(uint8_t status) override { 73 | if (status == I2C_STATUS_OK) 74 | _portInputState = inputBuffer[0] | _portMode; 75 | else 76 | _portInputState = 0xff; 77 | } 78 | void _setupDevice() override { 79 | // IOCON is set ODR=1 (open drain shared interrupt pin), INTPOL=0 (active-Low) 80 | I2CManager.write(_I2CAddress, 2, REG_IOCON, 0x04); 81 | _writePortModes(); 82 | _writePullups(); 83 | _writeGpioPort(); 84 | } 85 | 86 | uint8_t inputBuffer[1]; 87 | uint8_t outputBuffer[1]; 88 | 89 | enum { 90 | // Register definitions for MCP23008 91 | REG_IODIR=0x00, 92 | REG_GPINTEN=0x02, 93 | REG_INTCON=0x04, 94 | REG_IOCON=0x05, 95 | REG_GPPU=0x06, 96 | REG_GPIO=0x09, 97 | }; 98 | 99 | }; 100 | 101 | #endif 102 | -------------------------------------------------------------------------------- /DisplayInterface.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021 Neil McKechnie 3 | * © 2021 Chris Harlow 4 | * All rights reserved. 5 | * 6 | * This file is part of CommandStation-EX 7 | * 8 | * This is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * It is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU General Public License 19 | * along with CommandStation. If not, see . 20 | */ 21 | #ifndef DisplayInterface_h 22 | #define DisplayInterface_h 23 | 24 | #include 25 | 26 | // Definition of base class for displays. The base class does nothing. 27 | class DisplayInterface : public Print { 28 | protected: 29 | static DisplayInterface *_displayHandler; 30 | static uint8_t _selectedDisplayNo; // Nothing selected. 31 | DisplayInterface *_nextHandler = NULL; 32 | uint8_t _displayNo = 0; 33 | 34 | public: 35 | // Add display object to list of displays 36 | void addDisplay(uint8_t displayNo) { 37 | _nextHandler = _displayHandler; 38 | _displayHandler = this; 39 | _displayNo = displayNo; 40 | } 41 | static DisplayInterface *getDisplayHandler() { 42 | return _displayHandler; 43 | } 44 | uint8_t getDisplayNo() { 45 | return _displayNo; 46 | } 47 | 48 | // The next functions are to provide compatibility with calls to the LCD function 49 | // which does not specify a display number. These always apply to display '0'. 50 | static void refresh() { refresh(0); }; 51 | static void setRow(uint8_t line) { setRow(0, line); }; 52 | static void clear() { clear(0); }; 53 | 54 | // Additional functions to support multiple displays. These perform a 55 | // multicast to all displays that match the selected displayNo. 56 | // Display number zero is the default one. 57 | static void setRow(uint8_t displayNo, uint8_t line) { 58 | _selectedDisplayNo = displayNo; 59 | for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler) { 60 | if (displayNo == p->_displayNo) p->_setRow(line); 61 | } 62 | } 63 | size_t write (uint8_t c) override { 64 | for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler) 65 | if (_selectedDisplayNo == p->_displayNo) p->_write(c); 66 | return _displayHandler ? 1 : 0; 67 | } 68 | static void clear(uint8_t displayNo) { 69 | for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler) 70 | if (displayNo == p->_displayNo) p->_clear(); 71 | } 72 | static void refresh(uint8_t displayNo) { 73 | for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler) 74 | if (displayNo == p->_displayNo) p->_refresh(); 75 | } 76 | static void loop() { 77 | for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler) 78 | p->_displayLoop(); 79 | }; 80 | // The following are overridden within the specific device class 81 | virtual void begin() {}; 82 | virtual size_t _write(uint8_t c) { (void)c; return 0; }; 83 | virtual void _setRow(uint8_t line) { (void)line; } 84 | virtual void _clear() {} 85 | virtual void _refresh() {} 86 | virtual void _displayLoop() {} 87 | }; 88 | 89 | class DisplayDevice { 90 | public: 91 | virtual bool begin() { return true; } 92 | virtual void clearNative() = 0; 93 | virtual void setRowNative(uint8_t line) = 0; 94 | virtual size_t writeNative(uint8_t c) = 0; 95 | virtual bool isBusy() = 0; 96 | virtual uint16_t getNumRows() = 0; 97 | virtual uint16_t getNumCols() = 0; 98 | }; 99 | #endif 100 | -------------------------------------------------------------------------------- /KeywordHasher.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2024 Vincent Hamp and Chris Harlow 3 | * All rights reserved. 4 | * 5 | * This file is part of CommandStation-EX 6 | * 7 | * This is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * It is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with CommandStation. If not, see . 19 | */ 20 | 21 | 22 | /* Reader be aware: 23 | This function implements the _hk data type so that a string keyword 24 | is hashed to the same value as the DCCEXParser uses to hash incoming 25 | keywords. 26 | Thus "MAIN"_hk generates exactly the same run time vakue 27 | as const int16_t HASH_KEYWORD_MAIN=11339 28 | */ 29 | #ifndef KeywordHasher_h 30 | #define KeywordHasher_h 31 | 32 | #include 33 | constexpr uint16_t CompiletimeKeywordHasher(const char * sv, uint16_t running=0) { 34 | return (*sv==0) ? running : CompiletimeKeywordHasher(sv+1, 35 | (*sv >= '0' && *sv <= '9') 36 | ? (10*running+*sv-'0') // Numeric hash 37 | : ((running << 5) + running) ^ *sv 38 | ); // 39 | } 40 | 41 | constexpr int16_t operator""_hk(const char * keyword, size_t len) 42 | { 43 | return (int16_t) CompiletimeKeywordHasher(keyword,len*0); 44 | } 45 | 46 | /* Some historical values for testing: 47 | const int16_t HASH_KEYWORD_MAIN = 11339; 48 | const int16_t HASH_KEYWORD_SLOW = -17209; 49 | const int16_t HASH_KEYWORD_SPEED28 = -17064; 50 | const int16_t HASH_KEYWORD_SPEED128 = 25816; 51 | */ 52 | 53 | static_assert("MAIN"_hk == 11339,"Keyword hasher error"); 54 | static_assert("SLOW"_hk == -17209,"Keyword hasher error"); 55 | static_assert("SPEED28"_hk == -17064,"Keyword hasher error"); 56 | static_assert("SPEED128"_hk == 25816,"Keyword hasher error"); 57 | 58 | // Compile time converter from "abcd"_s7 to the 7 segment nearest equivalent 59 | 60 | constexpr uint8_t seg7Digits[]={ 61 | 0b00111111,0b00000110,0b01011011,0b01001111, // 0..3 62 | 0b01100110,0b01101101,0b01111101,0b00000111, // 4..7 63 | 0b01111111,0b01101111 // 8..9 64 | }; 65 | 66 | constexpr uint8_t seg7Letters[]={ 67 | 0b01110111,0b01111100,0b00111001,0b01011110, // ABCD 68 | 0b01111001,0b01110001,0b00111101,0b01110110, // EFGH 69 | 0b00000100,0b00011110,0b01110010,0b00111000, //IJKL 70 | 0b01010101,0b01010100,0b01011100,0b01110011, // MNOP 71 | 0b10111111,0b01010000,0b01101101,0b01111000, // QRST 72 | 0b00111110,0b00011100,0b01101010,0b01001001, //UVWX 73 | 0b01100110,0b01011011 //YZ 74 | }; 75 | constexpr uint8_t seg7Space=0b00000000; 76 | constexpr uint8_t seg7Minus=0b01000000; 77 | constexpr uint8_t seg7Equals=0b01001000; 78 | 79 | 80 | constexpr uint32_t CompiletimeSeg7(const char * sv, uint32_t running, size_t rlen) { 81 | return (*sv==0 || rlen==0) ? running << (8*rlen) : CompiletimeSeg7(sv+1, 82 | (*sv >= '0' && *sv <= '9') ? (running<<8) | seg7Digits[*sv-'0'] : 83 | (*sv >= 'A' && *sv <= 'Z') ? (running<<8) | seg7Letters[*sv-'A'] : 84 | (*sv >= 'a' && *sv <= 'z') ? (running<<8) | seg7Letters[*sv-'a'] : 85 | (*sv == '-') ? (running<<8) | seg7Minus : 86 | (*sv == '=') ? (running<<8) | seg7Equals : 87 | (running<<8) | seg7Space, 88 | rlen-1 89 | ); // 90 | } 91 | 92 | constexpr uint32_t operator""_s7(const char * keyword, size_t len) 93 | { 94 | return CompiletimeSeg7(keyword,0*len,4); 95 | } 96 | #endif 97 | -------------------------------------------------------------------------------- /Release_Notes/ThrottleAssists.md: -------------------------------------------------------------------------------- 1 | Throttle Assist updates for versiuon 4.? 2 | 3 | Chris Harlow April 2022 4 | 5 | There are a number of additional throttle information commands that have been implemented to assist throttle authors to obtain information from the Command Station in order to implement turnout, route/automation and roster features which are already found in the Withrottle implementations. 6 | These commands are new and not overlapped with the existing commands which are probabaly due to be obsoleted as they are over complex and unfit for purpose. 7 | 8 | Turnouts: 9 | 10 | The conventional turnout definition commands and the `````` responses do not contain information about the turnout description which may have been provided in an EXRAIL script. A turnout description is much more user friendly than T123 and having a list helps the throttle UI build a suitable set of buttons. 11 | 12 | `````` command returns a list of turnout ids. The throttle should be uninterested in the turnout technology used but needs to know the ids it can throw/close and monitor the current state. 13 | e.g. response `````` 14 | 15 | ````` requests info on turnout 17. 16 | e.g. response `````` or `````` 17 | (T=thrown, C=closed) 18 | or `````` indicating turnout description not given. 19 | or `````` indicating turnout unknown (or possibly hidden.) 20 | 21 | Note: It is still the throttles responsibility to monitor the status broadcasts. 22 | (TBD I'm thinking that the existing broadcast is messy and needs cleaning up) 23 | However, I'm not keen on dynamically created/deleted turnouts so I have no intention of providing a command that indicates the turnout list has been updated since the throttle started. 24 | Also note that turnouts marked in EXRAIL with the HIDDEN keyword instead of a "description" will NOT show up in these commands. 25 | 26 | 27 | Automations/Routes 28 | 29 | A throttle need to know which EXRAIL Automations and Routes it can show the user. 30 | 31 | `````` Returns a list of Automations/Routes 32 | e.g. `````` 33 | Indicates route/automation ids. 34 | Information on each route needs to be obtained by 35 | `````` 36 | returns e.g. `````` for a route 37 | or `````` for an automation. 38 | or `````` for id not found 39 | 40 | Whats the difference: 41 | A Route is just a call to an EXRAIL ROUTE, traditionally to set some turnouts or signals but can be used to perform any kind of EXRAIL function... but its not expecting to know the loco. 42 | Thus a route can be triggered by sending in for example ``````. 43 | 44 | An Automation is a handoff of the last accessed loco id to an EXRAIL AUTOMATION which would typically drive the loco away. 45 | Thus an Automation expects a start command with a cab id 46 | e.g. `````` 47 | 48 | 49 | Roster Information: 50 | The `````` command requests a list of cab ids from the roster. 51 | e.g. responding `````` 52 | or for none. 53 | 54 | Each Roster entry had a name and function map obtained by: 55 | `````` reply like ``` 56 | 57 | Refer to EXRAIL ROSTER command for function map format. 58 | 59 | 60 | Obtaining throttle status. 61 | `````` Requests a deliberate update on the cab speed/functions in the same format as the cab broadcast. 62 | `````` 63 | Note that a slot of -1 indicates that the cab is not in the reminders table and this comand will not reserve a slot until such time as the cab is throttled. 64 | 65 | 66 | COMMANDS TO AVOID 67 | 68 | `````` Use `````` 69 | `````` Just drop the slot number 70 | `````` other than `````` 71 | `````` 72 | `````` 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /Sensors.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021 Neil McKechnie 3 | * © 2020-2021 Harald Barth 4 | * © 2020-2021 Chris Harlow 5 | * All rights reserved. 6 | * 7 | * This file is part of Asbelos DCC API 8 | * 9 | * This is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * It is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with CommandStation. If not, see . 21 | */ 22 | #ifndef Sensor_h 23 | #define Sensor_h 24 | 25 | #include "Arduino.h" 26 | #include "IODevice.h" 27 | 28 | // Uncomment the following #define statement to use callback notification 29 | // where the driver supports it. 30 | // The principle of callback notification is to avoid the Sensor class 31 | // having to poll the device driver cyclically for input values, and then scan 32 | // for changes. Instead, when the driver scans the inputs, if it detects 33 | // a change it invokes a callback function in the Sensor class. In the current 34 | // implementation, the advantages are limited because (a) the Sensor class 35 | // performs debounce checks, and (b) the Sensor class does not have a 36 | // static reference to the output stream for sending / messages 37 | // when a change is detected. These restrictions mean that the checkAll() 38 | // method still has to iterate through all of the Sensor objects looking 39 | // for changes. 40 | #define USE_NOTIFY 41 | 42 | struct SensorData { 43 | int snum; 44 | VPIN pin; 45 | uint8_t pullUp; 46 | }; 47 | 48 | class Sensor{ 49 | // The sensor list is a linked list where each sensor's 'nextSensor' field points to the next. 50 | // The pointer is null in the last on the list. 51 | 52 | public: 53 | SensorData data; 54 | struct { 55 | uint8_t active:1; 56 | uint8_t inputState:1; 57 | uint8_t latchDelay:6; 58 | }; // bit 7=active; bit 6=input state; bits 5-0=latchDelay 59 | 60 | static Sensor *firstSensor; 61 | #ifdef USE_NOTIFY 62 | static Sensor *firstPollSensor; 63 | static Sensor *lastSensor; 64 | #endif 65 | // readingSensor points to the next sensor to be polled, or null if the poll cycle is completed for 66 | // the period. 67 | static Sensor *readingSensor; 68 | 69 | // Constructor 70 | Sensor(); 71 | Sensor *nextSensor; 72 | 73 | void setState(int state); 74 | #ifndef DISABLE_EEPROM 75 | static void load(); 76 | static void store(); 77 | #endif 78 | static Sensor *create(int id, VPIN vpin, int pullUp); 79 | static void createMultiple(VPIN firstPin, byte count=1); 80 | static Sensor* get(int id); 81 | static bool remove(int id); 82 | static void checkAll(); 83 | static void printAll(Print *stream); 84 | static unsigned long lastReadCycle; // value of micros at start of last read cycle 85 | static const unsigned int cycleInterval = 10000; // min time between consecutive reads of each sensor in microsecs. 86 | // should not be less than device scan cycle time. 87 | static const unsigned int minReadCount = 1; // number of additional scans before acting on change 88 | // E.g. 1 means that a change is ignored for one scan and actioned on the next. 89 | // Max value is 63 90 | bool pollingRequired = true; 91 | 92 | #ifdef USE_NOTIFY 93 | static void inputChangeCallback(VPIN vpin, int state); 94 | static bool inputChangeCallbackRegistered; 95 | #endif 96 | 97 | }; // Sensor 98 | 99 | #endif 100 | -------------------------------------------------------------------------------- /EEStore.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021 Neil McKechnie 3 | * © 2021 Fred Decker 4 | * © 2020-2022 Harald Barth 5 | * © 2020-2021 Chris Harlow 6 | * © 2013-2016 Gregg E. Berman 7 | * All rights reserved. 8 | * 9 | * This file is part of CommandStation-EX 10 | * 11 | * This is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, either version 3 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * It is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with CommandStation. If not, see . 23 | */ 24 | 25 | #include "defines.h" 26 | #ifndef DISABLE_EEPROM 27 | #include "EEStore.h" 28 | 29 | #include "DIAG.h" 30 | #include "Outputs.h" 31 | #include "Sensors.h" 32 | #include "Turnouts.h" 33 | 34 | #if defined(ARDUINO_ARCH_SAMC) 35 | ExternalEEPROM EEPROM; 36 | #endif 37 | 38 | void EEStore::init() { 39 | #if defined(ARDUINO_ARCH_SAMC) 40 | EEPROM.begin(0x50); // Address for Microchip 24-series EEPROM with all three 41 | // A pins grounded (0b1010000 = 0x50) 42 | #endif 43 | 44 | eeStore = (EEStore *)calloc(1, sizeof(EEStore)); 45 | 46 | EEPROM.get(0, eeStore->data); // get eeStore data 47 | 48 | // check to see that eeStore contains valid DCC++ ID 49 | if (strncmp(eeStore->data.id, EESTORE_ID, sizeof(EESTORE_ID)) != 0) { 50 | // if not, create blank eeStore structure (no 51 | // turnouts, no sensors) and save it back to EEPROM 52 | strncpy(eeStore->data.id, EESTORE_ID, sizeof(EESTORE_ID)+0); 53 | eeStore->data.nTurnouts = 0; 54 | eeStore->data.nSensors = 0; 55 | eeStore->data.nOutputs = 0; 56 | EEPROM.put(0, eeStore->data); 57 | } 58 | 59 | reset(); // set memory pointer to first free EEPROM space 60 | Turnout::load(); // load turnout definitions 61 | Sensor::load(); // load sensor definitions 62 | Output::load(); // load output definitions 63 | } 64 | 65 | /////////////////////////////////////////////////////////////////////////////// 66 | 67 | void EEStore::clear() { 68 | sprintf(eeStore->data.id, 69 | EESTORE_ID); // create blank eeStore structure (no turnouts, no 70 | // sensors) and save it back to EEPROM 71 | eeStore->data.nTurnouts = 0; 72 | eeStore->data.nSensors = 0; 73 | eeStore->data.nOutputs = 0; 74 | EEPROM.put(0, eeStore->data); 75 | } 76 | 77 | /////////////////////////////////////////////////////////////////////////////// 78 | 79 | void EEStore::store() { 80 | reset(); 81 | Turnout::store(); 82 | Sensor::store(); 83 | Output::store(); 84 | EEPROM.put(0, eeStore->data); 85 | DIAG(F("EEPROM used: %d/%d bytes"), EEStore::pointer(), EEPROM.length()); 86 | } 87 | 88 | /////////////////////////////////////////////////////////////////////////////// 89 | 90 | void EEStore::advance(int n) { eeAddress += n; } 91 | 92 | /////////////////////////////////////////////////////////////////////////////// 93 | 94 | void EEStore::reset() { eeAddress = sizeof(EEStore); } 95 | /////////////////////////////////////////////////////////////////////////////// 96 | 97 | int EEStore::pointer() { return (eeAddress); } 98 | /////////////////////////////////////////////////////////////////////////////// 99 | 100 | void EEStore::dump(int num) { 101 | byte b = 0; 102 | DIAG(F("Addr 0x char")); 103 | for (int n = 0; n < num; n++) { 104 | EEPROM.get(n, b); 105 | DIAG(F("%d %x %c"), n, b, isprint(b) ? b : ' '); 106 | } 107 | } 108 | /////////////////////////////////////////////////////////////////////////////// 109 | 110 | EEStore *EEStore::eeStore = NULL; 111 | int EEStore::eeAddress = 0; 112 | #endif 113 | -------------------------------------------------------------------------------- /Release_Notes/TM1638.md: -------------------------------------------------------------------------------- 1 | ## TM1638 ## 2 | 3 | The TM1638 board provides a very cheap way of implementing 8 buttons, 8 leds and an 8 digit 7segment display in a package requiring just 5 Dupont wires (vcc, gnd + 3 GPIO pins) from the command station without soldering. 4 | 5 | 6 | This is ideal for prototyping and testing, simulating sensors and signals, displaying states etc. For a built layout, this could provide a control for things that are not particularly suited to throttle 'route' buttons, perhaps lineside automations or fiddle yard lane selection. 7 | 8 | By adding a simple HAL statement to myAutomation.h it creates 8 buttons/sensors and 8 leds. 9 | 10 | `HAL(TM1638,500,29,31,33)` 11 | Creates VPINs 500-507 And desscribes the GPIO pins used to connect the clk,dio,stb pins on the TM1638 board. 12 | 13 | Setting each of the VPINs will control the associated LED (using for example SET, RESET or BLINK in Exrail or ` from a command). 14 | 15 | Unlike most pins, you can also read the same pin number and get the button state, using Exrail IF/AT/ONBUTTON etc. 16 | 17 | For example: 18 | ` 19 | HAL(TM1638,500,29,31,33) 20 | ` 21 | All the folowing examples assume you are using VPIN 500 as the first, leftmost, led/button on the TM1638 board. 22 | 23 | 24 | `ONBUTTON(500) 25 | SET(500) // light the first led 26 | BLINK(501,500,500) // blink the second led 27 | SETLOCO(3) FWD(50) // set a loco going 28 | AT(501) STOP // press second button to stop 29 | RESET(500) RESET(501) // turn leds off 30 | DONE 31 | ` 32 | 33 | Buttons behave like any other sensor, so using `` will cause the command station to issue `` and `` messages when the first button is pressed or released. 34 | 35 | Exrail `JMRI_SENSOR(500,8)` will create `. 18 | */ 19 | 20 | /* 21 | * The IO_EXFastclock device driver is used to interface the standalone fast clock and receive time data. 22 | * 23 | * The EX-fastClock code lives in a separate repo (https://github.com/DCC-EX/EX-Fastclock) and contains the clock logic. 24 | * 25 | * 26 | */ 27 | 28 | #ifndef IO_EXFastclock_h 29 | #define IO_EXFastclock_h 30 | 31 | 32 | #include "IODevice.h" 33 | #include "I2CManager.h" 34 | #include "DIAG.h" 35 | #include "EXRAIL2.h" 36 | #include "CommandDistributor.h" 37 | 38 | bool FAST_CLOCK_EXISTS = true; 39 | 40 | class EXFastClock : public IODevice { 41 | public: 42 | // Constructor 43 | EXFastClock(I2CAddress i2cAddress){ 44 | _I2CAddress = i2cAddress; 45 | addDevice(this); 46 | } 47 | 48 | static void create(I2CAddress i2cAddress) { 49 | 50 | DIAG(F("Checking for Clock")); 51 | // Start by assuming we will find the clock 52 | // Check if specified I2C address is responding (blocking operation) 53 | // Returns I2C_STATUS_OK (0) if OK, or error code. 54 | I2CManager.begin(); 55 | uint8_t _checkforclock = I2CManager.checkAddress(i2cAddress); 56 | DIAG(F("Clock check result - %d"), _checkforclock); 57 | // XXXX change thistosave2 bytes 58 | if (_checkforclock == 0) { 59 | FAST_CLOCK_EXISTS = true; 60 | //DIAG(F("I2C Fast Clock found at %s"), i2cAddress.toString()); 61 | new EXFastClock(i2cAddress); 62 | } 63 | else { 64 | FAST_CLOCK_EXISTS = false; 65 | //DIAG(F("No Fast Clock found")); 66 | LCD(6,F("CLOCK NOT FOUND")); 67 | } 68 | 69 | } 70 | 71 | private: 72 | 73 | 74 | // Initialisation of Fastclock 75 | void _begin() override { 76 | 77 | if (FAST_CLOCK_EXISTS == true) { 78 | I2CManager.begin(); 79 | if (I2CManager.exists(_I2CAddress)) { 80 | _deviceState = DEVSTATE_NORMAL; 81 | #ifdef DIAG_IO 82 | _display(); 83 | #endif 84 | } else { 85 | _deviceState = DEVSTATE_FAILED; 86 | //LCD(6,F("CLOCK NOT FOUND")); 87 | DIAG(F("Fast Clock Not Found at address %s"), _I2CAddress.toString()); 88 | } 89 | } 90 | } 91 | 92 | // Processing loop to obtain clock time 93 | 94 | void _loop(unsigned long currentMicros) override{ 95 | 96 | if (FAST_CLOCK_EXISTS==true) { 97 | uint8_t readBuffer[3]; 98 | byte a,b; 99 | #ifdef EXRAIL_ACTIVE 100 | I2CManager.read(_I2CAddress, readBuffer, 3); 101 | // XXXX change this to save a few bytes 102 | a = readBuffer[0]; 103 | b = readBuffer[1]; 104 | //_clocktime = (a << 8) + b; 105 | //_clockrate = readBuffer[2]; 106 | 107 | CommandDistributor::setClockTime(((a << 8) + b), readBuffer[2], 1); 108 | //setClockTime(int16_t clocktime, int8_t clockrate, byte opt); 109 | 110 | // As the minimum clock increment is 2 seconds delay a bit - say 1 sec. 111 | // Clock interval is 60/ clockspeed i.e 60/b seconds 112 | delayUntil(currentMicros + ((60/b) * 1000000)); 113 | 114 | #endif 115 | 116 | } 117 | } 118 | 119 | // Display EX-FastClock device driver info. 120 | void _display() override { 121 | DIAG(F("FastCLock on I2C:%s - %S"), _I2CAddress.toString(), (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F("")); 122 | } 123 | 124 | }; 125 | 126 | #endif 127 | -------------------------------------------------------------------------------- /FSH.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2022 Paul M. Antoine 3 | * © 2021 Neil McKechnie 4 | * © 2021 Harald Barth 5 | * © 2021 Fred Decker 6 | * All rights reserved. 7 | * 8 | * This file is part of CommandStation-EX 9 | * 10 | * This is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * It is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with CommandStation. If not, see . 22 | */ 23 | #ifndef FSH_h 24 | #define FSH_h 25 | 26 | /* This is an architecture support file to manage the differences 27 | * between the nano/uno.mega and the later nanoEvery, unoWifiRev2 etc 28 | * 29 | * IMPORTANT: 30 | * To maintain portability the main code should NOT contain ANY references 31 | * to the following: 32 | * 33 | * __FlashStringHelper Use FSH instead. 34 | * PROGMEM use FLASH instead 35 | * pgm_read_byte_near use GETFLASH instead. 36 | * pgm_read_word_near use GETFLASHW instead. 37 | * 38 | * Also: 39 | * HIGHFLASH - PROGMEM forced to end of link so needs far pointers. 40 | * GETHIGHFLASH,GETHIGHFLASHW to access them 41 | * 42 | */ 43 | #include 44 | #ifdef ARDUINO_ARCH_AVR 45 | // AVR devices have flash memory mapped differently 46 | // progmem can be accessed by _near functions or _far 47 | typedef __FlashStringHelper FSH; 48 | #define FLASH PROGMEM 49 | #define GETFLASH(addr) pgm_read_byte_near(addr) 50 | #define STRCPY_P strcpy_P 51 | #define STRCMP_P strcmp_P 52 | #define STRNCPY_P strncpy_P 53 | #define STRNCMP_P strncmp_P 54 | #define STRLEN_P strlen_P 55 | #define STRCHR_P strchr_P 56 | 57 | #if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) 58 | // AVR_MEGA memory deliberately placed at end of link may need _far functions 59 | #define HIGHFLASH __attribute__((section(".fini2"))) 60 | #define HIGHFLASH3 __attribute__((section(".fini3"))) 61 | #define GETFARPTR(data) pgm_get_far_address(data) 62 | #define GETHIGHFLASH(data,offset) pgm_read_byte_far(GETFARPTR(data)+offset) 63 | #define GETHIGHFLASHW(data,offset) pgm_read_word_far(GETFARPTR(data)+offset) 64 | #define COPYHIGHFLASH(target,base,offset,length) \ 65 | memcpy_PF(target,GETFARPTR(base) + offset,length) 66 | #else 67 | // AVR_UNO/NANO runtime does not support _far functions so just use _near equivalent 68 | // as there is no progmem above 32kb anyway. 69 | #define HIGHFLASH PROGMEM 70 | #define HIGHFLASH3 PROGMEM 71 | #define GETFARPTR(data) ((uint32_t)(data)) 72 | #define GETHIGHFLASH(data,offset) pgm_read_byte_near(GETFARPTR(data)+(offset)) 73 | #define GETHIGHFLASHW(data,offset) pgm_read_word_near(GETFARPTR(data)+(offset)) 74 | #define COPYHIGHFLASH(target,base,offset,length) \ 75 | memcpy_P(target,(byte *)base + offset,length) 76 | #endif 77 | 78 | #else 79 | // Non-AVR Flat-memory devices have no need of this support so can be remapped to normal memory access 80 | #ifdef F 81 | #undef F 82 | #endif 83 | #ifdef FLASH 84 | #undef FLASH 85 | #endif 86 | #define F(str) (str) 87 | typedef char FSH; 88 | #define FLASH 89 | #define HIGHFLASH 90 | #define HIGHFLASH3 91 | #define GETFARPTR(data) ((uint32_t)(data)) 92 | #define GETFLASH(addr) (*(const byte *)(addr)) 93 | #define GETHIGHFLASH(data,offset) (*(const byte *)(GETFARPTR(data)+offset)) 94 | #define GETHIGHFLASHW(data,offset) (*(const uint16_t *)(GETFARPTR(data)+offset)) 95 | #define COPYHIGHFLASH(target,base,offset,length) \ 96 | memcpy(target,(byte *)&base + offset,length) 97 | #define STRCPY_P strcpy 98 | #define STRCMP_P strcmp 99 | #define STRNCPY_P strncpy 100 | #define STRNCMP_P strncmp 101 | #define STRLEN_P strlen 102 | #define STRCHR_P strchr 103 | #endif 104 | #endif 105 | -------------------------------------------------------------------------------- /EXRAILSensor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2024 Chris Harlow 3 | * All rights reserved. 4 | * 5 | * This file is part of CommandStation-EX 6 | * 7 | * This is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * It is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with CommandStation. If not, see . 19 | */ 20 | 21 | /********************************************************************** 22 | EXRAILSensor represents a sensor that should be monitored in order 23 | to call an exrail ONBUTTON or ONCHANGE handler. 24 | These are created at EXRAIL startup and thus need no delete or listing 25 | capability. 26 | The basic logic is similar to that found in the Sensor class 27 | except that on the relevant change an EXRAIL thread is started. 28 | **********************************************************************/ 29 | 30 | #include "EXRAILSensor.h" 31 | #include "EXRAIL2.h" 32 | 33 | void EXRAILSensor::checkAll() { 34 | if (firstSensor == NULL) return; // No sensors to be scanned 35 | if (readingSensor == NULL) { 36 | // Not currently scanning sensor list 37 | unsigned long thisTime = micros(); 38 | if (thisTime - lastReadCycle < cycleInterval) return; 39 | // Required time has elapsed since last read cycle started, 40 | // so initiate new scan through the sensor list 41 | readingSensor = firstSensor; 42 | lastReadCycle = thisTime; 43 | } 44 | 45 | // Loop until either end of list is encountered or we pause for some reason 46 | byte sensorCount = 0; 47 | 48 | while (readingSensor != NULL) { 49 | bool pause=readingSensor->check(); 50 | // Move to next sensor in list. 51 | readingSensor = readingSensor->nextSensor; 52 | // Currently process max of 16 sensors per entry. 53 | // Performance measurements taken during development indicate that, with 128 sensors configured 54 | // on 8x 16-pin MCP23017 GPIO expanders with polling (no change notification), all inputs can be read from the devices 55 | // within 1.4ms (400Mhz I2C bus speed), and a full cycle of checking 128 sensors for changes takes under a millisecond. 56 | if (pause || (++sensorCount)>=16) return; 57 | } 58 | } 59 | 60 | bool EXRAILSensor::check() { 61 | // check for debounced change in this sensor 62 | inputState = RMFT2::readSensor(pin); 63 | 64 | // Check if changed since last time, and process changes. 65 | if (inputState == active) {// no change 66 | latchDelay = minReadCount; // Reset counter 67 | return false; // no change 68 | } 69 | 70 | // Change detected ... has it stayed changed for long enough 71 | if (latchDelay > 0) { 72 | latchDelay--; 73 | return false; 74 | } 75 | 76 | // change validated, act on it. 77 | active = inputState; 78 | latchDelay = minReadCount; // Reset debounce counter 79 | if (onChange || active) { 80 | new RMFT2(progCounter); 81 | return true; // Don't check any more sensors on this entry 82 | } 83 | return false; 84 | } 85 | 86 | EXRAILSensor::EXRAILSensor(VPIN _pin, int _progCounter, bool _onChange) { 87 | // Add to the start of the list 88 | //DIAG(F("ONthing vpin=%d at %d"), _pin, _progCounter); 89 | nextSensor = firstSensor; 90 | firstSensor = this; 91 | 92 | pin=_pin; 93 | progCounter=_progCounter; 94 | onChange=_onChange; 95 | 96 | IODevice::configureInput(pin, true); 97 | active = IODevice::read(pin); 98 | inputState = active; 99 | latchDelay = minReadCount; 100 | } 101 | 102 | EXRAILSensor *EXRAILSensor::firstSensor=NULL; 103 | EXRAILSensor *EXRAILSensor::readingSensor=NULL; 104 | unsigned long EXRAILSensor::lastReadCycle=0; 105 | -------------------------------------------------------------------------------- /IO_TM1638.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2024, Chris Harlow. All rights reserved. 3 | * 4 | * This file is part of DCC++EX API 5 | * 6 | * This is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * It is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with CommandStation. If not, see . 18 | */ 19 | 20 | #ifndef IO_TM1638_h 21 | #define IO_TM1638_h 22 | #include 23 | #include "IODevice.h" 24 | #include "DIAG.h" 25 | 26 | class TM1638 : public IODevice { 27 | private: 28 | 29 | uint8_t _buttons; 30 | uint8_t _leds; 31 | unsigned long _lastLoop; 32 | static const int LoopHz=20; 33 | 34 | static const byte 35 | INSTRUCTION_WRITE_DATA=0x40, 36 | INSTRUCTION_READ_KEY=0x42, 37 | INSTRUCTION_ADDRESS_AUTO=0x40, 38 | INSTRUCTION_ADDRESS_FIXED=0x44, 39 | INSTRUCTION_NORMAL_MODE=0x40, 40 | INSTRUCTION_TEST_MODE=0x48, 41 | 42 | FIRST_DISPLAY_ADDRESS=0xC0, 43 | 44 | DISPLAY_TURN_OFF=0x80, 45 | DISPLAY_TURN_ON=0x88; 46 | 47 | 48 | uint8_t _clk_pin; 49 | uint8_t _stb_pin; 50 | uint8_t _dio_pin; 51 | uint8_t _pulse; 52 | bool _isOn; 53 | 54 | 55 | // Constructor 56 | TM1638(VPIN firstVpin, byte clk_pin,byte dio_pin,byte stb_pin); 57 | 58 | public: 59 | enum DigitFormat : byte { 60 | // last 4 bits are length. 61 | // DF_1.. DF_8 decimal 62 | DF_1=0x01,DF_2=0x02,DF_3=0x03,DF_4=0x04, 63 | DF_5=0x05,DF_6=0x06,DF_7=0x07,DF_8=0x08, 64 | // DF_1X.. DF_8X HEX 65 | DF_1X=0x11,DF_2X=0x12,DF_3X=0x13,DF_4X=0x14, 66 | DF_5X=0x15,DF_6X=0x16,DF_7X=0x17,DF_8X=0x18, 67 | // DF_1R .. DF_4R raw 7 segmnent data 68 | // only 4 because HAL analogWrite only passes 4 bytes 69 | DF_1R=0x21,DF_2R=0x22,DF_3R=0x23,DF_4R=0x24, 70 | 71 | // bits of data conversion type (ored with length) 72 | _DF_DECIMAL=0x00,// right adjusted decimal unsigned leading zeros 73 | _DF_HEX=0x10, // right adjusted hex leading zeros 74 | _DF_RAW=0x20 // bytes are raw 7-segment pattern (max length 4) 75 | }; 76 | 77 | static void create(VPIN firstVpin, byte clk_pin,byte dio_pin,byte stb_pin); 78 | 79 | // Functions overridden in IODevice 80 | void _begin(); 81 | void _loop(unsigned long currentMicros) override ; 82 | void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override; 83 | void _display() override ; 84 | int _read(VPIN pin) override; 85 | void _write(VPIN pin,int value) override; 86 | 87 | // Device driving functions 88 | private: 89 | enum pulse_t { 90 | PULSE1_16, 91 | PULSE2_16, 92 | PULSE4_16, 93 | PULSE10_16, 94 | PULSE11_16, 95 | PULSE12_16, 96 | PULSE13_16, 97 | PULSE14_16 98 | }; 99 | 100 | /** 101 | * @fn getButtons 102 | * @return state of 8 buttons 103 | */ 104 | uint8_t getButtons(); 105 | 106 | /** 107 | * @fn writeLed 108 | * @brief put led ON or OFF 109 | * @param num num of led(1-8) 110 | * @param state (true or false) 111 | */ 112 | void writeLed(uint8_t num, bool state); 113 | 114 | 115 | /** 116 | * @fn displayDig 117 | * @brief set 7 segment display + dot 118 | * @param digitId num of digit(0-7) 119 | * @param val value 8 bits 120 | */ 121 | void displayDig(uint8_t digitId, uint8_t pgfedcba); 122 | 123 | /** 124 | * @fn displayClear 125 | * @brief switch off all leds and segment display 126 | */ 127 | void displayClear(); 128 | void test(); 129 | void writeData(uint8_t data); 130 | void writeDataAt(uint8_t displayAddress, uint8_t data); 131 | void setDisplayMode(uint8_t displayMode); 132 | void setDataInstruction(uint8_t dataInstruction); 133 | }; 134 | #endif 135 | -------------------------------------------------------------------------------- /IO_PCF8574.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2022 Paul M Antoine 3 | * © 2021, Neil McKechnie. All rights reserved. 4 | * 5 | * This file is part of DCC++EX API 6 | * 7 | * This is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * It is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with CommandStation. If not, see . 19 | */ 20 | 21 | /* 22 | * The PCF8574 is a simple device; it only has one register. The device 23 | * input/output mode and pullup are configured through this, and the 24 | * output state is written and the input state read through it too. 25 | * 26 | * This is accomplished by having a weak resistor in series with the output, 27 | * and a read-back of the other end of the resistor. As an output, the 28 | * pin state is set to 1 or 0, and the output voltage goes to +5V or 0V 29 | * (through the weak resistor). 30 | * 31 | * In order to use the pin as an input, the output is written as 32 | * a '1' in order to pull up the resistor. Therefore the input will be 33 | * 1 unless the pin is pulled down externally, in which case it will be 0. 34 | * 35 | * As a consequence of this approach, it is not possible to use the device for 36 | * inputs without pullups. 37 | */ 38 | 39 | #ifndef IO_PCF8574_H 40 | #define IO_PCF8574_H 41 | 42 | #include "IO_GPIOBase.h" 43 | 44 | class PCF8574 : public GPIOBase { 45 | public: 46 | static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) { 47 | if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCF8574(firstVpin, nPins, i2cAddress, interruptPin); 48 | } 49 | 50 | private: 51 | PCF8574(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) 52 | : GPIOBase((FSH *)F("PCF8574"), firstVpin, nPins, i2cAddress, interruptPin) 53 | { 54 | requestBlock.setReadParams(_I2CAddress, inputBuffer, 1); 55 | } 56 | 57 | // The PCF8574 handles inputs by applying a weak pull-up when output is driven to '1'. 58 | // The pin state is driven '1' if the pin is an input, or if it is an output set to 1. 59 | // Unused pins are driven '0'. 60 | void _writeGpioPort() override { 61 | I2CManager.write(_I2CAddress, 1, (_portOutputState | ~_portMode) & _portInUse); 62 | } 63 | 64 | // The PCF8574 handles inputs by applying a weak pull-up when output is driven to '1'. 65 | // Therefore, writing '1' in _writePortModes is enough to set the module to input mode 66 | // and enable pull-up. 67 | void _writePullups() override { } 68 | 69 | void _writePortModes() override { 70 | _writeGpioPort(); 71 | } 72 | 73 | // In immediate mode, _readGpioPort reads the device GPIO port and updates _portInputState accordingly. 74 | // When not in immediate mode, it initiates a request using the request block and returns. 75 | // When the request completes, _processCompletion finishes the operation. 76 | void _readGpioPort(bool immediate) override { 77 | if (immediate) { 78 | uint8_t buffer[1]; 79 | I2CManager.read(_I2CAddress, buffer, 1); 80 | _portInputState = buffer[0] | _portMode; 81 | } else { 82 | requestBlock.wait(); // Wait for preceding operation to complete 83 | // Issue new request to read GPIO register 84 | I2CManager.queueRequest(&requestBlock); 85 | } 86 | } 87 | 88 | // This function is invoked when an I/O operation on the requestBlock completes. 89 | void _processCompletion(uint8_t status) override { 90 | if (status == I2C_STATUS_OK) 91 | _portInputState = inputBuffer[0] | _portMode; 92 | else 93 | _portInputState = 0xff; 94 | } 95 | 96 | // Set up device ports 97 | void _setupDevice() override { 98 | _writePortModes(); 99 | } 100 | 101 | uint8_t inputBuffer[1]; 102 | }; 103 | 104 | #endif 105 | -------------------------------------------------------------------------------- /IO_MCP23017.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021, Neil McKechnie. All rights reserved. 3 | * 4 | * This file is part of DCC++EX API 5 | * 6 | * This is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * It is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with CommandStation. If not, see . 18 | */ 19 | 20 | #ifndef io_mcp23017_h 21 | #define io_mcp23017_h 22 | 23 | #include "IO_GPIOBase.h" 24 | #include "FSH.h" 25 | 26 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 27 | /* 28 | * IODevice subclass for MCP23017 16-bit I/O expander. 29 | */ 30 | 31 | class MCP23017 : public GPIOBase { 32 | public: 33 | static void create(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) { 34 | if (checkNoOverlap(vpin, nPins, i2cAddress)) new MCP23017(vpin, nPins, i2cAddress, interruptPin); 35 | } 36 | 37 | private: 38 | // Constructor 39 | MCP23017(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) 40 | : GPIOBase((FSH *)F("MCP23017"), vpin, nPins, i2cAddress, interruptPin) 41 | { 42 | requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer), 43 | outputBuffer, sizeof(outputBuffer)); 44 | outputBuffer[0] = REG_GPIOA; 45 | } 46 | void _writeGpioPort() override { 47 | I2CManager.write(_I2CAddress, 3, REG_GPIOA, _portOutputState, _portOutputState>>8); 48 | } 49 | void _writePullups() override { 50 | // Set pullups only for in-use pins. This prevents pullup being set for a pin that 51 | // is intended for use as an output but hasn't been written to yet. 52 | uint16_t temp = _portPullup & _portInUse; 53 | I2CManager.write(_I2CAddress, 3, REG_GPPUA, temp, temp>>8); 54 | } 55 | void _writePortModes() override { 56 | // Write 0 to IODIR for in-use pins that are outputs, 1 for others. 57 | uint16_t temp = ~(_portMode & _portInUse); 58 | I2CManager.write(_I2CAddress, 3, REG_IODIRA, temp, temp>>8); 59 | // Enable interrupt for in-use pins which are inputs (_portMode=0) 60 | temp = ~_portMode & _portInUse; 61 | I2CManager.write(_I2CAddress, 3, REG_INTCONA, 0x00, 0x00); 62 | I2CManager.write(_I2CAddress, 3, REG_GPINTENA, temp, temp>>8); 63 | } 64 | void _readGpioPort(bool immediate) override { 65 | if (immediate) { 66 | uint8_t buffer[2]; 67 | I2CManager.read(_I2CAddress, buffer, 2, 1, REG_GPIOA); 68 | _portInputState = ((uint16_t)buffer[1]<<8) | buffer[0] | _portMode; 69 | } else { 70 | // Queue new request 71 | requestBlock.wait(); // Wait for preceding operation to complete 72 | // Issue new request to read GPIO register 73 | I2CManager.queueRequest(&requestBlock); 74 | } 75 | } 76 | // This function is invoked when an I/O operation on the requestBlock completes. 77 | void _processCompletion(uint8_t status) override { 78 | if (status == I2C_STATUS_OK) 79 | _portInputState = (((uint16_t)inputBuffer[1]<<8) | inputBuffer[0]) | _portMode; 80 | else 81 | _portInputState = 0xffff; 82 | } 83 | 84 | void _setupDevice() override { 85 | // IOCON is set MIRROR=1, ODR=1 (open drain shared interrupt pin) 86 | I2CManager.write(_I2CAddress, 2, REG_IOCON, 0x44); 87 | _writePortModes(); 88 | _writePullups(); 89 | _writeGpioPort(); 90 | } 91 | 92 | uint8_t inputBuffer[2]; 93 | uint8_t outputBuffer[1]; 94 | 95 | enum { 96 | REG_IODIRA = 0x00, 97 | REG_IODIRB = 0x01, 98 | REG_GPINTENA = 0x04, 99 | REG_GPINTENB = 0x05, 100 | REG_INTCONA = 0x08, 101 | REG_INTCONB = 0x09, 102 | REG_IOCON = 0x0A, 103 | REG_GPPUA = 0x0C, 104 | REG_GPPUB = 0x0D, 105 | REG_GPIOA = 0x12, 106 | REG_GPIOB = 0x13, 107 | }; 108 | 109 | }; 110 | 111 | #endif 112 | -------------------------------------------------------------------------------- /IO_PCA9555.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021, Neil McKechnie. All rights reserved. 3 | * 4 | * This file is part of DCC++EX API 5 | * 6 | * This is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * It is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with CommandStation. If not, see . 18 | */ 19 | 20 | #ifndef io_pca9555_h 21 | #define io_pca9555_h 22 | 23 | #include "IO_GPIOBase.h" 24 | #include "FSH.h" 25 | 26 | ///////////////////////////////////////////////////////////////////////////////////////////////////// 27 | /* 28 | * IODevice subclass for PCA9555 16-bit I/O expander (NXP & Texas Instruments). 29 | */ 30 | 31 | class PCA9555 : public GPIOBase { 32 | public: 33 | static void create(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) { 34 | if (checkNoOverlap(vpin, nPins, i2cAddress)) new PCA9555(vpin,nPins, i2cAddress, interruptPin); 35 | } 36 | 37 | private: 38 | // Constructor 39 | PCA9555(VPIN vpin, uint8_t nPins, I2CAddress I2CAddress, int interruptPin=-1) 40 | : GPIOBase((FSH *)F("PCA9555"), vpin, nPins, I2CAddress, interruptPin) 41 | { 42 | requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer), 43 | outputBuffer, sizeof(outputBuffer)); 44 | outputBuffer[0] = REG_INPUT_P0; 45 | } 46 | void _writeGpioPort() override { 47 | I2CManager.write(_I2CAddress, 3, REG_OUTPUT_P0, _portOutputState, _portOutputState>>8); 48 | } 49 | void _writePullups() override { 50 | // Do nothing, pull-ups are always in place for input ports 51 | // This function is here for HAL GPIOBase API compatibilitiy 52 | 53 | } 54 | void _writePortModes() override { 55 | // Write 0 to REG_CONF_P0 & REG_CONF_P1 for in-use pins that are outputs, 1 for others. 56 | // PCA9555 & TCA9555, Interrupt is always enabled for raising and falling edge 57 | uint16_t temp = ~(_portMode & _portInUse); 58 | I2CManager.write(_I2CAddress, 3, REG_CONF_P0, temp, temp>>8); 59 | } 60 | void _readGpioPort(bool immediate) override { 61 | if (immediate) { 62 | uint8_t buffer[2]; 63 | I2CManager.read(_I2CAddress, buffer, 2, 1, REG_INPUT_P0); 64 | _portInputState = ((uint16_t)buffer[1]<<8) | buffer[0]; 65 | /* PCA9555 Int bug fix, from PCA9555 datasheet: "must change command byte to something besides 00h 66 | * after a Read operation to the PCA9555 device or before reading from 67 | * another device" 68 | * Recommended solution, read from REG_OUTPUT_P0, then do nothing with the received data 69 | * Issue not seen during testing, uncomment if needed 70 | */ 71 | //I2CManager.read(_I2CAddress, buffer, 2, 1, REG_OUTPUT_P0); 72 | } else { 73 | // Queue new request 74 | requestBlock.wait(); // Wait for preceding operation to complete 75 | // Issue new request to read GPIO register 76 | I2CManager.queueRequest(&requestBlock); 77 | } 78 | } 79 | // This function is invoked when an I/O operation on the requestBlock completes. 80 | void _processCompletion(uint8_t status) override { 81 | if (status == I2C_STATUS_OK) 82 | _portInputState = ((uint16_t)inputBuffer[1]<<8) | inputBuffer[0]; 83 | else 84 | _portInputState = 0xffff; 85 | } 86 | 87 | void _setupDevice() override { 88 | // HAL API calls 89 | _writePortModes(); 90 | _writePullups(); 91 | _writeGpioPort(); 92 | } 93 | 94 | uint8_t inputBuffer[2]; 95 | uint8_t outputBuffer[1]; 96 | 97 | 98 | enum { 99 | REG_INPUT_P0 = 0x00, 100 | REG_INPUT_P1 = 0x01, 101 | REG_OUTPUT_P0 = 0x02, 102 | REG_OUTPUT_P1 = 0x03, 103 | REG_POL_INV_P0 = 0x04, 104 | REG_POL_INV_P1 = 0x05, 105 | REG_CONF_P0 = 0x06, 106 | REG_CONF_P1 = 0x07, 107 | }; 108 | 109 | }; 110 | 111 | #endif 112 | -------------------------------------------------------------------------------- /Release_Notes/NeoPixel.md: -------------------------------------------------------------------------------- 1 | NeoPixel support 2 | 3 | The IO_NeoPixel.h driver supports the adafruit neopixel seesaw board. It turns each pixel into an individual VPIN which can be given a colour and turned on or off using the new command or the NEOPIXEL Exrail macro. Exrail SIGNALS can also drive a single pixel signal or multiple separate pixels. 4 | 5 | 6 | 1. Defining the hardware driver: 7 | Add a driver definition in myAutomation.h for each adafruit I2C driver. 8 | 9 | HAL(neoPixel, firstVpin, numberOfPixels [, mode [, i2caddress]) 10 | Where mode is selected from the various pixel string types which have varying 11 | colour order or refresh frequency. For MOST strings this mode will be NEO_GRB but for others refer to the comments in IO_NeoPixel.h 12 | If omitted the node and i2caddress default to NEO_GRB, 0x60. 13 | 14 | HAL(NeoPixel,1000,20) 15 | This is a NeoPixel driver defaulting to I2C aqddress 0x60 for a GRB pixel string. Pixels are given vpin numbers from 1000 to 1019. 16 | HAL(NeoPixel,1020,20,NEO_GRB,0x61) 17 | This is a NeoPixel driver on i2c address 0x61 18 | 19 | 2. Setting pixels from the < > commands. 20 | By default, each pixel in the string is created as white but switched off. 21 | Each pixel has a vpin starting from the first vpin in the HAL definitions. 22 | 23 | switches pixel on (same as ) e.g. 24 | switches pixel off (same as ) e.g. 25 | (the z commands work on pixels the same as other gpio pins.) 26 | 27 | switches on/off count pixels starting at vpin. e.g 28 | Note: it IS acceptable to switch across 2 strings of pixels if they are contiguous vpin ranges. It is also interesting that this command doesnt care if the vpins are NeoPixel or any other type, so it can be used to switch a range of other pin types. 29 | 30 | sets the colour and on/off status of a pin or pins. Each colour is 0..255 e.g. sets pin 1005 to bright yellow and ON, <0 -1006 0 0 255 10> sets pins 1006 to 1015 (10 pins) to bright blue but OFF. 31 | Note: If you set a pin to a colour, you can turn it on and off without having to reset the colour every time. This is something the adafruit seesaw library can't do and is just one of several reasons why we dont use it. 32 | 33 | 3. Setting pixels from EXRAIL 34 | The new NEOPIXEL macro provides the same functionality as the command above. 35 | NEOPIXEL([-]vpin, red, green, blue [,count]) 36 | 37 | Setting pixels on or off (without colour change) can be done with SET/RESET [currently there is no set range facility but that may be added as a general exrail thing... watch this space] 38 | 39 | Because the pixels obey set/reset, the BLINK command can also be used to control blinking a pixel. 40 | 41 | 4. EXRAIL pixel signals. 42 | There are two types possible, a mast with separate fixed colour pixels for each aspect, or a mast with one multiple colour pixel for all aspects. 43 | 44 | For separate pixels, the colours should be established at startup and a normal SIGNALH macro used. 45 | 46 | AUTOSTART 47 | SIGNALH(1010,1011,1012) 48 | NEOPIXEL(1010,255,0,0) 49 | NEOPIXEL(1011,128,128,0) 50 | NEOPIXEL(1012,0,255,0) 51 | RED(1010) // force signal state otherwise all 3 lights will be on 52 | DONE 53 | 54 | For signals with 1 pixel, the NEOPIXEL_SIGNAL macro will create a signal 55 | NEOPIXEL_SIGNAL(vpin,redfx,amberfx,greenfx) 56 | 57 | ** Changed... **** 58 | The fx values above can be created by the NeoRGB macro so a bright red would be NeoRGB(255,0,0) bright green NeoRGB(0,255,0) and amber something like NeoRGB(255,100,0) 59 | NeoRGB creates a single int32_t value so it can be used in several ways as convenient. 60 | 61 | // create 1-lamp signal with NeoRGB colours 62 | NEOPIXEL_SIGNAL(1000,NeoRGB(255,0,0),NeoRGB(255,100,0),NeoRGB(0,255,0)) 63 | 64 | // Create 1-lamp signal with named colours. 65 | // This is better if you have multiple signals. 66 | // (Note: ALIAS is not suitable due to word length defaults) 67 | #define REDLAMP NeoRGB(255,0,0) 68 | #define AMBERLAMP NeoRGB(255,100,0) 69 | #define GREENLAMP NeoRGB(0,255,0) 70 | NEOPIXEL_SIGNAL(1001,REDLAMP,AMBERLAMP,GREENLAMP) 71 | 72 | // Create 1-lamp signal with web type RGB colours 73 | // (Using blue for the amber signal , just testing) 74 | NEOPIXEL_SIGNAL(1002,0xFF0000,0x0000FF,0x00FF00) 75 | 76 | 77 | -------------------------------------------------------------------------------- /IO_PCF8575.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2023, Paul Antoine, and Discord user @ADUBOURG 3 | * © 2021, Neil McKechnie. All rights reserved. 4 | * 5 | * This file is part of DCC++EX API 6 | * 7 | * This is free software: you can redistribute it and/or modify 8 | * it under the terms of the GNU General Public License as published by 9 | * the Free Software Foundation, either version 3 of the License, or 10 | * (at your option) any later version. 11 | * 12 | * It is distributed in the hope that it will be useful, 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | * GNU General Public License for more details. 16 | * 17 | * You should have received a copy of the GNU General Public License 18 | * along with CommandStation. If not, see . 19 | */ 20 | 21 | /* 22 | * The PCF8575 is a simple device; it only has one register. The device 23 | * input/output mode and pullup are configured through this, and the 24 | * output state is written and the input state read through it too. 25 | * 26 | * This is accomplished by having a weak resistor in series with the output, 27 | * and a read-back of the other end of the resistor. As an output, the 28 | * pin state is set to 1 or 0, and the output voltage goes to +5V or 0V 29 | * (through the weak resistor). 30 | * 31 | * In order to use the pin as an input, the output is written as 32 | * a '1' in order to pull up the resistor. Therefore the input will be 33 | * 1 unless the pin is pulled down externally, in which case it will be 0. 34 | * 35 | * As a consequence of this approach, it is not possible to use the device for 36 | * inputs without pullups. 37 | */ 38 | 39 | #ifndef IO_PCF8575_H 40 | #define IO_PCF8575_H 41 | 42 | #include "IO_GPIOBase.h" 43 | #include "FSH.h" 44 | 45 | class PCF8575 : public GPIOBase { 46 | public: 47 | static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) { 48 | if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCF8575(firstVpin, nPins, i2cAddress, interruptPin); 49 | } 50 | 51 | private: 52 | PCF8575(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) 53 | : GPIOBase((FSH *)F("PCF8575"), firstVpin, nPins, i2cAddress, interruptPin) 54 | { 55 | requestBlock.setReadParams(_I2CAddress, inputBuffer, sizeof(inputBuffer)); 56 | } 57 | 58 | // The PCF8575 handles inputs by applying a weak pull-up when output is driven to '1'. 59 | // The pin state is driven '1' if the pin is an input, or if it is an output set to 1. 60 | // Unused pins are driven '0'. 61 | void _writeGpioPort() override { 62 | uint16_t bits = (_portOutputState | ~_portMode) & _portInUse; 63 | I2CManager.write(_I2CAddress, 2, bits, bits>>8); 64 | } 65 | 66 | // The PCF8575 handles inputs by applying a weak pull-up when output is driven to '1'. 67 | // Therefore, writing '1' in _writePortModes is enough to set the module to input mode 68 | // and enable pull-up. 69 | void _writePullups() override { } 70 | 71 | // The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise. 72 | void _writePortModes() override { 73 | _writeGpioPort(); 74 | } 75 | 76 | // In immediate mode, _readGpioPort reads the device GPIO port and updates _portInputState accordingly. 77 | // When not in immediate mode, it initiates a request using the request block and returns. 78 | // When the request completes, _processCompletion finishes the operation. 79 | void _readGpioPort(bool immediate) override { 80 | if (immediate) { 81 | uint8_t buffer[2]; 82 | I2CManager.read(_I2CAddress, buffer, 2); 83 | _portInputState = (((uint16_t)buffer[1]<<8) | buffer[0]) | _portMode; 84 | } else { 85 | requestBlock.wait(); // Wait for preceding operation to complete 86 | // Issue new request to read GPIO register 87 | I2CManager.queueRequest(&requestBlock); 88 | } 89 | } 90 | 91 | // This function is invoked when an I/O operation on the requestBlock completes. 92 | void _processCompletion(uint8_t status) override { 93 | if (status == I2C_STATUS_OK) 94 | _portInputState = (((uint16_t)inputBuffer[1]<<8) | inputBuffer[0]) | _portMode; 95 | else 96 | _portInputState = 0xffff; 97 | } 98 | 99 | // Set up device ports 100 | void _setupDevice() override { 101 | _writePortModes(); 102 | _writeGpioPort(); 103 | _writePullups(); 104 | } 105 | 106 | uint8_t inputBuffer[2]; 107 | }; 108 | 109 | #endif 110 | -------------------------------------------------------------------------------- /myEX-Turntable.example.h: -------------------------------------------------------------------------------- 1 | /************************************************************************************************** 2 | * This is an example automation file to control EX-Turntable using recommended techniques. 3 | ************************************************************************************************** 4 | * INSTRUCTIONS 5 | ************************************************************************************************** 6 | * To use this example file as the starting point for your layout, there are two options: 7 | * 8 | * 1. If you don't have an existing "myAutomation.h" file, simply rename "myEX-Turntable.example.h" to 9 | * "myAutomation.h". 10 | * 2. If you have an existing "myAutomation.h" file, rename "myEX-Turntable.example.h" to "myEX-Turntable.h", 11 | * and then include it by adding the line below at the end of your existing "myAutomation.h", on a 12 | * line of its own: 13 | * 14 | * #include "myEX-Turntable.h" 15 | * 16 | * Note that there are further instructions in the documentation at https://dcc-ex.com/. 17 | *************************************************************************************************/ 18 | 19 | /************************************************************************************************** 20 | * The MOVETT() command below will automatically move your turntable to the defined step position on 21 | * start up. 22 | * 23 | * If you do not wish this to occur, simply comment the line out. 24 | * 25 | * NOTE: If you are including this file at the end of an existing "myAutomation.h" file, you will likely 26 | * need to move this line to the beginning of your existing "myAutomation.h" file in order for it to 27 | * be effective. 28 | *************************************************************************************************/ 29 | MOVETT(600, 114, Turn) 30 | DONE 31 | 32 | // For Conductor level users who wish to just use EX-Turntable, you don't need to understand this 33 | // and can move to defining the turntable positions below. You must, however, ensure this remains 34 | // before any position definitions or you will get compile errors when uploading. 35 | // 36 | // Definition of the EX_TURNTABLE macro to correctly create the ROUTEs required for each position. 37 | // This includes RESERVE()/FREE() to protect any automation activities. 38 | // 39 | #define EX_TURNTABLE(route_id, reserve_id, vpin, steps, activity, desc) \ 40 | ROUTE(route_id, desc) \ 41 | RESERVE(reserve_id) \ 42 | MOVETT(vpin, steps, activity) \ 43 | WAITFOR(vpin) \ 44 | FREE(reserve_id) \ 45 | DONE 46 | 47 | /************************************************************************************************** 48 | * TURNTABLE POSITION DEFINITIONS 49 | *************************************************************************************************/ 50 | // EX_TURNTABLE(route_id, reserve_id, vpin, steps, activity, desc) 51 | // 52 | // route_id = A unique number for each defined route, the route is what appears in throttles 53 | // reserve_id = A unique reservation number (0 - 255) to ensure nothing interferes with automation 54 | // vpin = The Vpin defined for the Turntable-EX device driver, default is 600 55 | // steps = The target step position 56 | // activity = The activity performed for this ROUTE (Note do not enclose in quotes "") 57 | // desc = Description that will appear in throttles (Must use quotes "") 58 | // 59 | EX_TURNTABLE(TTRoute1, Turntable, 600, 114, Turn, "Position 1") 60 | EX_TURNTABLE(TTRoute2, Turntable, 600, 227, Turn, "Position 2") 61 | EX_TURNTABLE(TTRoute3, Turntable, 600, 341, Turn, "Position 3") 62 | EX_TURNTABLE(TTRoute4, Turntable, 600, 2159, Turn, "Position 4") 63 | EX_TURNTABLE(TTRoute5, Turntable, 600, 2273, Turn, "Position 5") 64 | EX_TURNTABLE(TTRoute6, Turntable, 600, 2386, Turn, "Position 6") 65 | EX_TURNTABLE(TTRoute7, Turntable, 600, 0, Home, "Home turntable") 66 | 67 | // Pre-defined aliases to ensure unique IDs are used. 68 | // Turntable reserve ID, valid is 0 - 255 69 | ALIAS(Turntable, 255) 70 | 71 | // Turntable ROUTE ID reservations, using for uniqueness: 72 | ALIAS(TTRoute1) 73 | ALIAS(TTRoute2) 74 | ALIAS(TTRoute3) 75 | ALIAS(TTRoute4) 76 | ALIAS(TTRoute5) 77 | ALIAS(TTRoute6) 78 | ALIAS(TTRoute7) 79 | ALIAS(TTRoute8) 80 | ALIAS(TTRoute9) 81 | ALIAS(TTRoute10) 82 | ALIAS(TTRoute11) 83 | ALIAS(TTRoute12) 84 | ALIAS(TTRoute13) 85 | ALIAS(TTRoute14) 86 | ALIAS(TTRoute15) 87 | ALIAS(TTRoute16) 88 | ALIAS(TTRoute17) 89 | ALIAS(TTRoute18) 90 | ALIAS(TTRoute19) 91 | ALIAS(TTRoute20) 92 | ALIAS(TTRoute21) 93 | ALIAS(TTRoute22) 94 | ALIAS(TTRoute23) 95 | ALIAS(TTRoute24) 96 | ALIAS(TTRoute25) 97 | ALIAS(TTRoute26) 98 | ALIAS(TTRoute27) 99 | ALIAS(TTRoute28) 100 | ALIAS(TTRoute29) 101 | ALIAS(TTRoute30) 102 | -------------------------------------------------------------------------------- /installer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 4 | # © 2022,2023 Harald Barth 5 | # 6 | # This file is part of CommandStation-EX 7 | # 8 | # This is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # It is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU General Public License 19 | # along with CommandStation. If not, see . 20 | # 21 | # 22 | # Usage: mkdir DIRNAME ; cd DIRNAME ; ../installer.sh 23 | # or from install directory ./installer.sh 24 | # 25 | 26 | DCCEXGITURL="https://github.com/DCC-EX/CommandStation-EX" 27 | ACLIINSTALL="https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh" 28 | ACLI="./bin/arduino-cli" 29 | 30 | function need () { 31 | type -p $1 > /dev/null && return 32 | dpkg -l $1 2>&1 | egrep ^ii >/dev/null && return 33 | sudo apt-get install $1 34 | type -p $1 > /dev/null && return 35 | echo "Could not install $1, abort" 36 | exit 255 37 | } 38 | 39 | need git 40 | 41 | if cat /etc/issue | egrep '^Raspbian' 2>&1 >/dev/null ; then 42 | # we are on a raspi where we do not support graphical 43 | unset DISPLAY 44 | fi 45 | 46 | if [ x$DISPLAY != x ] ; then 47 | # we have DISPLAY, do the graphic thing 48 | need python3-tk 49 | need python3.8-venv 50 | mkdir -p ~/ex-installer/venv 51 | python3 -m venv ~/ex-installer/venv 52 | cd ~/ex-installer/venv || exit 255 53 | source ./bin/activate 54 | git clone https://github.com/DCC-EX/EX-Installer 55 | cd EX-Installer || exit 255 56 | pip3 install -r requirements.txt 57 | exec python3 -m ex_installer 58 | fi 59 | if test -d `basename "$DCCEXGITURL"` ; then 60 | : assume we are almost there 61 | cd `basename "$DCCEXGITURL"` || exit 255 62 | fi 63 | if test -d .git ; then 64 | : assume we are right here 65 | git pull 66 | else 67 | git clone "$DCCEXGITURL" 68 | cd `basename "$DCCEXGITURL"` || exit 255 69 | fi 70 | 71 | # prepare versions 72 | VERSIONS=/tmp/versions.$$ 73 | git tag --sort=v:refname | grep Prod | tail -1 > $VERSIONS 74 | echo master >> $VERSIONS 75 | git tag --sort=v:refname | grep Devel | tail -1 >> $VERSIONS 76 | echo devel >> $VERSIONS 77 | 78 | # ask user what version to use 79 | echo "What version to use? (give line number) If in doubt, use 1" 80 | cat -n $VERSIONS 81 | echo -n "> " 82 | LINE=`awk 'BEGIN {getline A < "/dev/tty"} ; A == NR {print}' $VERSIONS` 83 | git checkout $LINE 84 | 85 | if test -f config.h ; then 86 | : all well 87 | else 88 | # need to do this config better 89 | cp -p config.example.h config.h 90 | fi 91 | if test -x "$ACLI" ; then 92 | : all well 93 | else 94 | need curl 95 | curl "$ACLIINSTALL" > acliinstall.sh 96 | chmod +x acliinstall.sh 97 | ./acliinstall.sh 98 | fi 99 | 100 | $ACLI core update-index || exit 255 101 | 102 | # Board discovery 103 | BOARDS=/tmp/boards.$$ 104 | $ACLI board list > /dev/null # download missing components 105 | $ACLI board list | grep serial > $BOARDS # real run 106 | if test -s $BOARDS ; then 107 | : all well 108 | else 109 | echo "$ACLI: No boards found" 110 | exit 255 111 | fi 112 | if test x`< $BOARDS wc -l` = 'x1' ; then 113 | LINE=`cat $BOARDS` 114 | else 115 | # ask user 116 | echo "What board to use? (give line number)" 117 | cat -n $BOARDS 118 | echo -n "> " 119 | LINE=`awk 'BEGIN {getline A < "/dev/tty"} ; A == NR {print}' $BOARDS` 120 | fi 121 | rm $BOARDS 122 | PORT=`echo $LINE | cut -d" " -f1` 123 | echo Will use port: $PORT 124 | 125 | # FQBN discovery 126 | FQBN=`echo $LINE | egrep 'arduino:avr:[a-z][a-z]*' | sed 's/.*\(arduino:avr:[a-z][a-z]*\) .*/\1/1'` 127 | if test x$FQBN = x ; then 128 | # ask user 129 | cat > /tmp/fqbn.$$ < " 137 | FQBN=`awk 'BEGIN {getline A < "/dev/tty"} ; A == NR {print}' /tmp/fqbn.$$` 138 | fi 139 | rm /tmp/fqbn.$$ 140 | echo FQBN is $FQBN 141 | 142 | # Install phase 143 | $ACLI core install `echo $FQBN | sed 's,:[^:]*$,,1'` # remove last component to get package 144 | $ACLI board attach -p $PORT --fqbn $FQBN "$PWD" 145 | $ACLI compile --fqbn $FQBN "$PWD" 146 | $ACLI upload -v -t -p $PORT "$PWD" 147 | -------------------------------------------------------------------------------- /DCCWaveform.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021 M Steve Todd 3 | * © 2021 Mike S 4 | * © 2021 Fred Decker 5 | * © 2020-2024 Harald Barth 6 | * © 2020-2021 Chris Harlow 7 | * All rights reserved. 8 | * 9 | * This file is part of CommandStation-EX 10 | * 11 | * This is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, either version 3 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * It is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with CommandStation. If not, see . 23 | */ 24 | #ifndef DCCWaveform_h 25 | #define DCCWaveform_h 26 | 27 | #include "MotorDriver.h" 28 | #ifdef ARDUINO_ARCH_ESP32 29 | #include "DCCRMT.h" 30 | #include "TrackManager.h" 31 | #endif 32 | 33 | 34 | 35 | // Number of preamble bits. 36 | const byte PREAMBLE_BITS_MAIN = 16; 37 | const byte PREAMBLE_BITS_PROG = 22; 38 | const byte MAX_PACKET_SIZE = 5; // NMRA standard extended packets, payload size WITHOUT checksum. 39 | 40 | 41 | // The WAVE_STATE enum is deliberately numbered because a change of order would be catastrophic 42 | // to the transform array. 43 | enum WAVE_STATE : byte { 44 | WAVE_START=0, // wave going high at start of bit 45 | WAVE_MID_1=1, // middle of 1 bit 46 | WAVE_HIGH_0=2, // first part of 0 bit high 47 | WAVE_MID_0=3, // middle of 0 bit 48 | WAVE_LOW_0=4, // first part of 0 bit low 49 | WAVE_PENDING=5 // next bit not yet known 50 | }; 51 | 52 | // NOTE: static functions are used for the overall controller, then 53 | // one instance is created for each track. 54 | 55 | class DCCWaveform { 56 | public: 57 | DCCWaveform( byte preambleBits, bool isMain); 58 | static void begin(); 59 | static void loop(); 60 | static DCCWaveform mainTrack; 61 | static DCCWaveform progTrack; 62 | inline void clearRepeats() { transmitRepeats=0; } 63 | #ifndef ARDUINO_ARCH_ESP32 64 | inline void clearResets() { sentResetsSincePacket=0; } 65 | inline byte getResets() { return sentResetsSincePacket; } 66 | #else 67 | // extrafudge is added when we know that the resets will first come extrafudge packets in the future 68 | inline void clearResets(byte extrafudge=0) { 69 | if ((isMainTrack ? rmtMainChannel : rmtProgChannel) == NULL) return; 70 | resetPacketBase = isMainTrack ? rmtMainChannel->packetCount() : rmtProgChannel->packetCount(); 71 | resetPacketBase += extrafudge; 72 | }; 73 | inline byte getResets() { 74 | if ((isMainTrack ? rmtMainChannel : rmtProgChannel) == NULL) return 0; 75 | uint32_t packetcount = isMainTrack ? 76 | rmtMainChannel->packetCount() : rmtProgChannel->packetCount(); 77 | uint32_t count = packetcount - resetPacketBase; // Beware of unsigned interger arithmetic. 78 | if (count > UINT32_MAX/2) // we are in the extrafudge area 79 | return 0; 80 | if (count > 255) // cap to 255 81 | return 255; 82 | return count; // all special cases handled above 83 | }; 84 | #endif 85 | void schedulePacket(const byte buffer[], byte byteCount, byte repeats); 86 | bool isReminderWindowOpen(); 87 | void promotePendingPacket(); 88 | static bool setRailcom(bool on, bool debug); 89 | static bool isRailcom() {return railcomActive;} 90 | 91 | private: 92 | #ifndef ARDUINO_ARCH_ESP32 93 | volatile bool packetPending; 94 | volatile bool reminderWindowOpen; 95 | volatile byte sentResetsSincePacket; 96 | #else 97 | volatile uint32_t resetPacketBase; 98 | #endif 99 | static void interruptHandler(); 100 | void interrupt2(); 101 | 102 | bool isMainTrack; 103 | // Transmission controller 104 | byte transmitPacket[MAX_PACKET_SIZE+1]; // +1 for checksum 105 | byte transmitLength; 106 | byte transmitRepeats; // remaining repeats of transmission 107 | byte remainingPreambles; 108 | byte requiredPreambles; 109 | byte bits_sent; // 0-8 (yes 9 bits) sent for current byte 110 | byte bytes_sent; // number of bytes sent from transmitPacket 111 | WAVE_STATE state; // wave generator state machine 112 | byte pendingPacket[MAX_PACKET_SIZE+1]; // +1 for checksum 113 | byte pendingLength; 114 | byte pendingRepeats; 115 | static volatile bool railcomActive; // switched on by user 116 | static volatile bool railcomDebug; // switched on by user 117 | 118 | #ifdef ARDUINO_ARCH_ESP32 119 | static RMTChannel *rmtMainChannel; 120 | static RMTChannel *rmtProgChannel; 121 | #endif 122 | }; 123 | #endif 124 | -------------------------------------------------------------------------------- /LocoTable.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2023 Harald Barth 2 | * 3 | * This source is free software: you can redistribute it and/or modify 4 | * it under the terms of the GNU General Public License as published by 5 | * the Free Software Foundation, either version 3 of the License, or 6 | * (at your option) any later version. 7 | * 8 | * This source is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this software. If not, see 15 | * . 16 | */ 17 | #include "LocoTable.h" 18 | 19 | LocoTable::LOCO LocoTable::speedTable[MAX_LOCOS] = { {0,0,0,0,0,0} }; 20 | int LocoTable::highestUsedReg = 0; 21 | 22 | int LocoTable::lookupSpeedTable(int locoId, bool autoCreate) { 23 | // determine speed reg for this loco 24 | const int UNUSED = -1; 25 | int firstEmpty = UNUSED; 26 | int reg; 27 | for (reg = 0; reg < MAX_LOCOS; reg++) { 28 | if (speedTable[reg].loco == locoId) break; 29 | if (speedTable[reg].loco == 0 && firstEmpty == UNUSED) firstEmpty = reg; 30 | } 31 | 32 | if (reg == MAX_LOCOS && !autoCreate) // not found and not auto creating 33 | return -1; 34 | if (reg == MAX_LOCOS) { 35 | if (firstEmpty == UNUSED) { // did not find empty slot 36 | DIAG(F("Can not add id %d to full sniffer table (total > %d)"), locoId, MAX_LOCOS); 37 | return -1; 38 | } else { // populate empty slot 39 | reg = firstEmpty; 40 | //DIAG(F("LocoTable SNIFFER: Create loco %d in slot %d"), locoId, reg); 41 | speedTable[reg].loco = locoId; 42 | speedTable[reg].speedCode=128; // default direction forward 43 | speedTable[reg].groupFlags=0; 44 | speedTable[reg].functions=0; 45 | } 46 | } 47 | if (reg > highestUsedReg) highestUsedReg = reg; 48 | return reg; 49 | } 50 | 51 | // returns false only if loco existed but nothing was changed 52 | bool LocoTable::updateLoco(int loco, byte speedCode) { 53 | if (loco==0) { 54 | /* 55 | // broadcast stop/estop but dont change direction 56 | for (int reg = 0; reg < highestUsedReg; reg++) { 57 | if (speedTable[reg].loco==0) continue; 58 | byte newspeed=(speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f); 59 | if (speedTable[reg].speedCode != newspeed) { 60 | speedTable[reg].speedCode = newspeed; 61 | CommandDistributor::broadcastLoco(reg); 62 | } 63 | } 64 | */ 65 | return true; 66 | } 67 | 68 | // determine speed reg for this loco 69 | int reg=lookupSpeedTable(loco, false); 70 | 71 | if (reg>=0) { 72 | speedTable[reg].speedcounter++; 73 | if (speedTable[reg].speedCode!=speedCode) { 74 | speedTable[reg].speedCode = speedCode; 75 | return true; 76 | } else { 77 | return false; 78 | } 79 | } else { 80 | // need to make new entry 81 | reg=lookupSpeedTable(loco, true); 82 | if(reg >=0) { 83 | speedTable[reg].speedCode = speedCode; 84 | return true; 85 | } else { // as no loco was added, nothing changed 86 | return false; 87 | } 88 | } 89 | } 90 | 91 | bool LocoTable::updateFunc(int loco, byte func, int shift) { 92 | unsigned long previous; 93 | unsigned long newfunc; 94 | bool retval = false; // nothing was touched 95 | int reg = lookupSpeedTable(loco, false); 96 | if (reg < 0) { // not found 97 | retval = true; 98 | reg = lookupSpeedTable(loco, true); 99 | if (reg < 0) // could not create new entry, nothing changed 100 | return false; 101 | newfunc = previous = 0; 102 | } else { 103 | newfunc = previous = speedTable[reg].functions; 104 | } 105 | 106 | speedTable[reg].funccounter++; 107 | 108 | if(shift == 1) { // special case for light 109 | newfunc &= ~1UL; 110 | newfunc |= ((func & 0B10000) >> 4); 111 | } 112 | newfunc &= ~(0B1111UL << shift); 113 | newfunc |= ((func & 0B1111) << shift); 114 | 115 | if (newfunc != previous) { 116 | speedTable[reg].functions = newfunc; 117 | retval = true; 118 | } 119 | return retval; 120 | } 121 | 122 | void LocoTable::dumpTable(Stream *output) { 123 | output->print("\n-----------Table---------\n"); 124 | for (byte reg = 0; reg <= highestUsedReg; reg++) { 125 | if (speedTable[reg].loco != 0) { 126 | output->print(speedTable[reg].loco); 127 | output->print(' '); 128 | output->print(speedTable[reg].speedCode); 129 | output->print(' '); 130 | output->print(speedTable[reg].functions); 131 | output->print(" #funcpacks:"); 132 | output->print(speedTable[reg].funccounter); 133 | output->print(" #speedpacks:"); 134 | output->print(speedTable[reg].speedcounter); 135 | speedTable[reg].funccounter = 0; 136 | speedTable[reg].speedcounter = 0; 137 | output->print('\n'); 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /Release_Notes/release_notes_v3.0.0.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | The DCC-EX Team is pleased to release CommandStation-EX-v3.0.0 as a Production Release. This release is a major re-write of earlier versions. We've re-architected the code-base so that it can better handle new features going forward. Download the compressed files here: 4 | 5 | **Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.** 6 | 7 | [CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/files/5611333/CommandStation-EX.zip) 8 | [CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/files/5611335/CommandStation-EX.tar.gz) 9 | 10 | 11 | **Known Bugs:** 12 | - **Consisting through JMRI** - currently does not work in this release. A number of testers were able to develop a work around. If interested enter a Support Ticket. 13 | - **Wi-Fi** - works, but can be challenging to use if you want to switch between AP mode and STA station mode. 14 | - **Pololu Motor Shield** - is supported with this release, but the user may have to play around with some timings to enable programming mode due to limitation in its current sensing circuitry 15 | 16 | **Summary of the key new features added to CommandStation-EX V3.0.0:** 17 | - **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients. 18 | - **Withrottle Integrations** - Act as a host for four WiThrottle clients concurrently. 19 | - **Add LCD/OLED support** - OLED supported on Mega only 20 | - **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second. 21 | - **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers 22 | - **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts. 23 | - **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs 24 | - **New, simpler function command** - `````` command allows setting functions based on their number, not based on a code as in `````` 25 | - **Function reminders** - Function reminders are sent in addition to speed reminders 26 | - **Functions to F28** - All NMRA functions are now supported 27 | - **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them 28 | - **Diagnostic `````` commands** - See documentation for a full list of new diagnostic commands 29 | - **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM 30 | - **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers 31 | - **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code 32 | - **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM 33 | - **Fix EEPROM bugs** 34 | - **Number of locos discovery command** - ```<#>``` command 35 | - **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega. 36 | - **Automatic slot managment** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. `````` command added to release locos from memory. 37 | 38 | 39 | **Key Contributors** 40 | 41 | **Project Lead** 42 | - Fred Decker - Holly Springs, North Carolina, USA (FlightRisk) 43 | 44 | **CommandStation-EX Developers** 45 | - Chris Harlow - Bournemouth, UK (UKBloke) 46 | - Harald Barth - Stockholm, Sweden (Haba) 47 | - Fred Decker - Holly Springs, North Carolina, USA (FlightRisk) 48 | - Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting) 49 | - M Steve Todd - - Engine Driver and JMRI Interface 50 | - Scott Catalanno - Pennsylvania 51 | - Gregor Baues - Île-de-France, France (grbba) 52 | 53 | **exInstaller Software** 54 | - Anthony W - Dayton, Ohio, USA (Dex, Dex++) 55 | 56 | **Website and Documentation** 57 | - Mani Kumar - Bangalor, India (Mani / Mani Kumar) 58 | - Fred Decker - Holly Springs, North Carolina, USA (FlightRisk) 59 | - Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting) 60 | - Roger Beschizza - Dorset, UK (Roger Beschizza) 61 | - Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter) 62 | -Kevin Smith - (KCSmith) 63 | 64 | **Beta Testing / Release Management / Support** 65 | - Larry Dribin - Release Management 66 | - Keith Ledbetter 67 | - BradVan der Elst 68 | - Andrew Pye 69 | - Mike Bowers 70 | - Randy McKenzie 71 | - Roberto Bravin 72 | - Sim Brigden 73 | - Alan Lautenslager 74 | - Martin Bafver 75 | - Mário André Silva 76 | - Anthony Kochevar 77 | - Gajanatha Kobbekaduwe 78 | - Sumner Patterson 79 | - Paul - Virginia, USA 80 | 81 | **Downloads (zip and tar.gz) below. These are named without version number in the folder name to make the Arduino IDE happy.** 82 | 83 | [CommandStation-EX.zip](https://github.com/DCC-EX/CommandStation-EX/files/5611333/CommandStation-EX.zip) 84 | [CommandStation-EX.tar.gz](https://github.com/DCC-EX/CommandStation-EX/files/5611335/CommandStation-EX.tar.gz) 85 | 86 | -------------------------------------------------------------------------------- /SerialManager.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2022 Paul M. Antoine 3 | * © 2021 Chris Harlow 4 | * © 2022 2024 Harald Barth 5 | * All rights reserved. 6 | * 7 | * This file is part of DCC++EX 8 | * 9 | * This is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * It is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with CommandStation. If not, see . 21 | */ 22 | 23 | #include "SerialManager.h" 24 | #include "DCCEXParser.h" 25 | #include "StringFormatter.h" 26 | #include "DIAG.h" 27 | 28 | #ifdef ARDUINO_ARCH_ESP32 29 | #ifdef SERIAL_BT_COMMANDS 30 | #include 31 | //#include 32 | #if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED) 33 | #error No Bluetooth library available 34 | #endif //ENABLED 35 | BluetoothSerial SerialBT; 36 | //BleSerial SerialBT; 37 | #endif //COMMANDS 38 | #endif //ESP32 39 | 40 | static const byte PAYLOAD_FALSE = 0; 41 | static const byte PAYLOAD_NORMAL = 1; 42 | static const byte PAYLOAD_STRING = 2; 43 | 44 | SerialManager * SerialManager::first=NULL; 45 | 46 | SerialManager::SerialManager(Stream * myserial) { 47 | serial=myserial; 48 | next=first; 49 | first=this; 50 | bufferLength=0; 51 | inCommandPayload=PAYLOAD_FALSE; 52 | } 53 | 54 | void SerialManager::init() { 55 | USB_SERIAL.begin(115200); 56 | while (!USB_SERIAL && millis() < 5000); // wait max 5s for Serial to start 57 | new SerialManager(&USB_SERIAL); 58 | 59 | #ifdef SERIAL6_COMMANDS 60 | Serial6.begin(115200); 61 | new SerialManager(&Serial6); 62 | #endif 63 | #ifdef SERIAL5_COMMANDS 64 | Serial5.begin(115200); 65 | new SerialManager(&Serial5); 66 | #endif 67 | #ifdef SERIAL4_COMMANDS 68 | Serial4.begin(115200); 69 | new SerialManager(&Serial4); 70 | #endif 71 | #ifdef SERIAL3_COMMANDS 72 | Serial3.begin(115200); 73 | new SerialManager(&Serial3); 74 | #endif 75 | #ifdef SERIAL2_COMMANDS 76 | #ifdef ARDUINO_ARCH_ESP32 77 | Serial2.begin(115200, SERIAL_8N1, 16, 17); // GPIO 16 RXD2; GPIO 17 TXD2 on ESP32 78 | #else // not ESP32 79 | Serial2.begin(115200); 80 | #endif // ESP32 81 | new SerialManager(&Serial2); 82 | #endif 83 | #ifdef SERIAL1_COMMANDS 84 | Serial1.begin(115200); 85 | new SerialManager(&Serial1); 86 | #endif 87 | #ifdef SERIAL_BT_COMMANDS 88 | { 89 | //SerialBT.setPin("6666"); // choose other pin 90 | uint64_t chipid = ESP.getEfuseMac(); 91 | char idstr[16] = {0}; 92 | snprintf(idstr, 15, "DCCEX-%08X", 93 | __builtin_bswap32((uint32_t)(chipid>>16))); 94 | SerialBT.begin(idstr); 95 | new SerialManager(&SerialBT); 96 | delay(1000); 97 | } 98 | #endif 99 | #ifdef SABERTOOTH 100 | #ifdef ARDUINO_ARCH_ESP32 101 | Serial2.begin(9600, SERIAL_8N1, 16, 17); // GPIO 16 RXD2; GPIO 17 TXD2 on ESP32 102 | #else 103 | Serial2.begin(9600); 104 | #endif 105 | #endif 106 | } 107 | 108 | void SerialManager::broadcast(char * stringBuffer) { 109 | for (SerialManager * s=first;s;s=s->next) s->broadcast2(stringBuffer); 110 | } 111 | void SerialManager::broadcast2(char * stringBuffer) { 112 | serial->print(stringBuffer); 113 | } 114 | 115 | void SerialManager::loop() { 116 | for (SerialManager * s=first;s;s=s->next) s->loop2(); 117 | } 118 | 119 | void SerialManager::loop2() { 120 | while (serial->available()) { 121 | char ch = serial->read(); 122 | if (!inCommandPayload) { 123 | if (ch == '<') { 124 | inCommandPayload = PAYLOAD_NORMAL; 125 | bufferLength = 0; 126 | buffer[0] = '\0'; 127 | } 128 | } else { // if (inCommandPayload) 129 | if (bufferLength < (COMMAND_BUFFER_SIZE-1)) { 130 | buffer[bufferLength++] = ch; // advance bufferLength 131 | if (inCommandPayload > PAYLOAD_NORMAL) { 132 | if (inCommandPayload > 32 + 2) { // String way too long 133 | ch = '>'; // we end this nonsense 134 | inCommandPayload = PAYLOAD_NORMAL; 135 | DIAG(F("Parse error: Unbalanced string")); 136 | // fall through to ending parsing below 137 | } else if (ch == '"') { // String end 138 | inCommandPayload = PAYLOAD_NORMAL; 139 | continue; // do not fall through 140 | } else 141 | inCommandPayload++; 142 | } 143 | if (inCommandPayload == PAYLOAD_NORMAL) { 144 | if (ch == '>') { 145 | buffer[bufferLength] = '\0'; // This \0 is after the '>' 146 | DCCEXParser::parse(serial, buffer, NULL); // buffer parsed with trailing '>' 147 | inCommandPayload = PAYLOAD_FALSE; 148 | break; 149 | } else if (ch == '"') { 150 | inCommandPayload = PAYLOAD_STRING; 151 | } 152 | } 153 | } else { 154 | DIAG(F("Parse error: input buffer overflow")); 155 | inCommandPayload = PAYLOAD_FALSE; 156 | } 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /TrackManager.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2022 Chris Harlow 3 | * © 2022-2024 Harald Barth 4 | * © 2023 Colin Murdoch 5 | * 6 | * All rights reserved. 7 | * 8 | * This file is part of CommandStation-EX 9 | * 10 | * This is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * It is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with CommandStation. If not, see . 22 | */ 23 | #ifdef ARDUINO_ARCH_ESP32 24 | #include 25 | #endif 26 | #ifndef TrackManager_h 27 | #define TrackManager_h 28 | #include "FSH.h" 29 | #include "MotorDriver.h" 30 | // Virtualised Motor shield multi-track hardware Interface 31 | 32 | // These constants help EXRAIL macros say SET_TRACK(2,mode) OR SET_TRACK(C,mode) etc. 33 | const byte TRACK_NUMBER_0=0, TRACK_NUMBER_A=0; 34 | const byte TRACK_NUMBER_1=1, TRACK_NUMBER_B=1; 35 | const byte TRACK_NUMBER_2=2, TRACK_NUMBER_C=2; 36 | const byte TRACK_NUMBER_3=3, TRACK_NUMBER_D=3; 37 | const byte TRACK_NUMBER_4=4, TRACK_NUMBER_E=4; 38 | const byte TRACK_NUMBER_5=5, TRACK_NUMBER_F=5; 39 | const byte TRACK_NUMBER_6=6, TRACK_NUMBER_G=6; 40 | const byte TRACK_NUMBER_7=7, TRACK_NUMBER_H=7; 41 | 42 | // These constants help EXRAIL macros convert Track Power e.g. SET_POWER(A ON|OFF). 43 | const byte TRACK_POWER_0=0, TRACK_POWER_OFF=0; 44 | const byte TRACK_POWER_1=1, TRACK_POWER_ON=1; 45 | 46 | class TrackManager { 47 | public: 48 | static void Setup(const FSH * shieldName, 49 | MotorDriver * track0=NULL, 50 | MotorDriver * track1=NULL, 51 | MotorDriver * track2=NULL, 52 | MotorDriver * track3=NULL, 53 | MotorDriver * track4=NULL, 54 | MotorDriver * track5=NULL, 55 | MotorDriver * track6=NULL, 56 | MotorDriver * track7=NULL 57 | ); 58 | 59 | static void setDCCSignal( bool on); 60 | static void setPROGSignal( bool on); 61 | static void setDCSignal(int16_t cab, byte speedbyte); 62 | static MotorDriver * getProgDriver(); 63 | #ifdef ARDUINO_ARCH_ESP32 64 | static std::vectorgetMainDrivers(); 65 | #endif 66 | 67 | static void setPower(POWERMODE mode) {setMainPower(mode); setProgPower(mode);} 68 | static void setTrackPower(POWERMODE mode, byte t); 69 | static void setTrackPower(TRACK_MODE trackmode, POWERMODE powermode); 70 | static void setMainPower(POWERMODE mode) {setTrackPower(TRACK_MODE_MAIN, mode);} 71 | static void setProgPower(POWERMODE mode) {setTrackPower(TRACK_MODE_PROG, mode);} 72 | 73 | static const int16_t MAX_TRACKS=8; 74 | static inline int8_t numTracks() { return lastTrack + 1; } 75 | static bool setTrackMode(byte track, TRACK_MODE mode, int16_t DCaddr=0, bool offAtChange=true); 76 | static bool parseEqualSign(Print * stream, int16_t params, int16_t p[]); 77 | static void loop(); 78 | static POWERMODE getMainPower(); 79 | static POWERMODE getProgPower(); 80 | static inline POWERMODE getPower(byte t) { return track[t]->getPower(); } 81 | static bool getPower(byte t, char s[]); 82 | static void setJoin(bool join); 83 | static bool isJoined() { return progTrackSyncMain;} 84 | static inline bool isActive (byte tr) { 85 | if (tr > lastTrack) return false; 86 | return track[tr]->getMode() & (TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_BOOST|TRACK_MODE_EXT);} 87 | static void setJoinRelayPin(byte joinRelayPin); 88 | static void sampleCurrent(); 89 | static void reportGauges(Print* stream); 90 | static void reportCurrent(Print* stream); 91 | static void reportObsoleteCurrent(Print* stream); 92 | static void streamTrackState(Print* stream, byte t); 93 | static bool isPowerOn(byte t); 94 | static bool isProg(byte t); 95 | static TRACK_MODE getMode(byte t); 96 | static int16_t returnDCAddr(byte t); 97 | static const FSH* getModeName(TRACK_MODE Mode); 98 | 99 | static int16_t joinRelay; 100 | static bool progTrackSyncMain; // true when prog track is a siding switched to main 101 | static bool progTrackBoosted; // true when prog track is not current limited 102 | 103 | #ifdef DEBUG_ADC 104 | public: 105 | #else 106 | private: 107 | #endif 108 | static MotorDriver* track[MAX_TRACKS]; 109 | 110 | private: 111 | static void addTrack(byte t, MotorDriver* driver); 112 | static int8_t lastTrack; 113 | static byte nextCycleTrack; 114 | static void applyDCSpeed(byte t); 115 | 116 | static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC 117 | #ifdef ARDUINO_ARCH_ESP32 118 | static byte tempProgTrack; // holds the prog track number during join 119 | #endif 120 | }; 121 | 122 | #endif 123 | -------------------------------------------------------------------------------- /IO_EncoderThrottle.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2024, Chris Harlow. All rights reserved. 3 | * 4 | * This file is part of EX-CommandStation 5 | * 6 | * This is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * It is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with CommandStation. If not, see . 18 | */ 19 | 20 | /* 21 | * The IO_EncoderThrottle device driver uses a rotary encoder connected to vpins 22 | * to drive a loco. 23 | * Loco id is selected by writeAnalog. 24 | */ 25 | 26 | #include "IODevice.h" 27 | #include "DIAG.h" 28 | #include "DCC.h" 29 | 30 | const byte _DIR_CW = 0x10; // Clockwise step 31 | const byte _DIR_CCW = 0x20; // Counter-clockwise step 32 | 33 | const byte transition_table[5][4]= { 34 | {0,1,3,0}, // 0: 00 35 | {1,1,1,2 | _DIR_CW}, // 1: 00->01 36 | {2,2,0,2}, // 2: 00->01->11 37 | {3,3,3,4 | _DIR_CCW}, // 3: 00->10 38 | {4,0,4,4} // 4: 00->10->11 39 | }; 40 | 41 | const byte _STATE_MASK = 0x07; 42 | const byte _DIR_MASK = 0x30; 43 | 44 | 45 | 46 | void EncoderThrottle::create(VPIN firstVpin, int dtPin, int clkPin, int clickPin, byte notch) { 47 | if (checkNoOverlap(firstVpin)) new EncoderThrottle(firstVpin, dtPin,clkPin,clickPin,notch); 48 | } 49 | 50 | 51 | // Constructor 52 | EncoderThrottle::EncoderThrottle(VPIN firstVpin, int dtPin, int clkPin, int clickPin, byte notch){ 53 | _firstVpin = firstVpin; 54 | _nPins = 1; 55 | _I2CAddress = 0; 56 | _dtPin=dtPin; 57 | _clkPin=clkPin; 58 | _clickPin=clickPin; 59 | _notch=notch; 60 | _locoid=0; 61 | _stopState=xrSTOP; 62 | _rocoState=0; 63 | _prevpinstate=4; // not 01..11 64 | IODevice::configureInput(dtPin,true); 65 | IODevice::configureInput(clkPin,true); 66 | IODevice::configureInput(clickPin,true); 67 | addDevice(this); 68 | _display(); 69 | } 70 | 71 | 72 | 73 | void EncoderThrottle::_loop(unsigned long currentMicros) { 74 | if (_locoid==0) return; // not in use 75 | 76 | // Clicking down on the roco, stops the loco and sets the direction as unknown. 77 | if (IODevice::read(_clickPin)) { 78 | if (_stopState==xrSTOP) return; // debounced multiple stops 79 | DCC::setThrottle(_locoid,1,DCC::getThrottleDirection(_locoid)); 80 | _stopState=xrSTOP; 81 | DIAG(F("DRIVE %d STOP"),_locoid); 82 | return; 83 | } 84 | 85 | // read roco pins and detect state change 86 | byte pinstate = (IODevice::read(_dtPin) << 1) | IODevice::read(_clkPin); 87 | if (pinstate==_prevpinstate) return; 88 | _prevpinstate=pinstate; 89 | 90 | _rocoState = transition_table[_rocoState & _STATE_MASK][pinstate]; 91 | if ((_rocoState & _DIR_MASK) == 0) return; // no value change 92 | 93 | int change=(_rocoState & _DIR_CW)?+1:-1; 94 | // handle roco change -1 or +1 (clockwise) 95 | 96 | if (_stopState==xrSTOP) { 97 | // first move after button press sets the direction. (clockwise=fwd) 98 | _stopState=change>0?xrFWD:xrREV; 99 | } 100 | 101 | // when going fwd, clockwise increases speed. 102 | // but when reversing, anticlockwise increases speed. 103 | // This is similar to a center-zero pot control but with 104 | // the added safety that you cant panic-spin into the other 105 | // direction. 106 | if (_stopState==xrREV) change=-change; 107 | // manage limits 108 | int oldspeed=DCC::getThrottleSpeed(_locoid); 109 | if (oldspeed==1)oldspeed=0; // break out of estop 110 | int newspeed=change>0 ? (min((oldspeed+_notch),126)) : (max(0,(oldspeed-_notch))); 111 | if (newspeed==1) newspeed=0; // normal decelereated stop. 112 | if (oldspeed!=newspeed) { 113 | DIAG(F("DRIVE %d notch %S %d %S"),_locoid, 114 | change>0?F("UP"):F("DOWN"),_notch, 115 | _stopState==xrFWD?F("FWD"):F("REV")); 116 | DCC::setThrottle(_locoid,newspeed,_stopState==xrFWD); 117 | } 118 | } 119 | 120 | // Selocoid as analog value to start drive 121 | // use 122 | void EncoderThrottle::_writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) { 123 | (void) param2; 124 | _locoid=value; 125 | if (param1>0) _notch=param1; 126 | _rocoState=0; 127 | 128 | // If loco is moving, we inherit direction from it. 129 | _stopState=xrSTOP; 130 | if (_locoid>0) { 131 | auto speedbyte=DCC::getThrottleSpeedByte(_locoid); 132 | if ((speedbyte & 0x7f) >1) { 133 | // loco is moving 134 | _stopState= (speedbyte & 0x80)?xrFWD:xrREV; 135 | } 136 | } 137 | _display(); 138 | } 139 | 140 | 141 | void EncoderThrottle::_display() { 142 | DIAG(F("DRIVE vpin %d loco %d notch %d"),_firstVpin,_locoid,_notch); 143 | } 144 | -------------------------------------------------------------------------------- /DCC.h: -------------------------------------------------------------------------------- 1 | /* 2 | * © 2021 Mike S 3 | * © 2021 Fred Decker 4 | * © 2021 Herb Morton 5 | * © 2020-2021 Harald Barth 6 | * © 2020-2021 Chris Harlow 7 | * All rights reserved. 8 | * 9 | * This file is part of Asbelos DCC API 10 | * 11 | * This is free software: you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation, either version 3 of the License, or 14 | * (at your option) any later version. 15 | * 16 | * It is distributed in the hope that it will be useful, 17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | * GNU General Public License for more details. 20 | * 21 | * You should have received a copy of the GNU General Public License 22 | * along with CommandStation. If not, see . 23 | */ 24 | #ifndef DCC_h 25 | #define DCC_h 26 | #include 27 | #include "MotorDriver.h" 28 | #include "MotorDrivers.h" 29 | #include "FSH.h" 30 | 31 | #include "defines.h" 32 | #ifndef HIGHEST_SHORT_ADDR 33 | #define HIGHEST_SHORT_ADDR 127 34 | #else 35 | #if HIGHEST_SHORT_ADDR > 127 36 | #error short addr greater than 127 does not make sense 37 | #endif 38 | #endif 39 | #include "DCCACK.h" 40 | const uint16_t LONG_ADDR_MARKER = 0x4000; 41 | 42 | 43 | // Allocations with memory implications..! 44 | // Base system takes approx 900 bytes + 8 per loco. Turnouts, Sensors etc are dynamically created 45 | #if defined(HAS_ENOUGH_MEMORY) 46 | const byte MAX_LOCOS = 50; 47 | #else 48 | const byte MAX_LOCOS = 30; 49 | #endif 50 | 51 | class DCC 52 | { 53 | public: 54 | static inline void setShieldName(const FSH * motorShieldName) { 55 | shieldName=(FSH *)motorShieldName; 56 | }; 57 | static void begin(); 58 | static void loop(); 59 | 60 | // Public DCC API functions 61 | static void setThrottle(uint16_t cab, uint8_t tSpeed, bool tDirection); 62 | static int8_t getThrottleSpeed(int cab); 63 | static uint8_t getThrottleSpeedByte(int cab); 64 | static uint8_t getThrottleFrequency(int cab); 65 | static bool getThrottleDirection(int cab); 66 | static void writeCVByteMain(int cab, int cv, byte bValue); 67 | static void writeCVBitMain(int cab, int cv, byte bNum, bool bValue); 68 | static void setFunction(int cab, byte fByte, byte eByte); 69 | static bool setFn(int cab, int16_t functionNumber, bool on); 70 | static void changeFn(int cab, int16_t functionNumber); 71 | static int8_t getFn(int cab, int16_t functionNumber); 72 | static uint32_t getFunctionMap(int cab); 73 | static void setDCFreq(int cab,byte freq); 74 | static void updateGroupflags(byte &flags, int16_t functionNumber); 75 | static void setAccessory(int address, byte port, bool gate, byte onoff = 2); 76 | static bool setExtendedAccessory(int16_t address, int16_t value, byte repeats=3); 77 | static bool writeTextPacket(byte *b, int nBytes); 78 | 79 | // ACKable progtrack calls bitresults callback 0,0 or -1, cv returns value or -1 80 | static void readCV(int16_t cv, ACK_CALLBACK callback); 81 | static void readCVBit(int16_t cv, byte bitNum, ACK_CALLBACK callback); // -1 for error 82 | static void writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback); 83 | static void writeCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback); 84 | static void verifyCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback); 85 | static void verifyCVBit(int16_t cv, byte bitNum, bool bitValue, ACK_CALLBACK callback); 86 | 87 | static void getLocoId(ACK_CALLBACK callback); 88 | static void setLocoId(int id,ACK_CALLBACK callback); 89 | static void setConsistId(int id,bool reverse,ACK_CALLBACK callback); 90 | // Enhanced API functions 91 | static void forgetLoco(int cab); // removes any speed reminders for this loco 92 | static void forgetAllLocos(); // removes all speed reminders 93 | static void displayCabList(Print *stream); 94 | static FSH *getMotorShieldName(); 95 | static inline void setGlobalSpeedsteps(byte s) { 96 | globalSpeedsteps = s; 97 | }; 98 | 99 | struct LOCO 100 | { 101 | int loco; 102 | byte speedCode; 103 | byte groupFlags; 104 | uint32_t functions; 105 | }; 106 | static LOCO speedTable[MAX_LOCOS]; 107 | static int lookupSpeedTable(int locoId, bool autoCreate); 108 | static byte cv1(byte opcode, int cv); 109 | static byte cv2(int cv); 110 | 111 | private: 112 | static byte loopStatus; 113 | static void setThrottle2(uint16_t cab, uint8_t speedCode); 114 | static void updateLocoReminder(int loco, byte speedCode); 115 | static void setFunctionInternal(int cab, byte fByte, byte eByte, byte count); 116 | static bool issueReminder(int reg); 117 | static int lastLocoReminder; 118 | static int highestUsedReg; 119 | static FSH *shieldName; 120 | static byte globalSpeedsteps; 121 | 122 | static void issueReminders(); 123 | static void callback(int value); 124 | 125 | 126 | // NMRA codes # 127 | static const byte SET_SPEED = 0x3f; 128 | static const byte WRITE_BYTE_MAIN = 0xEC; 129 | static const byte WRITE_BIT_MAIN = 0xE8; 130 | static const byte WRITE_BYTE = 0x7C; 131 | static const byte VERIFY_BYTE = 0x74; 132 | static const byte BIT_MANIPULATE = 0x78; 133 | static const byte WRITE_BIT = 0xF0; 134 | static const byte VERIFY_BIT = 0xE0; 135 | static const byte BIT_ON = 0x08; 136 | static const byte BIT_OFF = 0x00; 137 | }; 138 | 139 | #endif 140 | -------------------------------------------------------------------------------- /Release - Architecture Doc/Prod-Release-Notes.md: -------------------------------------------------------------------------------- 1 | The DCC-EX Team is pleased to release CommandStation-EX-v3.0.0 as a Production Release. This release is a major re-write of earlier versions. We've re-architected the code-base so that it can better handle new features going forward. 2 | 3 | **Known Bugs:** 4 | - **Consisting through JMRI** - currently does not work in this release. You may use the command to do this manually. 5 | - **Wi-Fi** - works, but can be challenging to use if you want to switch between AP mode and STA station mode. 6 | - **Pololu Motor Shield** - is supported with this release, but the user may have to play around with some timings to enable programming mode due to limitation in its current sensing circuitry 7 | 8 | **Summary of the key new features added to CommandStation-EX V3.0.3** 9 | - ** command to write loco address and clear consist** 10 | - ** command will allow for consist address** 11 | - **Startup commands implemented** 12 | 13 | **Summary of the key new features added to CommandStation-EX V3.0.2:** 14 | - **Create new output for current in mA for ```` command** - New current response outputs current in mA, overlimit current, and maximum board capable current 15 | - **Simultaneously update JMRI to handle new current meter** 16 | 17 | **Summary of the key new features added to CommandStation-EX V3.0.1:** 18 | - **Add back fix for jitter** 19 | - **Add Turnouts, Outputs and Sensors to `````` command output** 20 | 21 | **Summary of the key new features added to CommandStation-EX V3.0.0:** 22 | 23 | - **New USB Browser Based Throttle** - WebThrottle-EX is a full front-end to controller to control the CS to run trains. 24 | - **WiFi Support** - AP and station modes supported. Auto-detection of an ESP8266 WiFi module with AT firmware on a Mega's serial port. Connection to JMRI and WiThrottle clients. 25 | - **Withrottle Integrations** - Act as a host for up to four WiThrottle clients concurrently. 26 | - **Add LCD/OLED support** - OLED supported on Mega only 27 | - **Improved CV programming routines** - checks for length of CV pulse, and breaks out of the wait state once it has received an ACK, now reading one CV per second. 28 | - **Improved current sensing** - rewrote current sensing routines for safer operation. Current thresholds based on milliamps, not magic numbers 29 | - **Individual track power control** - Ability to toggle power on either or both tracks, and to "JOIN" the tracks and make them output the same waveform for multiple power districts. 30 | - **Single or Dual-Pin PWM output** - Allows control of H-bridges with PH/EN or dual PWM inputs 31 | - **New, simpler function command** - `````` command allows setting functions based on their number, not based on a code as in `````` 32 | - **Function reminders** - Function reminders are sent in addition to speed reminders 33 | - **Functions to F28** - All NMRA functions are now supported 34 | - **Filters and user functions** - Ability to filter commands in the parser and execute custom code based on them. (ex: Redirect Turnout commands via NRF24) 35 | - **Diagnostic `````` commands** - See documentation for a full list of new diagnostic commands 36 | - **Rewrote DCC++ Parser** - more efficient operation, accepts multi-char input and uses less RAM 37 | - **Rewritten waveform generator** - capable of using any pin for DCC waveform out, eliminating the need for jumpers 38 | - **Rewritten packet generator** - Simplify and make smaller, remove idea of "registers" from original code 39 | - **Add free RAM messages** - Free RAM messages are now printed whenever there is a decerase in available RAM 40 | - **Fix EEPROM bugs** 41 | - **Number of locos discovery command** - ```<#>``` command 42 | - **Support for more locomotives** - 20 locomotives on an UNO and 50 an a Mega. 43 | - **Automatic slot managment** - slot variable in throttle/function commands are ignored and slot management is taken care of automatically. `````` command added to release locos from memory. 44 | 45 | 46 | **Key Contributors** 47 | 48 | **Project Lead** 49 | - Fred Decker - Holly Springs, North Carolina, USA (FlightRisk) 50 | 51 | **CommandStation-EX Developers** 52 | - Chris Harlow - Bournemouth, UK (UKBloke) 53 | - Harald Barth - Stockholm, Sweden (Haba) 54 | - Fred Decker - Holly Springs, North Carolina, USA (FlightRisk) 55 | - Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting) 56 | - M Steve Todd - - Engine Driver and JMRI Interface 57 | - Scott Catalanno - Pennsylvania 58 | - Gregor Baues - Île-de-France, France (grbba) 59 | 60 | **exInstaller Software** 61 | - Anthony W - Dayton, Ohio, USA (Dex, Dex++) 62 | 63 | **Website and Documentation** 64 | - Mani Kumar - Bangalor, India (Mani / Mani Kumar) 65 | - Fred Decker - Holly Springs, North Carolina, USA (FlightRisk) 66 | - Dave Cutting - Logan, Utah, USA (Dave Cutting/ David Cutting) 67 | - Roger Beschizza - Dorset, UK (Roger Beschizza) 68 | - Keith Ledbetter - Chicago, Illinois, USA (Keith Ledbetter) 69 | - Kevin Smith - (KCSmith) 70 | 71 | **WebThrotle-EX** 72 | - Fred Decker - Holly Springs, NC (FlightRisk/FrightRisk) 73 | - Mani Kumar - Bangalor, India (Mani /Mani Kumar) 74 | - Matt H - 75 | 76 | 77 | 78 | **Beta Testing / Release Management / Support** 79 | - Larry Dribin - Release Management 80 | - Keith Ledbetter 81 | - BradVan der Elst 82 | - Andrew Pye 83 | - Mike Bowers 84 | - Randy McKenzie 85 | - Roberto Bravin 86 | - Sim Brigden 87 | - Alan Lautenslager 88 | - Martin Bafver 89 | - Mário André Silva 90 | - Anthony Kochevar 91 | - Gajanatha Kobbekaduwe 92 | - Sumner Patterson 93 | - Paul - Virginia, USA 94 | --------------------------------------------------------------------------------