diff --git a/IODevice.h b/IODevice.h
index 74fe49b..d12fafd 100644
--- a/IODevice.h
+++ b/IODevice.h
@@ -542,8 +542,10 @@ protected:
#include "IO_MCP23017.h"
#include "IO_PCF8574.h"
#include "IO_PCF8575.h"
+#include "IO_PCA9555.h"
#include "IO_duinoNodes.h"
#include "IO_EXIOExpander.h"
+#include "IO_trainbrains.h"
#endif // iodevice_h
diff --git a/IO_trainbrains.h b/IO_trainbrains.h
new file mode 100644
index 0000000..058fe02
--- /dev/null
+++ b/IO_trainbrains.h
@@ -0,0 +1,98 @@
+/*
+ * © 2023, Chris Harlow. All rights reserved.
+ * © 2021, Neil McKechnie. All rights reserved.
+ *
+ * This file is part of DCC++EX API
+ *
+ * This is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * It is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with CommandStation. If not, see .
+ */
+
+#ifndef io_trainbrains_h
+#define io_trainbrains_h
+
+#include "IO_GPIOBase.h"
+#include "FSH.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////////////////
+/*
+ * IODevice subclass for trainbrains 3-block occupancy detector.
+ * For details see http://trainbrains.eu
+ */
+
+ enum TrackUnoccupancy
+{
+ TRACK_UNOCCUPANCY_UNKNOWN = 0,
+ TRACK_OCCUPIED = 1,
+ TRACK_UNOCCUPIED = 2
+};
+
+class Trainbrains02 : public GPIOBase {
+public:
+ static void create(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress) {
+ if (checkNoOverlap(vpin, nPins, i2cAddress)) new Trainbrains02(vpin, nPins, i2cAddress);
+ }
+
+private:
+ // Constructor
+ Trainbrains02(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1)
+ : GPIOBase((FSH *)F("Trainbrains02"), vpin, nPins, i2cAddress, interruptPin)
+ {
+ requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
+ outputBuffer, sizeof(outputBuffer));
+
+ outputBuffer[0] = (uint8_t)_I2CAddress; // strips away the mux part.
+ outputBuffer[1] =14;
+ outputBuffer[2] =1;
+ outputBuffer[3] =0; // This is the channel updated at each poling call
+ outputBuffer[4] =0;
+ outputBuffer[5] =0;
+ outputBuffer[6] =0;
+ outputBuffer[7] =0;
+ outputBuffer[8] =0;
+ outputBuffer[9] =0;
+ }
+
+ void _writeGpioPort() override {}
+
+ void _readGpioPort(bool immediate) override {
+ // cycle channel on device each time
+ outputBuffer[3]=channelInProgress+1; // 1-origin
+ channelInProgress++;
+ if(channelInProgress>=_nPins) channelInProgress=0;
+
+ if (immediate) {
+ _processCompletion(I2CManager.read(_I2CAddress, inputBuffer, sizeof(inputBuffer),
+ outputBuffer, sizeof(outputBuffer)));
+ } else {
+ // Queue new request
+ requestBlock.wait(); // Wait for preceding operation to complete
+ // Issue new request to read GPIO register
+ I2CManager.queueRequest(&requestBlock);
+ }
+ }
+
+ // This function is invoked when an I/O operation on the requestBlock completes.
+ void _processCompletion(uint8_t status) override {
+ if (status != I2C_STATUS_OK) inputBuffer[6]=TRACK_UNOCCUPANCY_UNKNOWN;
+ if (inputBuffer[6] == TRACK_UNOCCUPIED ) _portInputState |= 0x01 <